[verified] fix calendar subscription layout
This commit is contained in:
@@ -201,21 +201,39 @@ test('all bottom destinations expose one active page and desktop layout stays un
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('calendar week focus keeps seven usable day controls without page overflow', async ({ page }) => {
|
test('calendar week focus keeps seven usable day controls without page overflow', async ({ page }) => {
|
||||||
await page.route('**/api/v1/calendar-subscriptions', route => route.fulfill({ json: [{ id:'calendar-source', name:'工作', url:'https://example.com/work.ics', color:'#f15a29', enabled:true, refreshed_at:null, last_error:null, stale:false }] }))
|
const calendarSources = ['天气','法定节假日','老黄历','节日节气','星座','影视上新','股市指数','街舞赛事','F1'].map((name, index) => ({ id:`calendar-source-${index}`, name, url:`https://example.com/${index}.ics`, color:'#f15a29', enabled:true, refreshed_at:null, last_error:null, stale:false }))
|
||||||
|
await page.route('**/api/v1/calendar-subscriptions', route => route.fulfill({ json: calendarSources }))
|
||||||
await page.route('**/api/v1/calendar-events?*', route => route.fulfill({ json: { events: [], sources: [] } }))
|
await page.route('**/api/v1/calendar-events?*', route => route.fulfill({ json: { events: [], sources: [] } }))
|
||||||
await page.goto('/')
|
await page.goto('/')
|
||||||
const mobile = (await page.viewportSize())!.width <= 930
|
const mobile = (await page.viewportSize())!.width <= 930
|
||||||
if (mobile) await bottomTab(page, '日历订阅').click()
|
if (mobile) await bottomTab(page, '日历订阅').click()
|
||||||
else await page.locator('.primary-nav').getByRole('button', { name: '日历订阅', exact: true }).click()
|
else await page.locator('.primary-nav').getByRole('button', { name: '日历订阅', exact: true }).click()
|
||||||
const strip = page.getByRole('group', { name: '选择日期' })
|
const strip = page.getByRole('group', { name: '选择日期' })
|
||||||
|
const filters = page.locator('.calendar-filters')
|
||||||
await expect(strip).toBeVisible()
|
await expect(strip).toBeVisible()
|
||||||
|
await expect(filters).toBeVisible()
|
||||||
await expect(strip.locator('.calendar-week-day')).toHaveCount(7)
|
await expect(strip.locator('.calendar-week-day')).toHaveCount(7)
|
||||||
const metrics = await strip.evaluate(element => ({
|
const metrics = await strip.evaluate(element => {
|
||||||
documentOverflow: document.documentElement.scrollWidth - document.documentElement.clientWidth,
|
const filters = document.querySelector<HTMLElement>('.calendar-filters')!
|
||||||
stripOverflow: element.scrollWidth > element.clientWidth,
|
const calendar = document.querySelector<HTMLElement>('.calendar-view')!
|
||||||
buttons: [...element.querySelectorAll<HTMLElement>('.calendar-week-day')].map(button => ({ width: button.getBoundingClientRect().width, height: button.getBoundingClientRect().height })),
|
const main = calendar.closest('main') as HTMLElement
|
||||||
}))
|
return {
|
||||||
|
documentOverflow: document.documentElement.scrollWidth - document.documentElement.clientWidth,
|
||||||
|
bodyOverflow: document.body.scrollWidth - document.body.clientWidth,
|
||||||
|
mainOverflow: main.scrollWidth - main.clientWidth,
|
||||||
|
calendarOverflow: calendar.scrollWidth - calendar.clientWidth,
|
||||||
|
filters: { clientWidth: filters.clientWidth, scrollWidth: filters.scrollWidth, overflowX: getComputedStyle(filters).overflowX },
|
||||||
|
stripOverflow: element.scrollWidth > element.clientWidth,
|
||||||
|
buttons: [...element.querySelectorAll<HTMLElement>('.calendar-week-day')].map(button => ({ width: button.getBoundingClientRect().width, height: button.getBoundingClientRect().height })),
|
||||||
|
}
|
||||||
|
})
|
||||||
expect(metrics.documentOverflow).toBe(0)
|
expect(metrics.documentOverflow).toBe(0)
|
||||||
|
expect(metrics.bodyOverflow).toBe(0)
|
||||||
|
expect(metrics.mainOverflow).toBe(0)
|
||||||
|
expect(metrics.calendarOverflow).toBe(0)
|
||||||
|
if ((await page.viewportSize())!.width <= 930) expect(metrics.filters.clientWidth).toBeLessThan(metrics.filters.scrollWidth)
|
||||||
|
else expect(metrics.filters.clientWidth).toBeGreaterThanOrEqual(metrics.filters.scrollWidth)
|
||||||
|
expect(metrics.filters.overflowX).toBe('auto')
|
||||||
expect(metrics.buttons.every(button => button.width >= 44 && button.height >= 44)).toBeTruthy()
|
expect(metrics.buttons.every(button => button.width >= 44 && button.height >= 44)).toBeTruthy()
|
||||||
const selectedColors = await strip.locator('.calendar-week-day.is-selected').evaluate(element => {
|
const selectedColors = await strip.locator('.calendar-week-day.is-selected').evaluate(element => {
|
||||||
const style = getComputedStyle(element)
|
const style = getComputedStyle(element)
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ 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','memos','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('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('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)')})
|
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)')})
|
||||||
|
it('uses the shell page title once and contains the horizontal source scroller',()=>{const panel=readFileSync('src/CalendarPanel.vue','utf8');expect(panel).not.toContain('<h1>日历订阅</h1>');expect(css).toContain('.calendar-view{min-width:0;');expect(css).toContain('.calendar-filters{min-width:0;max-width:100%;')})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ onMounted(()=>void load())
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="calendar-view" :class="{loading}">
|
<section class="calendar-view" :class="{loading}">
|
||||||
<header class="calendar-heading"><div><h1>日历订阅</h1><p>{{filteredEvents.length}} 个日程 · {{subscriptions.length}} 个来源</p></div><button class="soft-button calendar-manage" aria-label="管理日历源" @click="manageOpen=true"><Settings2/>日历源</button></header>
|
<header class="calendar-heading"><p>{{filteredEvents.length}} 个日程 · {{subscriptions.length}} 个来源</p><button class="soft-button calendar-manage" aria-label="管理日历源" @click="manageOpen=true"><Settings2/>日历源</button></header>
|
||||||
<p v-if="error" class="inline-error" role="alert">{{error}}</p>
|
<p v-if="error" class="inline-error" role="alert">{{error}}</p>
|
||||||
<div class="calendar-toolbar"><button aria-label="上一周" @click="moveWeek(-1)"><ChevronLeft/></button><button class="calendar-today" aria-label="回到今天" @click="today">今天</button><strong>{{weekLabel}}</strong><button aria-label="下一周" @click="moveWeek(1)"><ChevronRight/></button></div>
|
<div class="calendar-toolbar"><button aria-label="上一周" @click="moveWeek(-1)"><ChevronLeft/></button><button class="calendar-today" aria-label="回到今天" @click="today">今天</button><strong>{{weekLabel}}</strong><button aria-label="下一周" @click="moveWeek(1)"><ChevronRight/></button></div>
|
||||||
<div class="calendar-week-strip" role="group" aria-label="选择日期"><button v-for="day in weekDays" :key="key(day)" class="calendar-week-day" :class="{'is-selected':key(day)===selectedDayKey,'is-today':isToday(day)}" :data-day="key(day)" :aria-label="`${displayDay(key(day))}${dayEventCount(day)?`,${dayEventCount(day)}个日程`:',无日程'}`" :aria-pressed="key(day)===selectedDayKey" @click="selectDay(day)"><small>{{weekDayLabel(day)}}</small><b>{{day.getDate()}}</b><i v-if="dayEventCount(day)" aria-hidden="true">{{dayEventCount(day)}}</i></button></div>
|
<div class="calendar-week-strip" role="group" aria-label="选择日期"><button v-for="day in weekDays" :key="key(day)" class="calendar-week-day" :class="{'is-selected':key(day)===selectedDayKey,'is-today':isToday(day)}" :data-day="key(day)" :aria-label="`${displayDay(key(day))}${dayEventCount(day)?`,${dayEventCount(day)}个日程`:',无日程'}`" :aria-pressed="key(day)===selectedDayKey" @click="selectDay(day)"><small>{{weekDayLabel(day)}}</small><b>{{day.getDate()}}</b><i v-if="dayEventCount(day)" aria-hidden="true">{{dayEventCount(day)}}</i></button></div>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user