feat: collapse archived lists in sidebar
This commit is contained in:
@@ -80,6 +80,71 @@ describe('completion feedback motion', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('archived task-list disclosure', () => {
|
||||
it('keeps one collapsed disclosure row even when the archive is empty', () => {
|
||||
expect(app).toContain('const archivedListsExpanded = ref(false)')
|
||||
expect(app).toContain('class="archived-lists-toggle"')
|
||||
expect(app).toContain('已归档 {{ archivedLists.length }}')
|
||||
expect(app).toContain(':aria-expanded="archivedLists.length > 0 && archivedListsExpanded"')
|
||||
expect(app).toContain('aria-controls="archived-task-lists"')
|
||||
expect(app).toContain(':disabled="archivedLists.length === 0"')
|
||||
expect(app).toContain('id="archived-task-lists"')
|
||||
})
|
||||
|
||||
it('renders compact archived rows with a restore and purge menu', () => {
|
||||
expect(app).toContain('v-show="archivedListsExpanded"')
|
||||
expect(app).toContain('class="list-row archived-row"')
|
||||
expect(app).toContain('class="archived-row-menu-trigger"')
|
||||
expect(app).toContain('恢复清单')
|
||||
expect(app).toContain('永久删除清单')
|
||||
expect(css).toContain('.archived-lists-toggle{min-height:44px;')
|
||||
expect(css).toContain('.archived-row{min-height:44px;display:grid;grid-template-columns:minmax(0,1fr) 44px;align-items:center;')
|
||||
expect(css).toContain('.archived-row-actions{position:fixed;z-index:70;')
|
||||
expect(css).toContain('@media(max-width:930px){.archived-action-mask{display:block;position:fixed;')
|
||||
expect(css).toContain('.archived-row-actions{position:fixed;z-index:61;left:0;right:0;top:auto;bottom:0;')
|
||||
})
|
||||
|
||||
it('preserves the expanded state while restoring or deleting the last list', () => {
|
||||
const restoreBlock = app.slice(app.indexOf('async function restoreList'), app.indexOf('function openPurgeList'))
|
||||
const purgeBlock = app.slice(app.indexOf('async function confirmPurgeList'), app.indexOf('function toggleSidebarCreate'))
|
||||
expect(restoreBlock).not.toContain('archivedListsExpanded.value = false')
|
||||
expect(purgeBlock).not.toContain('archivedListsExpanded.value = false')
|
||||
})
|
||||
|
||||
it('keeps archived and ordinary sidebar action menus mutually exclusive', () => {
|
||||
const archivedActionBlock = app.slice(app.indexOf('function toggleArchivedListAction'), app.indexOf('function openPurgeList'))
|
||||
const sidebarActionBlock = app.slice(app.indexOf('function openSidebarAction'), app.indexOf('function runSidebarCreate'))
|
||||
expect(archivedActionBlock).toContain('closeSidebarAction()')
|
||||
expect(sidebarActionBlock).toContain('closeArchivedListAction(false)')
|
||||
})
|
||||
|
||||
it('closes the desktop archived menu from outside pointer input without replacing the mobile mask', () => {
|
||||
expect(app).toContain("document.addEventListener('pointerdown', handleArchivedListOutsidePointer)")
|
||||
expect(app).toContain("window.matchMedia('(min-width: 931px)').matches")
|
||||
expect(app).toContain("target.closest('.archived-row-menu,.archived-row-actions')) closeArchivedListAction(false)")
|
||||
expect(app).toContain('<Teleport to="body">')
|
||||
expect(app).toContain('class="archived-action-mask" @click.self="closeArchivedListAction()"')
|
||||
expect(app).toContain("document.addEventListener('scroll', handleArchivedListViewportChange, true)")
|
||||
expect(app).toContain("window.addEventListener('resize', handleArchivedListViewportChange)")
|
||||
expect(css).toContain('@media(max-width:930px){.archived-action-mask{display:block;position:fixed;')
|
||||
})
|
||||
|
||||
it('restores archive menu focus after Escape and actions, with a fallback after row removal', () => {
|
||||
expect(app).toContain('const archivedListsToggle = ref<HTMLButtonElement | null>(null)')
|
||||
expect(app).toContain('let archivedListActionTrigger: HTMLElement | null = null')
|
||||
expect(app).toContain("document.addEventListener('keydown', handleArchivedListEscape)")
|
||||
expect(app).toContain('resolveArchivedMenuFocusTarget(archivedListActionTrigger, archivedListsToggle.value)')
|
||||
expect(app).toContain('ref="archivedListsToggle"')
|
||||
expect(app).toContain("@click=\"toggleArchivedListAction(list,$event.currentTarget)\"")
|
||||
expect(app).toContain('purgeListTrigger = archivedListActionTrigger')
|
||||
expect(app).toContain('focusArchivedListTrigger()')
|
||||
})
|
||||
|
||||
it('disables archive disclosure motion for reduced-motion users', () => {
|
||||
expect(css).toContain('@media(prefers-reduced-motion:reduce){.archived-lists-toggle svg,.archived-list-items{transition:none}}')
|
||||
})
|
||||
})
|
||||
|
||||
describe('mobile sheet contract', () => {
|
||||
it('uses shared roles for details, creation and secondary actions', () => {
|
||||
expect(app).toContain('class="task-compose-mask app-sheet-mask"')
|
||||
@@ -629,7 +694,7 @@ describe('sidebar information hierarchy', () => {
|
||||
expect(app).toContain('aria-label="打开清单操作"')
|
||||
expect(app).toContain('class="sidebar-action-mask app-sheet-mask"')
|
||||
expect(app).toContain('class="sidebar-action-sheet app-sheet app-sheet--actions"')
|
||||
expect(app).toContain('@keydown.esc="sidebarCreateOpen=false;closeSidebarAction()"')
|
||||
expect(app).toContain('@keydown.esc="sidebarCreateOpen=false;closeArchivedListAction();closeSidebarAction()"')
|
||||
expect(app).toContain('sidebarCreateOpen.value = false; sidebarAction.value = null')
|
||||
expect(app).not.toContain('aria-label="重命名文件夹" @click="renameEntity')
|
||||
expect(app).not.toContain('aria-label="重命名清单" @click="renameEntity')
|
||||
@@ -724,22 +789,22 @@ describe('sidebar layout', () => {
|
||||
expect(css).not.toContain('.list-row.list-dragging{touch-action:none}')
|
||||
})
|
||||
|
||||
it('only restores archived lists from the explicit restore action', () => {
|
||||
expect(app).toContain('class="list-row-main archived-row-label"')
|
||||
it('only restores archived lists from the explicit restore menu action', () => {
|
||||
expect(app).toContain('class="archived-row-label"')
|
||||
expect(app).not.toContain(':aria-label="`恢复清单:${list.name}`" @click="restoreList(list)"')
|
||||
expect(app).toContain('aria-label="恢复清单" @click="restoreList(list)"')
|
||||
expect(app).toContain('@click="restoreList(archivedListAction)"><ArchiveRestore/>恢复清单')
|
||||
})
|
||||
|
||||
it('loads archived lists during bootstrap so archived rows are visible after refresh', () => {
|
||||
expect(app).toContain('expandedFolders.value = new Set(folders.value.map((folder) => folder.id))\n await loadArchivedLists()\n void preloadCountdowns()\n await loadRestoredView()')
|
||||
})
|
||||
|
||||
it('styles archived rows through the new list-row-main structure', () => {
|
||||
expect(css).toContain('.archived-row .list-row-main>svg{color:#c9a45c}')
|
||||
it('styles archived rows through the compact disclosure structure', () => {
|
||||
expect(css).toContain('.archived-row-label{min-width:0;overflow:hidden;text-overflow:ellipsis;')
|
||||
})
|
||||
|
||||
it('offers permanent deletion only beside archived lists', () => {
|
||||
expect(app).toContain('aria-label="永久删除清单" @click="openPurgeList(list,$event.currentTarget)"')
|
||||
it('offers permanent deletion only inside archived list menus', () => {
|
||||
expect(app).toContain('@click="openPurgeList(archivedListAction)"><Trash2/>永久删除清单')
|
||||
expect(app).toContain("api(`/lists/${purgeListTarget.value.id}/purge`, { method: 'DELETE' })")
|
||||
expect(app).not.toMatch(/list\.is_inbox[^\n]*openPurgeList/)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user