fix: move task pagination below list
This commit is contained in:
@@ -0,0 +1,63 @@
|
|||||||
|
import type { APIRequestContext, Page } from '@playwright/test'
|
||||||
|
import { expect, test } from './fixtures'
|
||||||
|
|
||||||
|
async function csrf(request: APIRequestContext) {
|
||||||
|
const state = await request.storageState()
|
||||||
|
return state.cookies.find(cookie => cookie.name === 'dodo_csrf')?.value ?? ''
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createTask(request: APIRequestContext, baseURL: string, listId: string, title: string) {
|
||||||
|
const response = await request.post('/api/v1/tasks', {
|
||||||
|
data: { title, list_id: listId },
|
||||||
|
headers: { 'x-csrf-token': await csrf(request), origin: baseURL },
|
||||||
|
})
|
||||||
|
expect(response.ok(), await response.text()).toBeTruthy()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openInbox(page: Page) {
|
||||||
|
const inbox = page.locator('.sidebar').getByRole('button', { name: '收集箱', exact: true })
|
||||||
|
if (await page.evaluate(() => window.innerWidth <= 930)) {
|
||||||
|
await page.locator('main .topbar > button').first().click()
|
||||||
|
}
|
||||||
|
await inbox.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
test('task pagination stays below the list and returns to the list start after navigation', async ({ page, request, baseURL }) => {
|
||||||
|
const bootstrapResponse = await request.get('/api/v1/bootstrap')
|
||||||
|
expect(bootstrapResponse.ok()).toBeTruthy()
|
||||||
|
const inbox = (await bootstrapResponse.json()).lists.find((item: { is_inbox: boolean }) => item.is_inbox)
|
||||||
|
expect(inbox).toBeTruthy()
|
||||||
|
|
||||||
|
for (let index = 1; index <= 51; index += 1) {
|
||||||
|
await createTask(request, baseURL!, inbox.id, `分页验收任务 ${String(index).padStart(2, '0')}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.goto('/')
|
||||||
|
await openInbox(page)
|
||||||
|
|
||||||
|
const taskList = page.locator('.task-list')
|
||||||
|
const pager = page.locator('.pager')
|
||||||
|
await expect(taskList.locator('.task-row')).toHaveCount(50)
|
||||||
|
await expect(pager).toBeVisible()
|
||||||
|
await expect(pager).toContainText('1 / 2')
|
||||||
|
|
||||||
|
const firstPageGeometry = await page.evaluate(() => {
|
||||||
|
const list = document.querySelector<HTMLElement>('.task-list')!.getBoundingClientRect()
|
||||||
|
const pagerBox = document.querySelector<HTMLElement>('.pager')!.getBoundingClientRect()
|
||||||
|
return { listBottom: list.bottom, pagerTop: pagerBox.top }
|
||||||
|
})
|
||||||
|
expect(firstPageGeometry.pagerTop).toBeGreaterThanOrEqual(firstPageGeometry.listBottom)
|
||||||
|
|
||||||
|
await pager.getByRole('button', { name: '下一页' }).click()
|
||||||
|
await expect(pager).toContainText('2 / 2')
|
||||||
|
await expect(taskList.locator('.task-row')).toHaveCount(1)
|
||||||
|
|
||||||
|
const secondPageGeometry = await page.evaluate(() => {
|
||||||
|
const list = document.querySelector<HTMLElement>('.task-list')!.getBoundingClientRect()
|
||||||
|
const pagerBox = document.querySelector<HTMLElement>('.pager')!.getBoundingClientRect()
|
||||||
|
return { listTop: list.top, listBottom: list.bottom, pagerTop: pagerBox.top, viewportHeight: innerHeight }
|
||||||
|
})
|
||||||
|
expect(secondPageGeometry.listTop).toBeGreaterThanOrEqual(-1)
|
||||||
|
expect(secondPageGeometry.listTop).toBeLessThan(secondPageGeometry.viewportHeight / 2)
|
||||||
|
expect(secondPageGeometry.pagerTop).toBeGreaterThanOrEqual(secondPageGeometry.listBottom)
|
||||||
|
})
|
||||||
+23
-9
@@ -56,6 +56,7 @@ let purgeListTrigger: HTMLElement | null = null
|
|||||||
const tasks = ref<Task[]>([])
|
const tasks = ref<Task[]>([])
|
||||||
const overdueTasks = ref<Task[]>([])
|
const overdueTasks = ref<Task[]>([])
|
||||||
const trash = ref<Task[]>([])
|
const trash = ref<Task[]>([])
|
||||||
|
const taskListElement = ref<HTMLElement | null>(null)
|
||||||
const NAVIGATION_STORAGE_KEY = 'dodo.navigation'
|
const NAVIGATION_STORAGE_KEY = 'dodo.navigation'
|
||||||
const restoredNavigation = readStoredNavigation(window.localStorage, NAVIGATION_STORAGE_KEY)
|
const restoredNavigation = readStoredNavigation(window.localStorage, NAVIGATION_STORAGE_KEY)
|
||||||
const activeList = ref(restoredNavigation.listId)
|
const activeList = ref(restoredNavigation.listId)
|
||||||
@@ -724,7 +725,7 @@ async function loadTrashPage() {
|
|||||||
async function loadTrash() {
|
async function loadTrash() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
error.value = ''
|
error.value = ''
|
||||||
return await runLatestRequest('trash', loadTrashPage, {
|
const committed = await runLatestRequest('trash', loadTrashPage, {
|
||||||
success: (data) => {
|
success: (data) => {
|
||||||
trash.value = data.items ?? []
|
trash.value = data.items ?? []
|
||||||
totalTasks.value = data.total ?? trash.value.length
|
totalTasks.value = data.total ?? trash.value.length
|
||||||
@@ -732,6 +733,11 @@ async function loadTrash() {
|
|||||||
error: fail,
|
error: fail,
|
||||||
finally: () => { loading.value = false },
|
finally: () => { loading.value = false },
|
||||||
})
|
})
|
||||||
|
if (committed && page.value > totalPages.value) {
|
||||||
|
page.value = totalPages.value
|
||||||
|
return await loadTrash()
|
||||||
|
}
|
||||||
|
return committed
|
||||||
}
|
}
|
||||||
async function switchView(view: View, listId?: string) {
|
async function switchView(view: View, listId?: string) {
|
||||||
if (activeView.value === 'memos' && view !== 'memos' && memoPanel.value?.dirty && !(await confirmAction('有未保存的更改', '确定离开当前备忘录吗?'))) return
|
if (activeView.value === 'memos' && view !== 'memos' && memoPanel.value?.dirty && !(await confirmAction('有未保存的更改', '确定离开当前备忘录吗?'))) return
|
||||||
@@ -1535,15 +1541,23 @@ function moveListWithinScope(item: TaskList, direction: 'up' | 'down') {
|
|||||||
void persistListMove(item, item.folder_id, move.targetId, move.placement)
|
void persistListMove(item, item.folder_id, move.targetId, move.placement)
|
||||||
closeSidebarAction()
|
closeSidebarAction()
|
||||||
}
|
}
|
||||||
function previousPage() {
|
async function scrollToTaskPageStart() {
|
||||||
|
await nextTick()
|
||||||
|
taskListElement.value?.scrollIntoView({ block: 'start' })
|
||||||
|
}
|
||||||
|
async function previousPage() {
|
||||||
if (page.value <= 1 || loading.value) return
|
if (page.value <= 1 || loading.value) return
|
||||||
page.value -= 1
|
page.value -= 1
|
||||||
activeView.value === 'trash' ? loadTrash() : isTaskView(activeView.value) ? loadAll() : undefined
|
if (activeView.value === 'trash') await loadTrash()
|
||||||
|
else if (isTaskView(activeView.value)) await loadAll()
|
||||||
|
await scrollToTaskPageStart()
|
||||||
}
|
}
|
||||||
function nextPage() {
|
async function nextPage() {
|
||||||
if (page.value >= totalPages.value || loading.value) return
|
if (page.value >= totalPages.value || loading.value) return
|
||||||
page.value += 1
|
page.value += 1
|
||||||
activeView.value === 'trash' ? loadTrash() : isTaskView(activeView.value) ? loadAll() : undefined
|
if (activeView.value === 'trash') await loadTrash()
|
||||||
|
else if (isTaskView(activeView.value)) await loadAll()
|
||||||
|
await scrollToTaskPageStart()
|
||||||
}
|
}
|
||||||
|
|
||||||
function reconcileDesktopPaneWidths() {
|
function reconcileDesktopPaneWidths() {
|
||||||
@@ -1718,10 +1732,8 @@ onUnmounted(() => {
|
|||||||
<button id="today-tasks-heading" class="today-section-toggle today-section-anchor" type="button" :aria-expanded="!todaySectionCollapse.tasks" aria-controls="today-tasks" @click="toggleTodaySection('tasks')"><span class="today-section-title">今天</span><span class="today-section-summary">{{totalTasks}}</span><span class="today-section-chevron" aria-hidden="true">{{ todaySectionCollapse.tasks ? '›' : '⌄' }}</span></button>
|
<button id="today-tasks-heading" class="today-section-toggle today-section-anchor" type="button" :aria-expanded="!todaySectionCollapse.tasks" aria-controls="today-tasks" @click="toggleTodaySection('tasks')"><span class="today-section-title">今天</span><span class="today-section-summary">{{totalTasks}}</span><span class="today-section-chevron" aria-hidden="true">{{ todaySectionCollapse.tasks ? '›' : '⌄' }}</span></button>
|
||||||
</template>
|
</template>
|
||||||
<div v-if="activeView==='tasks' || activeView==='upcoming'" id="task-list-heading" class="list-section-heading"><span id="task-list-title" class="list-section-title">任务</span><span class="list-section-count">{{ totalTasks }}</span><button v-if="activeView==='tasks' && taskReorderAvailable" class="list-section-action" type="button" :aria-pressed="taskReorderMode" @click="taskReorderMode=!taskReorderMode;cancelTaskReorder()">{{ taskReorderMode ? '完成' : '调整顺序' }}</button></div>
|
<div v-if="activeView==='tasks' || activeView==='upcoming'" id="task-list-heading" class="list-section-heading"><span id="task-list-title" class="list-section-title">任务</span><span class="list-section-count">{{ totalTasks }}</span><button v-if="activeView==='tasks' && taskReorderAvailable" class="list-section-action" type="button" :aria-pressed="taskReorderMode" @click="taskReorderMode=!taskReorderMode;cancelTaskReorder()">{{ taskReorderMode ? '完成' : '调整顺序' }}</button></div>
|
||||||
<div v-if="activeView==='trash'" class="list-toolbar"><span v-if="activeView==='trash' || totalPages > 1 || totalTasks > 0">{{ totalPages > 1 ? `第 ${page} / ${totalPages} 页 · ` : '' }}共 {{totalTasks}} 项</span><span class="list-toolbar-actions"><button v-if="taskReorderAvailable" class="soft-button reorder-mode-toggle task-reorder-toggle" type="button" :aria-pressed="taskReorderMode" @click="taskReorderMode=!taskReorderMode;cancelTaskReorder()">{{ taskReorderMode ? '完成' : '调整顺序' }}</button></span></div>
|
<div v-if="activeView==='trash'" class="list-toolbar"><span v-if="activeView==='trash' || totalPages > 1 || totalTasks > 0">共 {{totalTasks}} 项</span><span class="list-toolbar-actions"><button v-if="taskReorderAvailable" class="soft-button reorder-mode-toggle task-reorder-toggle" type="button" :aria-pressed="taskReorderMode" @click="taskReorderMode=!taskReorderMode;cancelTaskReorder()">{{ taskReorderMode ? '完成' : '调整顺序' }}</button></span></div>
|
||||||
<div v-if="activeView==='tasks' && totalPages > 1" class="list-page-meta"><span>第 {{ page }} / {{ totalPages }} 页 · 共 {{ totalTasks }} 项</span></div>
|
<section :id="activeView==='today' ? 'today-tasks' : undefined" class="task-list plain-list" :class="{loading}" v-show="activeView!=='today' || !todaySectionCollapse.tasks" :role="activeView==='today' ? 'region' : undefined" :aria-labelledby="activeView==='today' ? 'today-tasks-heading' : (activeView==='tasks' || activeView==='upcoming') ? 'task-list-title' : undefined" ref="taskListElement">
|
||||||
<div v-if="activeView!=='trash' && totalPages > 1" class="pager"><button class="secondary" :disabled="page<=1 || loading" @click="previousPage">上一页</button><span>{{page}} / {{totalPages}}</span><button class="secondary" :disabled="page>=totalPages || loading" @click="nextPage">下一页</button></div>
|
|
||||||
<section :id="activeView==='today' ? 'today-tasks' : undefined" class="task-list plain-list" :class="{loading}" v-show="activeView!=='today' || !todaySectionCollapse.tasks" :role="activeView==='today' ? 'region' : undefined" :aria-labelledby="activeView==='today' ? 'today-tasks-heading' : (activeView==='tasks' || activeView==='upcoming') ? 'task-list-title' : undefined">
|
|
||||||
<template v-for="node in taskTree" :key="node.task.id">
|
<template v-for="node in taskTree" :key="node.task.id">
|
||||||
<article :data-task-id="node.task.id" class="task-row swipeable" :class="{done:node.task.completed,'task-row--trash':activeView==='trash','just-completed': justCompletedTaskIds.has(node.task.id),'completion-exiting':completionExitingTaskIds.has(node.task.id),selected:selectedTask?.id===node.task.id,ready:Math.abs(taskSwipeOffsets[node.task.id] ?? 0) >= 64,reordering:taskReorder?.id===node.task.id,'reorder-target':taskReorderTarget===node.task.id}" :style="{ '--swipe-x': `${taskSwipeOffsets[node.task.id] ?? 0}px`, '--reorder-y': `${taskReorder?.id === node.task.id ? taskReorder.offsetY : 0}px` }" @pointerdown="startTaskPointer(node.task, $event)" @pointermove="moveTaskPointer(node.task, $event)" @pointerup="finishTaskPointer(node.task, $event)" @pointercancel="cancelTaskPointer(node.task)" @touchstart.passive="startTaskSwipe(node.task, $event)" @touchmove.passive="moveTaskSwipe(node.task, $event)" @touchend="finishTaskSwipe(node.task, $event)" @touchcancel="cancelTaskSwipe(node.task)">
|
<article :data-task-id="node.task.id" class="task-row swipeable" :class="{done:node.task.completed,'task-row--trash':activeView==='trash','just-completed': justCompletedTaskIds.has(node.task.id),'completion-exiting':completionExitingTaskIds.has(node.task.id),selected:selectedTask?.id===node.task.id,ready:Math.abs(taskSwipeOffsets[node.task.id] ?? 0) >= 64,reordering:taskReorder?.id===node.task.id,'reorder-target':taskReorderTarget===node.task.id}" :style="{ '--swipe-x': `${taskSwipeOffsets[node.task.id] ?? 0}px`, '--reorder-y': `${taskReorder?.id === node.task.id ? taskReorder.offsetY : 0}px` }" @pointerdown="startTaskPointer(node.task, $event)" @pointermove="moveTaskPointer(node.task, $event)" @pointerup="finishTaskPointer(node.task, $event)" @pointercancel="cancelTaskPointer(node.task)" @touchstart.passive="startTaskSwipe(node.task, $event)" @touchmove.passive="moveTaskSwipe(node.task, $event)" @touchend="finishTaskSwipe(node.task, $event)" @touchcancel="cancelTaskSwipe(node.task)">
|
||||||
<button v-if="taskReorderMode" class="drag-handle task-drag-handle" :disabled="totalPages > 1" aria-label="上下拖动任务排序" title="上下拖动排序" @pointerdown.stop="startTaskReorder(node.task, $event)" @pointermove.stop="moveTaskReorder(node.task, $event)" @pointerup.stop="finishTaskReorder(node.task, $event)" @pointercancel.stop="cancelTaskReorder"><GripVertical/></button>
|
<button v-if="taskReorderMode" class="drag-handle task-drag-handle" :disabled="totalPages > 1" aria-label="上下拖动任务排序" title="上下拖动排序" @pointerdown.stop="startTaskReorder(node.task, $event)" @pointermove.stop="moveTaskReorder(node.task, $event)" @pointerup.stop="finishTaskReorder(node.task, $event)" @pointercancel.stop="cancelTaskReorder"><GripVertical/></button>
|
||||||
@@ -1733,6 +1745,8 @@ onUnmounted(() => {
|
|||||||
<div v-if="activeView==='today' && hiddenCompletedTaskCount > 0 && !visibleTasks.length && !loading" class="today-filtered-empty-note"><span>已隐藏已完成任务</span><button type="button" class="link" @click="showCompleted=true">显示</button></div>
|
<div v-if="activeView==='today' && hiddenCompletedTaskCount > 0 && !visibleTasks.length && !loading" class="today-filtered-empty-note"><span>已隐藏已完成任务</span><button type="button" class="link" @click="showCompleted=true">显示</button></div>
|
||||||
<div v-else-if="!visibleTasks.length&&!loading" class="empty"><ListTodo/><b>{{ hiddenCompletedTaskCount > 0 ? '已完成任务已隐藏' : '这里还很安静' }}</b><span>{{hiddenCompletedTaskCount > 0 ? '开启“显示已完成”即可查看。':'写下第一件想完成的小事吧'}}</span></div>
|
<div v-else-if="!visibleTasks.length&&!loading" class="empty"><ListTodo/><b>{{ hiddenCompletedTaskCount > 0 ? '已完成任务已隐藏' : '这里还很安静' }}</b><span>{{hiddenCompletedTaskCount > 0 ? '开启“显示已完成”即可查看。':'写下第一件想完成的小事吧'}}</span></div>
|
||||||
</section>
|
</section>
|
||||||
|
<div v-if="activeView==='tasks' && totalPages > 1" class="list-page-meta" aria-live="polite"><span>第 {{ page }} / {{ totalPages }} 页 · 共 {{ totalTasks }} 项</span></div>
|
||||||
|
<div v-if="totalPages > 1 && (activeView!=='today' || !todaySectionCollapse.tasks)" class="pager"><button class="secondary" :disabled="page<=1 || loading" @click="previousPage">上一页</button><span aria-live="polite">{{page}} / {{totalPages}}</span><button class="secondary" :disabled="page>=totalPages || loading" @click="nextPage">下一页</button></div>
|
||||||
<div v-if="activeView==='today'" class="today-habits-section today-section-anchor">
|
<div v-if="activeView==='today'" class="today-habits-section today-section-anchor">
|
||||||
<button id="today-habits-heading" class="today-section-toggle" type="button" :aria-expanded="!todaySectionCollapse.habits" aria-controls="today-habits" @click="toggleTodaySection('habits')"><span class="today-section-title">习惯</span><span class="today-section-summary">{{todayHabitTotal}}</span><span class="today-section-chevron" aria-hidden="true">{{ todaySectionCollapse.habits ? '›' : '⌄' }}</span></button>
|
<button id="today-habits-heading" class="today-section-toggle" type="button" :aria-expanded="!todaySectionCollapse.habits" aria-controls="today-habits" @click="toggleTodaySection('habits')"><span class="today-section-title">习惯</span><span class="today-section-summary">{{todayHabitTotal}}</span><span class="today-section-chevron" aria-hidden="true">{{ todaySectionCollapse.habits ? '›' : '⌄' }}</span></button>
|
||||||
<div v-show="!todaySectionCollapse.habits" id="today-habits" role="region" aria-labelledby="today-habits-heading">
|
<div v-show="!todaySectionCollapse.habits" id="today-habits" role="region" aria-labelledby="today-habits-heading">
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ main.list-main>.mvp-view>.habit-archive-section{border-top:1px solid #e8e0d5}
|
|||||||
.list-section-heading{width:min(100%,630px);min-height:44px;margin:0 auto;display:flex;align-items:center;border-bottom:1px solid #e8e0d5}
|
.list-section-heading{width:min(100%,630px);min-height:44px;margin:0 auto;display:flex;align-items:center;border-bottom:1px solid #e8e0d5}
|
||||||
.list-section-title{font-size:13px;font-weight:700}.list-section-count{margin-left:7px;font-size:12px;font-weight:400;color:var(--muted)}
|
.list-section-title{font-size:13px;font-weight:700}.list-section-count{margin-left:7px;font-size:12px;font-weight:400;color:var(--muted)}
|
||||||
.list-section-action{min-height:44px;margin-left:auto;padding:0;border:0;background:transparent;color:var(--accent);font-size:12px;font-weight:650}
|
.list-section-action{min-height:44px;margin-left:auto;padding:0;border:0;background:transparent;color:var(--accent);font-size:12px;font-weight:650}
|
||||||
.list-page-meta{min-height:36px;display:flex;align-items:center;justify-content:flex-end;gap:10px;color:var(--muted);font-size:12px}
|
.list-page-meta{min-height:36px;margin-top:10px;display:flex;align-items:center;justify-content:flex-end;gap:10px;color:var(--muted);font-size:12px}
|
||||||
@media(min-width:1440px){main.list-main>.list-page-context,main.list-main>.list-section-heading,main.list-main>.task-list,main.list-main>.list-page-meta,main.list-main>.pager,main.list-main>.mvp-view{width:min(100%,900px)}}
|
@media(min-width:1440px){main.list-main>.list-page-context,main.list-main>.list-section-heading,main.list-main>.task-list,main.list-main>.list-page-meta,main.list-main>.pager,main.list-main>.mvp-view{width:min(100%,900px)}}
|
||||||
@media(min-width:721px) and (max-width:1439px){main.list-main>.list-page-context,main.list-main>.list-section-heading,main.list-main>.task-list,main.list-main>.list-page-meta,main.list-main>.pager,main.list-main>.mvp-view{width:min(100%,630px)}}
|
@media(min-width:721px) and (max-width:1439px){main.list-main>.list-page-context,main.list-main>.list-section-heading,main.list-main>.task-list,main.list-main>.list-page-meta,main.list-main>.pager,main.list-main>.mvp-view{width:min(100%,630px)}}
|
||||||
@media(max-width:720px){main.list-main{padding-left:29px;padding-right:29px}main.list-main>.list-page-context,main.list-main>.list-section-heading,main.list-main>.task-list,main.list-main>.list-page-meta,main.list-main>.pager,main.list-main>.mvp-view{width:100%}.list-page-title{font-size:24px}}
|
@media(max-width:720px){main.list-main{padding-left:29px;padding-right:29px}main.list-main>.list-page-context,main.list-main>.list-section-heading,main.list-main>.task-list,main.list-main>.list-page-meta,main.list-main>.pager,main.list-main>.mvp-view{width:100%}.list-page-title{font-size:24px}}
|
||||||
|
|||||||
@@ -849,7 +849,10 @@ describe('task and habit row decoration', () => {
|
|||||||
const mutationBlock = app.slice(app.indexOf('async function mutateTrashTask'), app.indexOf('async function restoreTask'))
|
const mutationBlock = app.slice(app.indexOf('async function mutateTrashTask'), app.indexOf('async function restoreTask'))
|
||||||
const restoreBlock = app.slice(app.indexOf('async function restoreTask'), app.indexOf('async function purgeTask'))
|
const restoreBlock = app.slice(app.indexOf('async function restoreTask'), app.indexOf('async function purgeTask'))
|
||||||
const purgeBlock = app.slice(app.indexOf('async function purgeTask'), app.indexOf('async function addSubtask'))
|
const purgeBlock = app.slice(app.indexOf('async function purgeTask'), app.indexOf('async function addSubtask'))
|
||||||
expect(loadBlock).toContain('return await runLatestRequest')
|
expect(loadBlock).toContain("const committed = await runLatestRequest('trash'")
|
||||||
|
expect(loadBlock).toContain('if (committed && page.value > totalPages.value)')
|
||||||
|
expect(loadBlock).toContain('page.value = totalPages.value')
|
||||||
|
expect(loadBlock).toContain('return await loadTrash()')
|
||||||
expect(mutationBlock).toContain('await taskMutationReconciler.run(')
|
expect(mutationBlock).toContain('await taskMutationReconciler.run(')
|
||||||
expect(mutationBlock).toContain('{ affectsTrash: true, affectsTaskView }')
|
expect(mutationBlock).toContain('{ affectsTrash: true, affectsTaskView }')
|
||||||
expect(mutationBlock).not.toContain('performTrashMutation(')
|
expect(mutationBlock).not.toContain('performTrashMutation(')
|
||||||
|
|||||||
@@ -81,6 +81,14 @@ describe('approved five-detail polish', () => {
|
|||||||
expect(app).toContain("v-if=\"activeView==='tasks' && taskReorderAvailable\"")
|
expect(app).toContain("v-if=\"activeView==='tasks' && taskReorderAvailable\"")
|
||||||
expect(app).toContain('<span class="list-section-count">{{ totalTasks }}</span>')
|
expect(app).toContain('<span class="list-section-count">{{ totalTasks }}</span>')
|
||||||
expect(app).toContain("v-if=\"activeView==='tasks' && totalPages > 1\" class=\"list-page-meta\"")
|
expect(app).toContain("v-if=\"activeView==='tasks' && totalPages > 1\" class=\"list-page-meta\"")
|
||||||
|
const taskListEnd = app.indexOf('</section>', app.indexOf('class=\"task-list plain-list\"'))
|
||||||
|
expect(app.indexOf('class=\"list-page-meta\"')).toBeGreaterThan(taskListEnd)
|
||||||
|
expect(app.indexOf('class=\"pager\"')).toBeGreaterThan(taskListEnd)
|
||||||
|
expect(app).toContain('ref="taskListElement"')
|
||||||
|
expect(app).toContain("totalPages > 1 && (activeView!=='today' || !todaySectionCollapse.tasks)")
|
||||||
|
expect(app).toContain("taskListElement.value?.scrollIntoView({ block: 'start' })")
|
||||||
|
expect(app).toContain('class="list-page-meta" aria-live="polite"')
|
||||||
|
expect(app).toContain('<span aria-live="polite">{{page}} / {{totalPages}}</span>')
|
||||||
expect(app).toContain("if (activeView.value === 'upcoming') { openParams.set('due_from', isoAtLocalDayOffset(0)); openParams.set('due_to', isoAtLocalDayOffset(8)) }")
|
expect(app).toContain("if (activeView.value === 'upcoming') { openParams.set('due_from', isoAtLocalDayOffset(0)); openParams.set('due_to', isoAtLocalDayOffset(8)) }")
|
||||||
expect(app).toContain("new Date(task.due_at) >= startOfLocalDay(0) && new Date(task.due_at) < startOfLocalDay(8)")
|
expect(app).toContain("new Date(task.due_at) >= startOfLocalDay(0) && new Date(task.due_at) < startOfLocalDay(8)")
|
||||||
expect(app).not.toContain('class="list-search-clear"')
|
expect(app).not.toContain('class="list-search-clear"')
|
||||||
|
|||||||
Reference in New Issue
Block a user