[verified] move trash restore into action menu
This commit is contained in:
@@ -50,7 +50,7 @@ test('Trash uses compact deadline groups without exposing child-level actions',
|
|||||||
for (const title of [overdueTitle, futureTitle, undatedTitle]) {
|
for (const title of [overdueTitle, futureTitle, undatedTitle]) {
|
||||||
const row = page.locator('.task-row--trash').filter({ hasText: title })
|
const row = page.locator('.task-row--trash').filter({ hasText: title })
|
||||||
await expect(row).toHaveCount(1)
|
await expect(row).toHaveCount(1)
|
||||||
await expect(row.getByRole('button', { name: '恢复' })).toBeVisible()
|
await expect(row.getByRole('button', { name: '恢复' })).toHaveCount(0)
|
||||||
await expect(row.getByRole('button', { name: '打开任务操作' })).toBeVisible()
|
await expect(row.getByRole('button', { name: '打开任务操作' })).toBeVisible()
|
||||||
expect(await row.locator('.task-check').count()).toBe(0)
|
expect(await row.locator('.task-check').count()).toBe(0)
|
||||||
}
|
}
|
||||||
@@ -62,10 +62,12 @@ test('Trash uses compact deadline groups without exposing child-level actions',
|
|||||||
await actionTrigger.press('Enter')
|
await actionTrigger.press('Enter')
|
||||||
const menu = page.getByRole('menu', { name: '回收站任务操作' })
|
const menu = page.getByRole('menu', { name: '回收站任务操作' })
|
||||||
await expect(menu).toBeVisible()
|
await expect(menu).toBeVisible()
|
||||||
await expect(menu.getByRole('menuitem', { name: '永久删除' })).toBeFocused()
|
await expect(menu.getByRole('menuitem', { name: '恢复', exact: true })).toBeFocused()
|
||||||
await menu.getByRole('menuitem', { name: '永久删除' }).press('Tab')
|
await menu.getByRole('menuitem', { name: '恢复', exact: true }).press('Tab')
|
||||||
await expect(menu.getByRole('menuitem', { name: '永久删除' })).toBeFocused()
|
await expect(menu.getByRole('menuitem', { name: '永久删除' })).toBeFocused()
|
||||||
await menu.getByRole('menuitem', { name: '永久删除' }).press('Shift+Tab')
|
await menu.getByRole('menuitem', { name: '永久删除' }).press('Shift+Tab')
|
||||||
|
await expect(menu.getByRole('menuitem', { name: '恢复', exact: true })).toBeFocused()
|
||||||
|
await menu.getByRole('menuitem', { name: '恢复', exact: true }).press('ArrowUp')
|
||||||
await expect(menu.getByRole('menuitem', { name: '永久删除' })).toBeFocused()
|
await expect(menu.getByRole('menuitem', { name: '永久删除' })).toBeFocused()
|
||||||
await menu.getByRole('menuitem', { name: '永久删除' }).press('Escape')
|
await menu.getByRole('menuitem', { name: '永久删除' }).press('Escape')
|
||||||
await expect(menu).toBeHidden()
|
await expect(menu).toBeHidden()
|
||||||
|
|||||||
@@ -106,13 +106,14 @@ test('task rows use the body for detail and Trash keeps distinct actions', async
|
|||||||
const restoreRow = await taskRow(page, restoreTitle)
|
const restoreRow = await taskRow(page, restoreTitle)
|
||||||
const purgeRow = await taskRow(page, purgeTitle)
|
const purgeRow = await taskRow(page, purgeTitle)
|
||||||
for (const deletedRow of [restoreRow, purgeRow]) {
|
for (const deletedRow of [restoreRow, purgeRow]) {
|
||||||
await expect(deletedRow.getByRole('button', { name: '恢复' })).toBeVisible()
|
await expect(deletedRow.getByRole('button', { name: '恢复' })).toHaveCount(0)
|
||||||
await expect(deletedRow.getByRole('button', { name: '打开任务操作' })).toBeVisible()
|
await expect(deletedRow.getByRole('button', { name: '打开任务操作' })).toBeVisible()
|
||||||
await expect(deletedRow.locator('.task-main')).not.toHaveAttribute('role')
|
await expect(deletedRow.locator('.task-main')).not.toHaveAttribute('role')
|
||||||
await expect(deletedRow.locator('.task-main')).not.toHaveAttribute('tabindex')
|
await expect(deletedRow.locator('.task-main')).not.toHaveAttribute('tabindex')
|
||||||
expect(await deletedRow.locator('.task-detail-trigger, .task-check').count()).toBe(0)
|
expect(await deletedRow.locator('.task-detail-trigger, .task-check').count()).toBe(0)
|
||||||
}
|
}
|
||||||
await restoreRow.getByRole('button', { name: '恢复' }).click()
|
await restoreRow.getByRole('button', { name: '打开任务操作' }).click()
|
||||||
|
await page.getByRole('menuitem', { name: '恢复', exact: true }).click()
|
||||||
await expect(restoreRow).toHaveCount(0)
|
await expect(restoreRow).toHaveCount(0)
|
||||||
await purgeRow.getByRole('button', { name: '打开任务操作' }).click()
|
await purgeRow.getByRole('button', { name: '打开任务操作' }).click()
|
||||||
await page.getByRole('menuitem', { name: '永久删除' }).click()
|
await page.getByRole('menuitem', { name: '永久删除' }).click()
|
||||||
|
|||||||
+15
-3
@@ -1172,7 +1172,12 @@ function openTrashAction(task: Task, event?: Event) {
|
|||||||
trashAction.value = task
|
trashAction.value = task
|
||||||
nextTick(() => trashMenu.value?.querySelector<HTMLElement>('[role=menuitem]')?.focus())
|
nextTick(() => trashMenu.value?.querySelector<HTMLElement>('[role=menuitem]')?.focus())
|
||||||
}
|
}
|
||||||
function focusTrashMenuItem() { trashMenu.value?.querySelector<HTMLElement>('[role=menuitem]')?.focus() }
|
function moveTrashMenuFocus(step: number) {
|
||||||
|
const items = [...(trashMenu.value?.querySelectorAll<HTMLElement>('[role=menuitem]') ?? [])]
|
||||||
|
if (!items.length) return
|
||||||
|
const activeIndex = items.indexOf(document.activeElement as HTMLElement)
|
||||||
|
items[(activeIndex + step + items.length) % items.length]?.focus()
|
||||||
|
}
|
||||||
function focusTrashActionTrigger(trigger: HTMLElement | null) {
|
function focusTrashActionTrigger(trigger: HTMLElement | null) {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (trigger?.isConnected) trigger.focus()
|
if (trigger?.isConnected) trigger.focus()
|
||||||
@@ -1185,6 +1190,13 @@ function closeTrashAction(restoreFocus = true) {
|
|||||||
trashActionTrigger = null
|
trashActionTrigger = null
|
||||||
if (restoreFocus) focusTrashActionTrigger(trigger)
|
if (restoreFocus) focusTrashActionTrigger(trigger)
|
||||||
}
|
}
|
||||||
|
async function requestRestoreTask() {
|
||||||
|
const task = trashAction.value
|
||||||
|
const trigger = trashActionTrigger
|
||||||
|
closeTrashAction(false)
|
||||||
|
if (task) await restoreTask(task)
|
||||||
|
focusTrashActionTrigger(trigger)
|
||||||
|
}
|
||||||
async function requestPurgeTask() {
|
async function requestPurgeTask() {
|
||||||
const task = trashAction.value
|
const task = trashAction.value
|
||||||
const trigger = trashActionTrigger
|
const trigger = trashActionTrigger
|
||||||
@@ -1785,7 +1797,7 @@ onUnmounted(() => {
|
|||||||
<article v-for="node in group.nodes" :key="node.task.id" :data-task-id="node.task.id" class="task-row task-row--trash">
|
<article v-for="node in group.nodes" :key="node.task.id" :data-task-id="node.task.id" class="task-row task-row--trash">
|
||||||
<div class="task-main"><strong :title="node.task.title">{{node.task.title}}</strong><span v-if="node.subtasks.length" class="meta"><span class="meta-item"><ListChecks/>含 {{node.subtasks.length}} 个子任务,整组处理</span></span></div>
|
<div class="task-main"><strong :title="node.task.title">{{node.task.title}}</strong><span v-if="node.subtasks.length" class="meta"><span class="meta-item"><ListChecks/>含 {{node.subtasks.length}} 个子任务,整组处理</span></span></div>
|
||||||
<span v-if="node.task.due_at" class="task-tail"><TaskDueDisplay :due-at="node.task.due_at" :due-has-time="node.task.due_has_time" :completed="node.task.completed" :now-ms="taskDueNowMs" /></span>
|
<span v-if="node.task.due_at" class="task-tail"><TaskDueDisplay :due-at="node.task.due_at" :due-has-time="node.task.due_has_time" :completed="node.task.completed" :now-ms="taskDueNowMs" /></span>
|
||||||
<span class="task-actions"><button class="restore" @click.stop="restoreTask(node.task)"><ArchiveRestore/>恢复</button><button class="icon danger ghost trash-more" aria-label="打开任务操作" aria-haspopup="menu" :aria-expanded="trashAction?.id===node.task.id" :title="`${node.task.title}操作`" @click.stop="openTrashAction(node.task,$event)"><Ellipsis/></button></span>
|
<span class="task-actions"><button class="icon ghost trash-more" aria-label="打开任务操作" aria-haspopup="menu" :aria-expanded="trashAction?.id===node.task.id" :title="`${node.task.title}操作`" @click.stop="openTrashAction(node.task,$event)"><Ellipsis/></button></span>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -1873,7 +1885,7 @@ onUnmounted(() => {
|
|||||||
<Transition name="toast"><div v-if="notice" class="toast" role="status">{{notice}}</div></Transition>
|
<Transition name="toast"><div v-if="notice" class="toast" role="status">{{notice}}</div></Transition>
|
||||||
<div v-if="error" class="error-toast" role="alert">{{error}}<button @click="error=''"><X/></button></div>
|
<div v-if="error" class="error-toast" role="alert">{{error}}<button @click="error=''"><X/></button></div>
|
||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<span v-if="trashAction" class="trash-action-mask" @click.self="closeTrashAction()"><span ref="trashMenu" class="trash-action-menu" role="menu" aria-label="回收站任务操作" @keydown.esc.stop="closeTrashAction()" @keydown.tab.prevent="focusTrashMenuItem"><span class="trash-action-title" :title="trashAction.title">{{trashAction.title}}</span><button role="menuitem" class="danger-text" @click="requestPurgeTask"><Trash2/>永久删除</button></span></span>
|
<span v-if="trashAction" class="trash-action-mask" @click.self="closeTrashAction()"><span ref="trashMenu" class="trash-action-menu" role="menu" aria-label="回收站任务操作" @keydown.esc.stop="closeTrashAction()" @keydown.tab.prevent="moveTrashMenuFocus($event.shiftKey?-1:1)" @keydown.down.prevent="moveTrashMenuFocus(1)" @keydown.up.prevent="moveTrashMenuFocus(-1)"><span class="trash-action-title" :title="trashAction.title">{{trashAction.title}}</span><button role="menuitem" @click="requestRestoreTask"><ArchiveRestore/>恢复</button><button role="menuitem" class="danger-text" @click="requestPurgeTask"><Trash2/>永久删除</button></span></span>
|
||||||
<span v-if="archivedListAction" class="archived-action-mask" @click.self="closeArchivedListAction()"><span ref="archivedMenu" class="archived-row-actions" :style="archivedMenuStyle" role="menu"><button role="menuitem" @click="restoreList(archivedListAction)"><ArchiveRestore/>恢复清单</button><button role="menuitem" class="danger-text" @click="openPurgeList(archivedListAction)"><Trash2/>永久删除清单</button></span></span>
|
<span v-if="archivedListAction" class="archived-action-mask" @click.self="closeArchivedListAction()"><span ref="archivedMenu" class="archived-row-actions" :style="archivedMenuStyle" role="menu"><button role="menuitem" @click="restoreList(archivedListAction)"><ArchiveRestore/>恢复清单</button><button role="menuitem" class="danger-text" @click="openPurgeList(archivedListAction)"><Trash2/>永久删除清单</button></span></span>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
<AppSheet :open="Boolean(purgeListTarget)" variant="actions" panel-class="purge-list-dialog" title-id="purge-list-title" description-id="purge-list-description" initial-focus=".secondary" :busy="purgeListSubmitting" @close="closePurgeList">
|
<AppSheet :open="Boolean(purgeListTarget)" variant="actions" panel-class="purge-list-dialog" title-id="purge-list-title" description-id="purge-list-description" initial-focus=".secondary" :busy="purgeListSubmitting" @close="closePurgeList">
|
||||||
|
|||||||
@@ -125,9 +125,9 @@ main>.trash-page-context,main>.trash-groups,main:has(>.trash-page-context)>.empt
|
|||||||
.trash-page-context{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:start;gap:16px;padding:7px 0 21px;border-bottom:1px solid #e8e0d5}
|
.trash-page-context{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:start;gap:16px;padding:7px 0 21px;border-bottom:1px solid #e8e0d5}
|
||||||
.trash-page-title{margin:0;font-size:34px;line-height:1.1;font-weight:700;letter-spacing:-1.2px}.trash-page-summary{margin:8px 0 0;color:var(--muted);font-size:13px}.trash-page-count{min-height:44px;display:flex;align-items:center;color:var(--muted);font-size:12px}
|
.trash-page-title{margin:0;font-size:34px;line-height:1.1;font-weight:700;letter-spacing:-1.2px}.trash-page-summary{margin:8px 0 0;color:var(--muted);font-size:13px}.trash-page-count{min-height:44px;display:flex;align-items:center;color:var(--muted);font-size:12px}
|
||||||
.trash-groups{display:grid}.trash-group{display:grid}.trash-group-heading{min-height:44px;padding-top:11px;display:flex;align-items:center;gap:7px;border-bottom:1px solid #e8e0d5}.trash-group-heading h2{margin:0;font-size:13px;font-weight:750}.trash-group-heading span{color:var(--muted);font-size:12px}
|
.trash-groups{display:grid}.trash-group{display:grid}.trash-group-heading{min-height:44px;padding-top:11px;display:flex;align-items:center;gap:7px;border-bottom:1px solid #e8e0d5}.trash-group-heading h2{margin:0;font-size:13px;font-weight:750}.trash-group-heading span{color:var(--muted);font-size:12px}
|
||||||
.trash-list>.task-row,.trash-list>.task-row:has(>.task-tail .task-due--timed){height:62px;min-height:62px;max-height:62px}.trash-list .task-main{display:grid;align-content:center;gap:3px}.trash-list .task-main strong{font-size:14px;font-weight:620}.trash-list .task-main .meta{padding:0;font-size:11px}.trash-list .task-main .meta-item{display:inline-flex;align-items:center;gap:4px}.trash-list .task-main .meta-item svg{width:13px;height:13px}.trash-list .task-actions{display:flex;align-items:center;gap:0}.trash-list .restore{min-height:44px}.trash-list .trash-more{width:44px;height:44px;color:var(--muted);opacity:1}.trash-list .trash-more svg{width:18px;height:18px}.trash-action-mask{position:fixed;z-index:90;inset:0;display:flex;align-items:flex-end;justify-content:center;background:rgba(45,38,31,.18)}.trash-action-menu{width:min(320px,calc(100vw - 24px));margin:12px;padding:8px;display:grid;gap:4px;background:var(--surface-raised);border:1px solid var(--border-cream);border-radius:var(--radius-panel);box-shadow:var(--highlight-inner),var(--shadow-raised)}.trash-action-title{min-width:0;padding:9px 10px 7px;color:var(--muted);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.trash-action-menu button{min-height:44px;width:100%;display:flex;align-items:center;gap:9px;padding:0 10px;border:0;border-radius:var(--radius-control);background:transparent;text-align:left}.trash-action-menu button:hover{background:#fff3ee}
|
.trash-list>.task-row,.trash-list>.task-row:has(>.task-tail .task-due--timed){height:62px;min-height:62px;max-height:62px}.trash-list .task-main{display:grid;align-content:center;gap:3px}.trash-list .task-main strong{font-size:14px;font-weight:620}.trash-list .task-main .meta{padding:0;font-size:11px}.trash-list .task-main .meta-item{display:inline-flex;align-items:center;gap:4px}.trash-list .task-main .meta-item svg{width:13px;height:13px}.trash-list .task-actions{display:flex;align-items:center;gap:0}.trash-list .trash-more{width:44px;height:44px;color:var(--muted);opacity:1}.trash-list .trash-more svg{width:18px;height:18px}.trash-action-mask{position:fixed;z-index:90;inset:0;display:flex;align-items:flex-end;justify-content:center;background:rgba(45,38,31,.18)}.trash-action-menu{width:min(320px,calc(100vw - 24px));margin:12px;padding:8px;display:grid;gap:4px;background:var(--surface-raised);border:1px solid var(--border-cream);border-radius:var(--radius-panel);box-shadow:var(--highlight-inner),var(--shadow-raised)}.trash-action-title{min-width:0;padding:9px 10px 7px;color:var(--muted);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.trash-action-menu button{min-height:44px;width:100%;display:flex;align-items:center;gap:9px;padding:0 10px;border:0;border-radius:var(--radius-control);background:transparent;text-align:left}.trash-action-menu button:hover{background:#fff3ee}
|
||||||
@media(max-width:720px){main:has(>.trash-page-context){padding-left:29px;padding-right:29px;padding-bottom:calc(78px + var(--safe-area-bottom))}main>.trash-page-context,main>.trash-groups,main:has(>.trash-page-context)>.empty,main:has(>.trash-page-context)>.pager{width:100%}.trash-page-context{padding-top:2px;padding-bottom:17px}.trash-page-title{font-size:24px}.trash-page-summary{font-size:12px;line-height:1.5}.trash-group-heading{padding-top:9px}.trash-list>.task-row{height:68px;min-height:68px;max-height:68px;grid-template-columns:minmax(0,1fr) minmax(38px,auto) auto;gap:6px}.trash-list .task-tail{display:flex;max-width:64px;padding-left:0;font-size:11px}.trash-list .task-due--timed .task-due__absolute,.trash-list .task-due--timed .task-due__separator{display:none}.trash-list .restore{min-width:68px;padding-inline:10px}}
|
@media(max-width:720px){main:has(>.trash-page-context){padding-left:29px;padding-right:29px;padding-bottom:calc(78px + var(--safe-area-bottom))}main>.trash-page-context,main>.trash-groups,main:has(>.trash-page-context)>.empty,main:has(>.trash-page-context)>.pager{width:100%}.trash-page-context{padding-top:2px;padding-bottom:17px}.trash-page-title{font-size:24px}.trash-page-summary{font-size:12px;line-height:1.5}.trash-group-heading{padding-top:9px}.trash-list>.task-row{height:68px;min-height:68px;max-height:68px;grid-template-columns:minmax(0,1fr) minmax(38px,auto) auto;gap:6px}.trash-list .task-tail{display:flex;max-width:64px;padding-left:0;font-size:11px}.trash-list .task-due--timed .task-due__absolute,.trash-list .task-due--timed .task-due__separator{display:none}}
|
||||||
@media(max-width:390px){main:has(>.trash-page-context){padding-left:17px;padding-right:17px}.trash-page-context{gap:10px}.trash-page-summary{max-width:230px}.trash-list .task-actions{margin-left:6px}.trash-list .restore{min-width:68px;padding-inline:10px}}
|
@media(max-width:390px){main:has(>.trash-page-context){padding-left:17px;padding-right:17px}.trash-page-context{gap:10px}.trash-page-summary{max-width:230px}.trash-list .task-actions{margin-left:6px}}
|
||||||
|
|
||||||
/* Approved task-list and full-Habits 01 parity. */
|
/* Approved task-list and full-Habits 01 parity. */
|
||||||
main.list-main{background:#fdfaf3}
|
main.list-main{background:#fdfaf3}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ describe('unified task due display', () => {
|
|||||||
expect(app.match(/<span v-if="[^\"]+\.due_at" class="task-tail"><TaskDueDisplay/g)).toHaveLength(3)
|
expect(app.match(/<span v-if="[^\"]+\.due_at" class="task-tail"><TaskDueDisplay/g)).toHaveLength(3)
|
||||||
expect(app).not.toContain('class="meta"><TaskDueDisplay')
|
expect(app).not.toContain('class="meta"><TaskDueDisplay')
|
||||||
expect(app).toContain('</div><span v-if="node.task.due_at" class="task-tail"><TaskDueDisplay')
|
expect(app).toContain('</div><span v-if="node.task.due_at" class="task-tail"><TaskDueDisplay')
|
||||||
expect(app).toContain('<span class="task-actions"><button class="restore" @click.stop="restoreTask(node.task)">')
|
expect(app).toContain('<span class="task-actions"><button class="icon ghost trash-more"')
|
||||||
expect(app).not.toContain('restoreTask(subtask)')
|
expect(app).not.toContain('restoreTask(subtask)')
|
||||||
expect(app).not.toContain('purgeTask(subtask)')
|
expect(app).not.toContain('purgeTask(subtask)')
|
||||||
expect(app).not.toContain('task-detail-trigger')
|
expect(app).not.toContain('task-detail-trigger')
|
||||||
@@ -325,7 +325,7 @@ describe('approved UI detail direction', () => {
|
|||||||
expect(ordinaryRows).not.toContain('aria-label="删除任务"')
|
expect(ordinaryRows).not.toContain('aria-label="删除任务"')
|
||||||
const trashRows = app.slice(app.indexOf('class="trash-groups"'), ordinaryStart)
|
const trashRows = app.slice(app.indexOf('class="trash-groups"'), ordinaryStart)
|
||||||
expect(trashRows).toContain('class="task-actions"')
|
expect(trashRows).toContain('class="task-actions"')
|
||||||
expect(trashRows).toContain('restoreTask(node.task)')
|
expect(trashRows).not.toContain('restoreTask(node.task)')
|
||||||
expect(trashRows).toContain('openTrashAction(node.task,$event)')
|
expect(trashRows).toContain('openTrashAction(node.task,$event)')
|
||||||
const overdue = app.slice(app.indexOf('<section v-if="overdueTaskTree.length"'), app.indexOf('id="today-tasks-heading"'))
|
const overdue = app.slice(app.indexOf('<section v-if="overdueTaskTree.length"'), app.indexOf('id="today-tasks-heading"'))
|
||||||
expect(overdue).toContain('selectTaskUnlessSwiped(node.task)')
|
expect(overdue).toContain('selectTaskUnlessSwiped(node.task)')
|
||||||
@@ -841,8 +841,14 @@ describe('task and habit row decoration', () => {
|
|||||||
expect(app).not.toContain('<strong>{{node.task.title}}</strong>')
|
expect(app).not.toContain('<strong>{{node.task.title}}</strong>')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('keeps the Trash restore action at least 44px tall', () => {
|
it('keeps Trash row actions inside the ellipsis menu', () => {
|
||||||
expect(css).toMatch(/\.restore\{[^}]*min-height:44px/)
|
const trashRows = app.slice(app.indexOf('class="trash-groups"'), app.indexOf('<section v-else'))
|
||||||
|
const trashMenu = app.slice(app.indexOf('class="trash-action-menu"'), app.indexOf('class="archived-action-mask"'))
|
||||||
|
expect(trashRows).not.toContain('class="restore"')
|
||||||
|
expect(trashRows).not.toContain('restoreTask(node.task)')
|
||||||
|
expect(trashMenu).toContain('@click="requestRestoreTask"')
|
||||||
|
expect(trashMenu).toContain('<ArchiveRestore/>恢复')
|
||||||
|
expect(trashMenu).toContain('@click="requestPurgeTask"')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('keeps Trash deadlines visible on mobile before the action area', () => {
|
it('keeps Trash deadlines visible on mobile before the action area', () => {
|
||||||
@@ -858,7 +864,9 @@ describe('task and habit row decoration', () => {
|
|||||||
expect(app).toContain('@click.stop="openTrashAction(node.task,$event)"')
|
expect(app).toContain('@click.stop="openTrashAction(node.task,$event)"')
|
||||||
expect(app).toContain('ref="trashMenu" class="trash-action-menu" role="menu" aria-label="回收站任务操作"')
|
expect(app).toContain('ref="trashMenu" class="trash-action-menu" role="menu" aria-label="回收站任务操作"')
|
||||||
expect(app).toContain('@keydown.esc.stop="closeTrashAction()"')
|
expect(app).toContain('@keydown.esc.stop="closeTrashAction()"')
|
||||||
expect(app).toContain('@keydown.tab.prevent="focusTrashMenuItem"')
|
expect(app).toContain('@keydown.tab.prevent="moveTrashMenuFocus($event.shiftKey?-1:1)"')
|
||||||
|
expect(app).toContain('@keydown.down.prevent="moveTrashMenuFocus(1)"')
|
||||||
|
expect(app).toContain('@keydown.up.prevent="moveTrashMenuFocus(-1)"')
|
||||||
expect(app).toContain("nextTick(() => trashMenu.value?.querySelector<HTMLElement>('[role=menuitem]')?.focus())")
|
expect(app).toContain("nextTick(() => trashMenu.value?.querySelector<HTMLElement>('[role=menuitem]')?.focus())")
|
||||||
expect(app).toContain('if (trigger?.isConnected) trigger.focus()')
|
expect(app).toContain('if (trigger?.isConnected) trigger.focus()')
|
||||||
expect(app).toContain('await purgeTask(task)')
|
expect(app).toContain('await purgeTask(task)')
|
||||||
|
|||||||
Reference in New Issue
Block a user