feat: refine archived habit management
ci / gitleaks (push) Successful in 7s
ci / docker (push) Successful in 3m28s

This commit is contained in:
2026-09-10 14:39:06 +08:00
parent bf61e69776
commit 3e80f2ecbb
8 changed files with 515 additions and 34 deletions
+47 -7
View File
@@ -267,16 +267,56 @@ describe('mobile list row language', () => {
expect(mvpPanel).toContain("habitDetailSheet.value?.focus()")
})
it('provides a minimal archived-habit viewing path', () => {
it('provides an archived-habit viewing path', () => {
expect(mvpPanel).toContain("request('/habits?archived=true')")
expect(mvpPanel).toContain("{{ archivedHabitsLoaded ? `已归档(${archivedHabits.length}` : '已归档' }}")
expect(mvpPanel).toContain("archiveState === 'success' ? `已归档(${archivedHabits.length}` : '已归档'")
expect(mvpPanel).toContain('class="archived-toggle habit-archive-toggle"')
expect(mvpPanel).toContain('v-for="h in archivedHabits"')
expect(css).toMatch(/@media\(max-width:930px\)\{\.habit-archive-toggle\{[^}]*width:100%;[^}]*min-height:44px;[^}]*appearance:none;[^}]*background:var\(--surface-raised\);[^}]*border:1px solid var\(--border-cream\)/)
expect(mvpPanel).toContain('archiveFlags.list ? archivedHabits : []')
expect(css).toMatch(/@media\(max-width:930px\)\{\.habit-archive-section\{[^}]*width:100%/)
expect(css).toMatch(/\.archived-habits\{[^}]*width:100%;[^}]*display:grid/)
expect(css).toMatch(/\.archived-habits>button\{[^}]*width:100%;[^}]*min-height:44px;[^}]*appearance:none;[^}]*grid-template-columns:minmax\(0,1fr\) auto;[^}]*background:var\(--surface-raised\);[^}]*border-bottom:1px solid var\(--border-cream\)/)
expect(css).toMatch(/\.archived-habits>button span\{[^}]*min-width:0;[^}]*overflow:hidden;[^}]*text-overflow:ellipsis;[^}]*white-space:nowrap/)
expect(css).toMatch(/\.archived-habits>button small\{[^}]*white-space:nowrap/)
expect(css).toMatch(/\.archived-habit-row\{[^}]*width:100%;[^}]*min-height:44px;[^}]*grid-template-columns:minmax\(0,1fr\) auto/)
expect(css).toMatch(/\.archived-habit-row b,\.archived-habit-row small\{[^}]*min-width:0;[^}]*overflow:hidden;[^}]*text-overflow:ellipsis;[^}]*white-space:nowrap/)
})
it('renders a separate accessible archive section with explicit states and lifecycle actions', () => {
const activeList = mvpPanel.slice(mvpPanel.indexOf('<div v-if="view === \'habits\'" class="habit-list">'), mvpPanel.indexOf('<section v-if="view === \'habits\'" class="habit-archive-section"'))
const todayList = mvpPanel.slice(mvpPanel.indexOf('class="habit-list today-habit-list"'), mvpPanel.indexOf('<!-- 完整习惯列表 -->'))
expect(activeList).not.toContain('habit-archive-toggle')
expect(todayList).not.toContain('habit-archive-section')
expect(mvpPanel).toContain('class="habit-archive-section"')
expect(mvpPanel).toContain('aria-controls="archived-habits-panel"')
expect(mvpPanel).toContain(':aria-expanded="showArchivedHabits"')
expect(mvpPanel).toContain('id="archived-habits-panel"')
expect(mvpPanel).toContain('archiveState === \'loading\'')
expect(mvpPanel).toContain('archiveState === \'error\'')
expect(mvpPanel).toContain('@click="loadArchivedHabits"')
expect(mvpPanel).toContain('formatArchivedAt(h.archived_at)')
expect(mvpPanel).toContain('<ChevronRight aria-hidden="true"/>')
expect(mvpPanel).toContain('@click="restoreHabit(selectedHabit)"')
expect(mvpPanel).toContain("method: 'POST'")
expect(mvpPanel).toContain('archivedHabits.value = archivedHabits.value.filter((item) => item.id !== h.id)')
expect(mvpPanel).toContain('恢复习惯')
})
it('preserves archived detail after restore or purge failures and falls focus back to disclosure after removal', () => {
const restoreBlock = mvpPanel.slice(mvpPanel.indexOf('async function restoreHabit'), mvpPanel.indexOf('async function deleteHabit'))
const deleteBlock = mvpPanel.slice(mvpPanel.indexOf('async function deleteHabit'), mvpPanel.indexOf('async function loadArchivedHabits'))
expect(restoreBlock).toContain('catch (reason)')
expect(deleteBlock).toContain('catch (reason)')
expect(restoreBlock).toContain('performHabitRestore({')
expect(restoreBlock).toContain('invalidateHabitGridCache()')
expect(restoreBlock).toContain("result.refreshed ? '习惯已恢复' : '习惯已恢复,但列表刷新失败,请重试'")
expect(restoreBlock.indexOf('selectedHabit.value = null')).toBeGreaterThan(restoreBlock.indexOf("method: 'POST'"))
expect(deleteBlock.indexOf('selectedHabit.value = null')).toBeGreaterThan(deleteBlock.indexOf("method: 'DELETE'"))
expect(mvpPanel).toContain('habitArchiveToggle.value?.focus()')
expect(mvpPanel).toContain('ref="habitArchiveToggle"')
})
it('styles archive rows as 44px full-width desktop rows and a mobile bottom sheet detail', () => {
expect(css).toMatch(/\.habit-archive-toggle\{[^}]*min-height:44px/)
expect(css).toMatch(/\.archived-habit-row\{[^}]*width:100%;[^}]*min-height:44px;[^}]*grid-template-columns:minmax\(0,1fr\) auto/)
expect(css).toContain('@media(max-width:930px){.habit-archive-section')
expect(css).toContain('.habit-detail-sheet.app-sheet--detail{')
})
it('reuses the habit composer for edits and patches only changed fields', () => {