feat: 移动端底部导航备忘录入口改为专注
This commit is contained in:
@@ -160,7 +160,7 @@ test('all bottom destinations expose one active page and desktop layout stays un
|
||||
const navigation = page.getByRole('navigation', { name: '主要导航' })
|
||||
const mobile = (await page.viewportSize())!.width <= 930
|
||||
if (mobile) {
|
||||
for (const label of ['今天', '习惯', '倒数日', '备忘录', '日历订阅']) {
|
||||
for (const label of ['今天', '习惯', '倒数日', '专注', '日历订阅']) {
|
||||
await bottomTab(page, label).click()
|
||||
await expect(navigation.locator('[aria-current="page"]')).toHaveCount(1)
|
||||
await expect(bottomTab(page, label)).toHaveAttribute('aria-current', 'page')
|
||||
|
||||
@@ -1910,7 +1910,7 @@ onUnmounted(() => {
|
||||
<footer class="detail-actions"><button class="danger-text detail-trash" type="button" :disabled="taskDetailBusy" @click="removeTask(selectedTask)"><Trash2/>移到回收站</button><button class="primary detail-save" type="submit" :disabled="taskDetailBusy">{{savingSelectedTask?'正在保存…':recurrenceLoading?'正在读取…':'保存更改'}}</button></footer>
|
||||
</AppSheet>
|
||||
|
||||
<nav class="bottom" :inert="memoBackgroundInert ? true : undefined" aria-label="主要导航"><button :class="{active:activeView==='today'}" :aria-current="activeView==='today' ? 'page' : undefined" @click="switchView('today')"><ListTodo/><span>今天</span></button><button :class="{active:activeView==='habits'}" :aria-current="activeView==='habits' ? 'page' : undefined" @click="switchView('habits')"><Repeat2/><span>习惯</span></button><button :class="{active:activeView==='countdowns'}" :aria-current="activeView==='countdowns' ? 'page' : undefined" @click="switchView('countdowns')"><CalendarHeart/><span>倒数日</span></button><button :class="{active:activeView==='memos'}" :aria-current="activeView==='memos' ? 'page' : undefined" @click="switchView('memos')"><StickyNote/><span>备忘录</span></button><button :class="{active:activeView==='calendar'}" :aria-current="activeView==='calendar' ? 'page' : undefined" @click="switchView('calendar')"><CalendarDays/><span>日历订阅</span></button></nav>
|
||||
<nav class="bottom" :inert="memoBackgroundInert ? true : undefined" aria-label="主要导航"><button :class="{active:activeView==='today'}" :aria-current="activeView==='today' ? 'page' : undefined" @click="switchView('today')"><ListTodo/><span>今天</span></button><button :class="{active:activeView==='habits'}" :aria-current="activeView==='habits' ? 'page' : undefined" @click="switchView('habits')"><Repeat2/><span>习惯</span></button><button :class="{active:activeView==='countdowns'}" :aria-current="activeView==='countdowns' ? 'page' : undefined" @click="switchView('countdowns')"><CalendarHeart/><span>倒数日</span></button><button :class="{active:activeView==='focus'}" :aria-current="activeView==='focus' ? 'page' : undefined" @click="switchView('focus')"><TimerReset/><span>专注</span></button><button :class="{active:activeView==='calendar'}" :aria-current="activeView==='calendar' ? 'page' : undefined" @click="switchView('calendar')"><CalendarDays/><span>日历订阅</span></button></nav>
|
||||
<FloatingAddButton v-if="['tasks','today','upcoming','habits','countdowns','memos'].includes(activeView)" :show="showFloatingAdd" :label="activeView==='habits' ? '添加习惯' : activeView==='countdowns' ? '添加倒数日' : activeView==='memos' ? '添加备忘录' : '添加任务'" @activate="activateFloatingAdd" />
|
||||
<AppSheet :open="taskComposeOpen" variant="create" panel-class="task-compose-sheet" title-id="task-compose-title" initial-focus=".task-compose-input" :style="taskComposeStyle" @close="closeTaskCompose" @submit.prevent="submitTaskCompose">
|
||||
<header class="app-sheet__header"><div><h2 id="task-compose-title">{{ taskComposeTitle }}</h2></div><button class="icon" type="button" aria-label="关闭添加任务" @click="closeTaskCompose"><X/></button></header>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { readFileSync } from 'node:fs'
|
||||
import { describe,expect,it } from 'vitest'
|
||||
const app=readFileSync('src/App.vue','utf8');const utils=readFileSync('src/lib/mvp-utils.ts','utf8');const css=readFileSync('src/calendar.css','utf8')
|
||||
describe('calendar shell integration',()=>{
|
||||
it('puts calendar beside the other first-level tools on desktop and mobile',()=>{const nav=app.slice(app.indexOf('<nav class="primary-nav">'),app.indexOf('</nav>',app.indexOf('<nav class="primary-nav">')));expect(nav.indexOf("switchView('habits')")).toBeLessThan(nav.indexOf("switchView('countdowns')"));expect(nav.indexOf("switchView('countdowns')")).toBeLessThan(nav.indexOf("switchView('memos')"));expect(nav.indexOf("switchView('memos')")).toBeLessThan(nav.indexOf("switchView('calendar')"));const bottom=app.slice(app.indexOf('<nav class="bottom"'),app.indexOf('</nav>',app.indexOf('<nav class="bottom"')));for(const view of ['today','habits','countdowns','memos','calendar'])expect(bottom).toContain(`switchView('${view}')`);expect(bottom).not.toContain("switchView('settings')")})
|
||||
it('puts calendar beside the other first-level tools on desktop and mobile',()=>{const nav=app.slice(app.indexOf('<nav class="primary-nav">'),app.indexOf('</nav>',app.indexOf('<nav class="primary-nav">')));expect(nav.indexOf("switchView('habits')")).toBeLessThan(nav.indexOf("switchView('countdowns')"));expect(nav.indexOf("switchView('countdowns')")).toBeLessThan(nav.indexOf("switchView('memos')"));expect(nav.indexOf("switchView('memos')")).toBeLessThan(nav.indexOf("switchView('calendar')"));const bottom=app.slice(app.indexOf('<nav class="bottom"'),app.indexOf('</nav>',app.indexOf('<nav class="bottom"')));for(const view of ['today','habits','countdowns','focus','calendar'])expect(bottom).toContain(`switchView('${view}')`);expect(bottom).not.toContain("switchView('settings')")})
|
||||
it('persists calendar navigation and mounts the panel',()=>{expect(utils).toContain("'calendar'");expect(app).toContain("import CalendarPanel from './CalendarPanel.vue'");expect(app).toContain("activeView==='calendar'")})
|
||||
it('places desktop event detail in the shell right pane while retaining the mobile sheet',()=>{const panel=readFileSync('src/CalendarPanel.vue','utf8');expect(app).toContain("'detail-open': Boolean(selectedTask) || habitDetailOpen || calendarDetailOpen");expect(app).toContain(':compact-layout="compactLayout" @detail="calendarDetailOpen=$event"');expect(app).toContain("selectedTask || habitDetailOpen || calendarDetailOpen");expect(app).toContain('watch([selectedTask, habitDetailOpen, calendarDetailOpen, sidebarCollapsed], reconcileDesktopPaneWidths)');expect(panel).toContain('defineProps<{ compactLayout: boolean }>()');expect(panel).toContain(':modal="compactLayout"');expect(panel).toContain('inline-target=".shell"');expect(panel).toContain("watch(selected,event=>emit('detail',Boolean(event)))");expect(panel).toContain("watch(()=>props.compactLayout,()=>{selected.value=null},{flush:'sync'})");expect(panel).not.toContain("onBeforeUnmount(()=>emit('detail',false))");expect(css).toContain('@media(min-width:931px){.calendar-view{padding-top:var(--space-4)}.calendar-event-detail')})
|
||||
it('keeps five 44px mobile targets across required breakpoints',()=>{const shellCss=readFileSync('src/style.css','utf8');expect(shellCss).toContain('grid-template-columns:repeat(5,minmax(0,1fr))');expect(css).toContain('min-height:44px');expect(css).toContain('@media(max-width:720px)');expect(css).toContain('@media(min-width:931px)');expect(css).toContain('@media(min-width:1440px)')})
|
||||
|
||||
@@ -7,12 +7,12 @@ const panel = readFileSync('src/MemoPanel.vue', 'utf8')
|
||||
const editor = readFileSync('src/components/MemoEditor.vue', 'utf8')
|
||||
|
||||
describe('memo shell integration', () => {
|
||||
it('places Memos immediately after Countdowns in desktop navigation and exposes it as a direct mobile destination', () => {
|
||||
it('places Memos immediately after Countdowns in desktop navigation while mobile navigation now leads with Focus', () => {
|
||||
const nav = app.slice(app.indexOf('<nav class="primary-nav">'), app.indexOf('</nav>', app.indexOf('<nav class="primary-nav">')))
|
||||
expect(nav.indexOf("switchView('memos')")).toBeGreaterThan(nav.indexOf("switchView('countdowns')"))
|
||||
expect(nav.match(/switchView\('memos'\)/g)).toHaveLength(1)
|
||||
const bottom = app.slice(app.indexOf('<nav class="bottom"'), app.indexOf('</nav>', app.indexOf('<nav class="bottom"')))
|
||||
expect(bottom).toContain("switchView('memos')")
|
||||
expect(bottom).not.toContain("switchView('memos')")
|
||||
expect(bottom.match(/aria-current=/g)).toHaveLength(5)
|
||||
})
|
||||
|
||||
|
||||
@@ -6,13 +6,14 @@ describe('pomodoro integration', () => {
|
||||
const css = readFileSync('src/style.css', 'utf8')
|
||||
const panel = readFileSync('src/PomodoroPanel.vue', 'utf8')
|
||||
|
||||
it('adds focus to desktop navigation and keeps the five-item mobile navigation unchanged', () => {
|
||||
it('adds focus to desktop navigation and replaces memos with focus in the five-item mobile navigation', () => {
|
||||
const primary = app.slice(app.indexOf('<nav class="primary-nav">'), app.indexOf('</nav>', app.indexOf('<nav class="primary-nav">')))
|
||||
const bottom = app.slice(app.indexOf('<nav class="bottom"'), app.indexOf('</nav>', app.indexOf('<nav class="bottom"')))
|
||||
|
||||
expect(primary).toContain("switchView('focus')")
|
||||
expect(primary.indexOf("switchView('focus')")).toBeGreaterThan(primary.indexOf("switchView('habits')"))
|
||||
expect(bottom).not.toContain("switchView('focus')")
|
||||
expect(bottom).toContain("switchView('focus')")
|
||||
expect(bottom).not.toContain("switchView('memos')")
|
||||
expect(bottom.match(/aria-current=/g)).toHaveLength(5)
|
||||
})
|
||||
|
||||
|
||||
@@ -121,13 +121,14 @@ describe('mobile navigation styles', () => {
|
||||
it('uses five direct mobile destinations and keeps Settings in the sidebar', () => {
|
||||
expect(app).not.toContain('aria-controls="mobile-more-menu"')
|
||||
expect(app).not.toContain('<Ellipsis/><span>更多</span>')
|
||||
expect(app).toContain("<StickyNote/><span>备忘录</span>")
|
||||
expect(app).toContain("<TimerReset/><span>专注</span>")
|
||||
expect(app).toContain("<StickyNote />备忘录</button>")
|
||||
expect(app).toContain("<CalendarDays/><span>日历订阅</span>")
|
||||
expect(app).not.toContain("@click=\"switchView('settings')\"><Settings/><span>设置</span>")
|
||||
})
|
||||
|
||||
it('marks only exact mobile destinations active and exposes aria-current only there', () => {
|
||||
for (const view of ['today', 'habits', 'countdowns', 'memos', 'calendar']) {
|
||||
for (const view of ['today', 'habits', 'countdowns', 'focus', 'calendar']) {
|
||||
expect(app).toContain(`:class="{active:activeView==='${view}'}" :aria-current="activeView==='${view}' ? 'page' : undefined"`)
|
||||
}
|
||||
expect(app).not.toContain("activeView==='tasks'||activeView==='upcoming'||activeView==='trash'||activeView==='settings'")
|
||||
|
||||
Reference in New Issue
Block a user