feat: unify completed filters in topbar
ci / gitleaks (push) Successful in 7s
ci / docker (push) Successful in 3m30s

This commit is contained in:
2026-09-10 12:16:45 +08:00
parent 1bc481d2e4
commit bf61e69776
6 changed files with 133 additions and 33 deletions
+32 -20
View File
@@ -7,6 +7,7 @@ const app = readFileSync('src/App.vue', 'utf8')
const mvpPanel = readFileSync('src/MvpPanel.vue', 'utf8')
const countdownPanel = readFileSync('src/CountdownPanel.vue', 'utf8')
const floatingAdd = readFileSync('src/components/FloatingAddButton.vue', 'utf8')
const completedFilterPill = readFileSync('src/components/CompletedFilterPill.vue', 'utf8')
describe('mobile navigation styles', () => {
it('renames the bottom More tab to a direct Settings tab', () => {
@@ -419,35 +420,46 @@ describe('task and habit row decoration', () => {
expect(app).toContain('if (restoredNavigation.view === \'tasks\' && restoredNavigation.listId)')
})
it('uses one positive completed-item preference across task and habit views', () => {
it('uses one topbar pill and keeps App as the only preference owner', () => {
expect(app).toContain("import CompletedFilterPill from './components/CompletedFilterPill.vue'")
expect(app.match(/<CompletedFilterPill/g)).toHaveLength(1)
expect(app).toContain("v-if=\"['today', 'tasks', 'upcoming', 'habits'].includes(activeView)\"")
expect(app).toContain('v-model="showCompleted"')
expect(app).toContain("readStoredBoolean(window.localStorage, SHOW_COMPLETED_STORAGE_KEY, true)")
expect(app).toContain("writeStoredBoolean(window.localStorage, SHOW_COMPLETED_STORAGE_KEY, value)")
expect(app).toContain(':show-completed="showCompleted"')
expect(app).toContain('@update:show-completed="showCompleted = $event"')
expect(app).not.toContain('@update:show-completed')
expect(mvpPanel).toContain('showCompleted: boolean')
expect(mvpPanel).toContain("'update:showCompleted': [value: boolean]")
expect(mvpPanel).not.toContain('HIDE_COMPLETED_HABITS_STORAGE_KEY')
expect(mvpPanel).not.toContain('dodo.hide-completed-habits')
expect(mvpPanel).not.toContain("'update:showCompleted': [value: boolean]")
expect(completedFilterPill).not.toContain('localStorage')
})
it('shows one positive switch before Today content and no switch inside today habits', () => {
expect(app).toContain('class="list-toolbar today-completed-toolbar"')
expect(app.indexOf('class="list-toolbar today-completed-toolbar"')).toBeLessThan(app.indexOf('class="today-board"'))
expect(app).toContain('<input v-model="showCompleted" type="checkbox"> 显示已完成')
it('removes duplicate completed filters while preserving habit completion buttons', () => {
expect(app).not.toContain('today-completed-toolbar')
expect(app).not.toContain('<input v-model="showCompleted" type="checkbox"> 显示已完成')
expect(mvpPanel).not.toContain('class="habit-toolbar"')
expect(mvpPanel).not.toMatch(/type="checkbox"[^>]*显示已完成/)
expect(mvpPanel.match(/class="task-check habit-check"/g)?.length).toBe(2)
const todayHabits = mvpPanel.slice(mvpPanel.indexOf('<!-- 今日习惯'), mvpPanel.indexOf('<!-- 完整习惯列表'))
expect(todayHabits).not.toContain('type="checkbox"')
expect(todayHabits).not.toContain('completed-filter-pill')
expect(todayHabits).not.toContain('显示已完成')
expect(todayHabits).not.toContain('隐藏已完成')
})
it('adds top spacing only to the Today completed toolbar before mobile overrides', () => {
const todayToolbarRule = '.today-completed-toolbar{margin-top:8px}'
const todayToolbarIndex = css.indexOf(todayToolbarRule)
const mobileBreakpointIndex = css.indexOf('@media(max-width:930px)')
expect(css.match(/\.today-completed-toolbar\{/g)).toHaveLength(1)
expect(todayToolbarIndex).toBeGreaterThan(css.indexOf('.list-toolbar{'))
expect(todayToolbarIndex).toBeLessThan(mobileBreakpointIndex)
expect(css.slice(mobileBreakpointIndex)).not.toContain('.today-completed-toolbar{')
it('keeps the pill at the topbar right and wraps only search when space is narrow', () => {
expect(css).toContain('main{container-type:inline-size;')
expect(css).toContain('.topbar{display:grid;grid-template-columns:44px minmax(0,1fr) auto;grid-template-areas:"menu title filter";')
expect(css).toContain('.topbar:has(.search):has(.topbar-filter){grid-template-columns:44px minmax(0,1fr) minmax(180px,260px) auto;grid-template-areas:"menu title search filter"}')
expect(css).toContain('.topbar-title{grid-area:title;min-width:0;width:100%}')
expect(css).toContain('.topbar h1{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;')
expect(css).toContain('.topbar-filter{grid-area:filter;justify-self:end;')
expect(app.indexOf('class="topbar-filter"')).toBeLessThan(app.indexOf('class="search"'))
expect(css).toContain('@container(max-width:640px){.topbar:has(.search):has(.topbar-filter){grid-template-columns:44px minmax(0,1fr) auto;grid-template-areas:"menu title filter" ". search search";grid-template-rows:auto auto;')
expect(css).toContain('.topbar>.icon{grid-area:menu}.topbar-title{grid-area:title}.topbar-filter{grid-area:filter}.topbar .search{grid-area:search;grid-row:2;justify-self:end;width:min(260px,100%);')
expect(css).toContain('@media(max-width:930px){.topbar:has(.search):has(.topbar-filter){grid-template-columns:44px minmax(0,1fr) auto;grid-template-areas:"menu title filter" ". search search";grid-template-rows:auto auto;')
const mobileLayout = css.slice(css.indexOf('@media(max-width:930px){.shell'))
expect(mobileLayout).toContain('.completed-filter-pill{min-width:138px;height:44px')
expect(css).not.toContain('.today-completed-toolbar{margin-top:8px}')
expect(css).not.toContain('.habit-toolbar{')
})
it('filters habits with the shared preference and disables sorting while completed items are hidden', () => {
@@ -457,7 +469,7 @@ describe('task and habit row decoration', () => {
expect(mvpPanel).toContain('v-for="h in visibleHabits"')
expect(mvpPanel).toContain(':disabled="!showCompleted"')
expect(mvpPanel).toContain('if (busy.value || !props.showCompleted) return')
expect(css).toContain('.habit-toolbar{')
expect(css).not.toContain('.habit-toolbar{')
})
it('distinguishes hidden completed items from true and search empty states', () => {