feat: redesign habit detail paper flow
ci / gitleaks (push) Successful in 9s
ci / docker (push) Successful in 3m46s

This commit is contained in:
2026-09-18 21:10:50 +08:00
parent 432a133a42
commit 0baedbc99e
6 changed files with 322 additions and 10 deletions
+48 -1
View File
@@ -617,7 +617,7 @@ describe('mobile sheet contract', () => {
})
it('keeps the danger zone reserved for archived habit deletion', () => {
expect(mvpPanel).toContain('v-if="selectedHabit.archived_at" class="app-sheet__danger"')
expect(mvpPanel).toContain('v-if="selectedHabit.archived_at" class="app-sheet__danger habit-detail-archived-actions"')
expect(mvpPanel).toContain('deleteHabit(selectedHabit)')
expect(css).toContain('.app-sheet__danger{border-top:1px solid #f1d4cd;')
})
@@ -643,6 +643,8 @@ describe('mobile list row language', () => {
expect(mvpPanel).toContain('@click="archiveHabit(selectedHabit)"')
expect(mvpPanel).toContain('v-if="selectedHabit.archived_at"')
expect(mvpPanel).toContain('@click="deleteHabit(selectedHabit)"')
expect(mvpPanel).toContain("if (!(await confirmAction(`永久删除习惯“${h.name}”?`, '所有历史打卡记录也会被删除,且无法恢复。'))) return")
expect(mvpPanel).toContain('if (busy.value) return\n busy.value = true')
expect(mvpPanel).toContain("request(`/habits/${h.id}/permanent`, { method: 'DELETE' })")
expect(mvpPanel).toContain("request(`/habits/${habit.id}/logs?from=${window.from}&to=${window.to}`)")
expect(mvpPanel).toContain('aria-labelledby="habit-history-title"')
@@ -1230,6 +1232,51 @@ describe('settings data tools', () => {
})
})
describe('habit detail paper-flow redesign', () => {
it('keeps the approved information order and every existing detail state/action', () => {
const sheet = mvpPanel.slice(mvpPanel.indexOf('<AppSheet :open="Boolean(selectedHabit)"'), mvpPanel.indexOf('</AppSheet>', mvpPanel.indexOf('<AppSheet :open="Boolean(selectedHabit)"')))
const detail = sheet.slice(sheet.indexOf('<template v-if="selectedHabit">'))
const ordered = [
'habit-detail-archive-note',
'habit-detail-title',
'habit-detail-hero',
'habit-detail-progress-row',
'记录方式',
'计划',
'开始日期',
'habit-history-title',
].map((token) => detail.indexOf(token))
expect(ordered.every((position) => position >= 0)).toBe(true)
expect(ordered).toEqual([...ordered].sort((a, b) => a - b))
expect(detail).toContain('selectedHabitDetailProgress.primary')
expect(detail).toContain('selectedHabitDetailProgress.note')
expect(detail).toContain('formatHabitRecordMode(selectedHabit)')
expect(detail).toContain('formatHabitSchedule(selectedHabit)')
expect(detail).toContain('formatHabitDetailDate(selectedHabit.start_date)')
expect(detail).not.toContain('最后记录于')
expect(detail).toContain('habitHistoryLoading')
expect(detail).toContain('habitHistoryError')
expect(detail).toContain('loadHabitHistory(true)')
expect(detail).toContain('loadHabitHistory(false)')
expect(detail).toContain('@click="editHabit(selectedHabit)"')
expect(detail).toContain('@click="archiveHabit(selectedHabit)"')
expect(detail).toContain('@click="restoreHabit(selectedHabit)"')
expect(detail).toContain('@click="deleteHabit(selectedHabit)"')
})
it('matches the paper-flow surface and action hierarchy at desktop and touch widths', () => {
expect(css).toContain('/* Approved habit detail 01: paper flow. */')
expect(css).toMatch(/\.habit-detail-hero\{[^}]*padding:[^;}]*;[^}]*border-bottom:1px solid #e4dbcf/)
expect(css).toMatch(/\.habit-detail-hero strong\{[^}]*font-size:28px/)
expect(css).toMatch(/\.habit-detail-progress-row\{[^}]*min-height:66px;[^}]*grid-template-columns:minmax\(0,1fr\) auto/)
expect(css).toMatch(/\.habit-detail-meta-row\{[^}]*min-height:52px;[^}]*grid-template-columns:86px minmax\(0,1fr\)/)
expect(css).toMatch(/\.habit-detail-active-actions\{[^}]*justify-content:space-between/)
expect(css).toMatch(/\.habit-detail-archive-button\{[^}]*background:transparent;[^}]*color:var\(--danger\)/)
expect(css).toMatch(/@media\(max-width:930px\)\{[^}]*\.habit-detail-sheet\.app-sheet--detail\{[^}]*width:100%/)
expect(css).toMatch(/@media\(max-width:930px\)\{[\s\S]*?\.habit-detail-sheet :is\([^}]*\)\{[^}]*min-height:44px/)
})
})
describe('task detail layout', () => {
it('keeps long subtask text inside the detail panel', () => {
expect(css).toContain('.detail-form{min-width:0;grid-template-columns:minmax(0,1fr);')