feat: unify task and habit pages with Today
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { loadTaskOpenTotal } from './lib/task-open-total'
|
||||
|
||||
const app = readFileSync('src/App.vue', 'utf8')
|
||||
const countdown = readFileSync('src/CountdownPanel.vue', 'utf8')
|
||||
@@ -9,6 +10,44 @@ const memoCss = readFileSync('src/memo.css', 'utf8')
|
||||
const memoPanel = readFileSync('src/MemoPanel.vue', 'utf8')
|
||||
const memoRow = readFileSync('src/components/MemoRow.vue', 'utf8')
|
||||
|
||||
describe('task-list open summary reconciliation', () => {
|
||||
it('returns primary data without waiting for the open-total request', async () => {
|
||||
let resolveSummary!: (value: { total: number }) => void
|
||||
const summary = new Promise<{ total: number }>((resolve) => { resolveSummary = resolve })
|
||||
const committed: Array<number | null> = []
|
||||
const result = await loadTaskOpenTotal(
|
||||
async () => summary,
|
||||
() => true,
|
||||
value => committed.push(value),
|
||||
)
|
||||
expect(result).toBeUndefined()
|
||||
expect(committed).toEqual([])
|
||||
resolveSummary({ total: 4 })
|
||||
await summary
|
||||
await Promise.resolve()
|
||||
expect(committed).toEqual([4])
|
||||
})
|
||||
|
||||
it('keeps null on failure and ignores stale list responses', async () => {
|
||||
const failed: Array<number | null> = []
|
||||
loadTaskOpenTotal(async () => { throw new Error('summary failed') }, () => true, value => failed.push(value))
|
||||
await Promise.resolve()
|
||||
await Promise.resolve()
|
||||
expect(failed).toEqual([])
|
||||
|
||||
let current = true
|
||||
let resolveSummary!: (value: { total: number }) => void
|
||||
const summary = new Promise<{ total: number }>((resolve) => { resolveSummary = resolve })
|
||||
const committed: Array<number | null> = []
|
||||
loadTaskOpenTotal(async () => summary, () => current, value => committed.push(value))
|
||||
current = false
|
||||
resolveSummary({ total: 9 })
|
||||
await summary
|
||||
await Promise.resolve()
|
||||
expect(committed).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('approved five-detail polish', () => {
|
||||
it('uses an independent accessible task-search panel toggle', () => {
|
||||
expect(app).toContain(":aria-label=\"mobileSearchOpen ? '收起搜索任务' : '展开搜索任务'\"")
|
||||
@@ -34,6 +73,53 @@ describe('approved five-detail polish', () => {
|
||||
expect(css).toMatch(/@media\(max-width:930px\)\{[\s\S]*?\.countdown-focus\{[^}]*height:140px/)
|
||||
})
|
||||
|
||||
it('uses approved Today-sourced page headers for task lists without changing Trash IA', () => {
|
||||
expect(app).toContain('v-if="activeView===\'tasks\'" class="list-page-context"')
|
||||
expect(app).toContain('<h1 class="list-page-title" :title="activeName">{{ activeName }}</h1>')
|
||||
expect(app).toContain('<p class="list-page-summary">{{ taskOpenTotal === null ? \'待完成统计暂不可用\' : `还有 ${taskOpenTotal} 项待完成` }}</p>')
|
||||
expect(app).toContain('<CompletedFilterPill v-model="showCompleted" class="list-inline-filter" />')
|
||||
expect(app).toContain('id="task-list-heading" class="list-section-heading"')
|
||||
expect(app).toContain('<span id="task-list-title" class="list-section-title">任务</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="query" class="list-search-clear"')
|
||||
expect(app).not.toContain("activeView==='tasks' && (totalPages > 1 || totalTasks > 0)")
|
||||
expect(app).toContain("activeView==='tasks' ? 'task-list-title' : undefined")
|
||||
const taskTopbar = app.slice(app.indexOf('<header class="topbar"'), app.indexOf('</header>'))
|
||||
expect(taskTopbar).not.toContain('CompletedFilterPill v-if="activeView!==\'today\'"')
|
||||
expect(app).toContain('v-if="activeView!==\'today\' && activeView!==\'tasks\'" class="list-toolbar"')
|
||||
expect(css).toContain('.list-page-context{width:min(100%,630px);margin:0 auto 0;display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:start;gap:0 16px}')
|
||||
expect(css).toContain('.list-page-title{margin:0;font-size:34px;line-height:1.15;font-weight:700;letter-spacing:-.035em}')
|
||||
expect(css).toContain('.list-page-summary{margin:8px 0 18px;color:var(--muted);font-size:13px}')
|
||||
expect(css).toContain('@media(min-width:1440px){main.list-main>.list-page-context')
|
||||
expect(css).toContain('@media(max-width:720px){main.list-main{padding-left:29px;padding-right:29px}main.list-main>.list-page-context,main.list-main>.list-search-reveal,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%}')
|
||||
expect(css).toContain('@media(min-width:721px) and (max-width:1439px){main.list-main>.list-page-context,main.list-main>.list-search-reveal,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)}')
|
||||
expect(css).toContain('.list-search-reveal{position:relative;min-height:44px;margin-bottom:0;display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center}')
|
||||
expect(css).toContain('@media(max-width:720px){')
|
||||
expect(css).not.toContain('top:-62px')
|
||||
expect(css).toContain('.list-inline-filter .completed-filter-pill__track,.habit-inline-filter .completed-filter-pill__track{width:31px;height:18px;background:#d8d0c5}')
|
||||
expect(css).toContain('.list-inline-filter .completed-filter-pill__thumb,.habit-inline-filter .completed-filter-pill__thumb{width:14px;height:14px}')
|
||||
expect(css).toContain('.list-section-heading{width:min(100%,630px);min-height:44px;margin:0 auto;display:flex;align-items:center;border-bottom:1px solid #e8e0d5}')
|
||||
})
|
||||
|
||||
it('uses approved Today-sourced page headers for full Habits and keeps archive lazy section', () => {
|
||||
expect(habits).toContain('v-if="view === \'habits\'" class="habit-page-context"')
|
||||
expect(habits).toContain('<h1 class="list-page-title">习惯</h1>')
|
||||
expect(habits).toContain('<p class="list-page-summary">今天还有 {{ todayHabitSummary.total - todayHabitSummary.completed }} 项</p>')
|
||||
expect(habits).toContain('class="completed-filter-pill habit-inline-filter"')
|
||||
expect(habits).toContain('@update:model-value="$emit(\'update:showCompleted\', $event)"')
|
||||
expect(habits).toContain('class="list-section-heading habit-section-heading"')
|
||||
expect(habits).toContain('<span class="list-section-title">今日习惯</span>')
|
||||
expect(habits).toContain('<span class="list-section-count">{{ todayHabitSummary.total }}</span>')
|
||||
expect(habits).not.toContain('<span class="list-section-count">{{ visibleHabits.length }}</span>')
|
||||
expect(habits).not.toContain('今日完成 {{')
|
||||
expect(habits).toContain('class="habit-archive-section"')
|
||||
expect(css).toContain('.habit-page-context{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:start;gap:0 16px}')
|
||||
expect(css).toContain('.habit-section-heading{margin-top:0}')
|
||||
expect(css).toContain('main.list-main>.mvp-view{gap:0}')
|
||||
expect(css).toContain('main.list-main>.mvp-view>.habit-archive-section{border-top:1px solid #e8e0d5}')
|
||||
})
|
||||
|
||||
it('uses one 58px plain-list contract for active task and habit rows', () => {
|
||||
expect(app).toContain("class=\"task-list plain-list\"")
|
||||
expect(app).toContain("'task-row--trash':activeView==='trash'")
|
||||
|
||||
Reference in New Issue
Block a user