fix: match Today approved prototype
This commit is contained in:
@@ -1,16 +1,65 @@
|
||||
import type { APIRequestContext } from '@playwright/test'
|
||||
import { expect, test } from './fixtures'
|
||||
|
||||
const widths = [721, 720, 390, 375]
|
||||
|
||||
test('Today plain-list layout matches the approved responsive geometry', async ({ page }) => {
|
||||
async function csrf(request: APIRequestContext) {
|
||||
const state = await request.storageState()
|
||||
return state.cookies.find(cookie => cookie.name === 'dodo_csrf')?.value ?? ''
|
||||
}
|
||||
|
||||
async function mutate(request: APIRequestContext, baseURL: string, path: string, data: Record<string, unknown>) {
|
||||
return request.post(path, { data, headers: { 'x-csrf-token': await csrf(request), origin: baseURL } })
|
||||
}
|
||||
|
||||
test('Today plain-list layout matches the approved responsive geometry', async ({ page, request, baseURL }, testInfo) => {
|
||||
const runId = `${testInfo.project.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
||||
const taskTitle = `原型对齐今日任务-${runId}`
|
||||
const habitName = `原型对齐习惯-${runId}`
|
||||
const bootstrap = await request.get('/api/v1/bootstrap')
|
||||
expect(bootstrap.ok(), await bootstrap.text()).toBeTruthy()
|
||||
const inbox = (await bootstrap.json()).lists.find((item: { is_inbox: boolean }) => item.is_inbox)
|
||||
expect(inbox).toBeTruthy()
|
||||
const today = new Intl.DateTimeFormat('sv-SE', {
|
||||
timeZone: 'Asia/Shanghai',
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
}).format(new Date())
|
||||
const taskResponse = await mutate(request, baseURL!, '/api/v1/tasks', { title: taskTitle, list_id: inbox.id, due_at: `${today}T23:59:00+08:00`, due_has_time: false })
|
||||
expect(taskResponse.ok(), await taskResponse.text()).toBeTruthy()
|
||||
const taskId = (await taskResponse.json()).id as string
|
||||
const habitResponse = await mutate(request, baseURL!, '/api/v1/habits', { name: habitName, kind: 'numeric', target: 8, max_value: 8, schedule_type: 'daily' })
|
||||
expect(habitResponse.ok(), await habitResponse.text()).toBeTruthy()
|
||||
const habitId = (await habitResponse.json()).id as string
|
||||
await page.goto('/')
|
||||
const taskRowLocator = page.locator('#today-tasks .task-row').filter({ hasText: taskTitle })
|
||||
const habitRowLocator = page.locator('.today-habit-row').filter({ hasText: habitName })
|
||||
await expect(taskRowLocator).toHaveCount(1)
|
||||
await expect(habitRowLocator).toHaveCount(1)
|
||||
for (const width of widths) {
|
||||
await page.setViewportSize({ width, height: width <= 390 ? 844 : 900 })
|
||||
await expect.poll(async () => page.evaluate(() => {
|
||||
const content = document.querySelector<HTMLElement>('.today-context')?.getBoundingClientRect()
|
||||
const environment = document.querySelector<HTMLElement>('.today-environment')?.getBoundingClientRect()
|
||||
if (!content || !environment) return null
|
||||
return {
|
||||
media720: matchMedia('(max-width: 720.98px)').matches,
|
||||
contentWidth: Math.round(content.width),
|
||||
environmentHeight: Math.round(environment.height),
|
||||
}
|
||||
})).toEqual({
|
||||
media720: width <= 720,
|
||||
contentWidth: width === 721 ? 630 : width - 58,
|
||||
environmentHeight: width === 721 ? 55 : width === 375 ? 90 : 93,
|
||||
})
|
||||
await expect(taskRowLocator).toHaveCount(1)
|
||||
await expect(habitRowLocator).toHaveCount(1)
|
||||
await expect(page.locator('.today-environment')).toBeVisible()
|
||||
await expect(page.getByRole('button', { name: '刷新当前页面' })).toBeVisible()
|
||||
await expect(page.getByRole('switch', { name: '显示已完成' })).toHaveCount(1)
|
||||
await expect(page.locator('.today-environment__gold')).toContainText('Au99.99')
|
||||
const metrics = await page.evaluate(() => {
|
||||
const metrics = await page.evaluate(({ taskId, habitId }) => {
|
||||
const rect = (selector: string) => document.querySelector<HTMLElement>(selector)!.getBoundingClientRect()
|
||||
const environment = document.querySelector<HTMLElement>('.today-environment')!
|
||||
const calendar = document.querySelector<HTMLElement>('.today-environment__calendar')!
|
||||
@@ -22,6 +71,20 @@ test('Today plain-list layout matches the approved responsive geometry', async (
|
||||
const firstSection = document.querySelector<HTMLElement>('.today-section-toggle')!
|
||||
const main = document.querySelector<HTMLElement>('main.today-main')!
|
||||
const content = rect('.today-context')
|
||||
const sectionTitle = firstSection.querySelector<HTMLElement>('.today-section-title')!
|
||||
const sectionSummary = firstSection.querySelector<HTMLElement>('.today-section-summary')!
|
||||
const sectionIcon = firstSection.querySelector<HTMLElement>('.today-section-chevron')!
|
||||
const summaries = [...document.querySelectorAll<HTMLElement>('.today-section-summary')].map(summary => ({
|
||||
text: summary.innerText.trim(),
|
||||
expected: summary.closest<HTMLButtonElement>('.today-section-toggle')?.getAttribute('aria-controls') === 'today-habits'
|
||||
? document.querySelectorAll('.today-habit-row').length
|
||||
: summary.closest<HTMLButtonElement>('.today-section-toggle')?.getAttribute('aria-controls') === 'today-overdue'
|
||||
? document.querySelectorAll('#today-overdue .task-row').length
|
||||
: document.querySelectorAll('#today-tasks .task-row').length,
|
||||
}))
|
||||
const taskRow = document.querySelector<HTMLElement>(`#today-tasks .task-row[data-task-id="${taskId}"]`)!
|
||||
const habitRow = document.querySelector<HTMLElement>(`.today-habit-row[data-habit-id="${habitId}"]`)!
|
||||
const fab = document.querySelector<HTMLElement>('.unified-fab')!
|
||||
const orderedElements = [environment, title, remaining, filter, firstSection]
|
||||
const ordered = orderedElements.map((element) => {
|
||||
const box = element.getBoundingClientRect()
|
||||
@@ -50,13 +113,16 @@ test('Today plain-list layout matches the approved responsive geometry', async (
|
||||
const secondary = [...environment.querySelectorAll<HTMLElement>('small')].map(node => ({ scrollWidth: node.scrollWidth, clientWidth: node.clientWidth, visible: visible(node) }))
|
||||
return {
|
||||
viewport: innerWidth,
|
||||
media720: matchMedia('(max-width: 720.98px)').matches,
|
||||
documentOverflow: document.documentElement.scrollWidth - document.documentElement.clientWidth,
|
||||
mainOverflow: main.scrollWidth - main.clientWidth,
|
||||
main: { ...main.getBoundingClientRect().toJSON(), clientWidth: main.clientWidth, paddingLeft: getComputedStyle(main).paddingLeft, paddingRight: getComputedStyle(main).paddingRight },
|
||||
content: { left: content.left, right: content.right, width: content.width },
|
||||
environment: { ...environment.getBoundingClientRect().toJSON(), scrollWidth: environment.scrollWidth, clientWidth: environment.clientWidth },
|
||||
directions: { calendar: getComputedStyle(calendar).flexDirection, weather: getComputedStyle(weather).flexDirection, gold: getComputedStyle(gold).flexDirection },
|
||||
uniqueRows,
|
||||
collisions,
|
||||
collisionRects: [calendar, weather, gold].map(element => element.getBoundingClientRect().toJSON()),
|
||||
secondary,
|
||||
ordered,
|
||||
orderedPairs,
|
||||
@@ -65,8 +131,32 @@ test('Today plain-list layout matches the approved responsive geometry', async (
|
||||
weatherText: weather.innerText,
|
||||
goldText: gold.innerText,
|
||||
goldLabel: gold.querySelector('.today-environment__gold-date')?.getAttribute('aria-label'),
|
||||
summaries,
|
||||
styles: {
|
||||
mainPaddingLeft: getComputedStyle(main).paddingLeft,
|
||||
titleFontSize: getComputedStyle(title).fontSize,
|
||||
titleFontWeight: getComputedStyle(title).fontWeight,
|
||||
filterWidth: filter.getBoundingClientRect().width,
|
||||
filterHeight: filter.getBoundingClientRect().height,
|
||||
filterVisualHeight: filter.querySelector<HTMLElement>('.completed-filter-pill__track')!.getBoundingClientRect().height,
|
||||
filterBackground: getComputedStyle(filter).backgroundColor,
|
||||
filterBorderRadius: getComputedStyle(filter).borderRadius,
|
||||
sectionBorderBottom: getComputedStyle(firstSection).borderBottomWidth,
|
||||
sectionTitleSize: getComputedStyle(sectionTitle).fontSize,
|
||||
sectionTitleWeight: getComputedStyle(sectionTitle).fontWeight,
|
||||
sectionSummaryText: sectionSummary.innerText,
|
||||
sectionIconText: sectionIcon.textContent,
|
||||
taskHeight: taskRow.getBoundingClientRect().height,
|
||||
taskBackground: getComputedStyle(taskRow).backgroundColor,
|
||||
taskBorderBottom: getComputedStyle(taskRow).borderBottomWidth,
|
||||
habitHeight: habitRow.getBoundingClientRect().height,
|
||||
habitBackground: getComputedStyle(habitRow).backgroundColor,
|
||||
habitBorderBottom: getComputedStyle(habitRow).borderBottomWidth,
|
||||
fabShadow: getComputedStyle(fab).boxShadow,
|
||||
fabIconWidth: fab.querySelector('svg')?.getBoundingClientRect().width,
|
||||
},
|
||||
}
|
||||
})
|
||||
}, { taskId, habitId })
|
||||
console.log(`[today-geometry][${width}] ${JSON.stringify(metrics)}`)
|
||||
expect(metrics.documentOverflow).toBe(0)
|
||||
expect(metrics.mainOverflow).toBeLessThanOrEqual(0)
|
||||
@@ -78,14 +168,43 @@ test('Today plain-list layout matches the approved responsive geometry', async (
|
||||
expect(metrics.goldText).toContain('Au99.99')
|
||||
expect(metrics.goldLabel).toMatch(/市场日期 \d{4}-\d{2}-\d{2}/)
|
||||
expect(metrics.secondary.every(line => line.visible && line.scrollWidth <= line.clientWidth)).toBeTruthy()
|
||||
expect(metrics.styles.titleFontWeight).toBe('700')
|
||||
expect(metrics.styles.sectionTitleSize).toBe('13px')
|
||||
expect(metrics.styles.sectionTitleWeight).toBe('700')
|
||||
expect(metrics.styles.sectionSummaryText).not.toContain('项')
|
||||
expect(metrics.summaries.every(summary => /^\d+$/.test(summary.text) && Number(summary.text) === summary.expected)).toBeTruthy()
|
||||
expect(metrics.styles.sectionIconText).not.toContain('项')
|
||||
expect(metrics.styles.sectionBorderBottom).toBe('1px')
|
||||
expect(metrics.styles.taskHeight).toBeCloseTo(58, 0)
|
||||
expect(metrics.styles.habitHeight).toBeCloseTo(58, 0)
|
||||
expect(metrics.styles.taskBackground).toBe('rgba(0, 0, 0, 0)')
|
||||
expect(metrics.styles.habitBackground).toBe('rgba(0, 0, 0, 0)')
|
||||
expect(metrics.styles.taskBorderBottom).toBe('1px')
|
||||
expect(metrics.styles.habitBorderBottom).toBe('1px')
|
||||
expect(metrics.styles.filterWidth).toBeLessThanOrEqual(90)
|
||||
expect(metrics.styles.filterHeight).toBeGreaterThanOrEqual(width <= 720 ? 44 : 36)
|
||||
expect(metrics.styles.filterVisualHeight).toBeLessThanOrEqual(36)
|
||||
expect(metrics.styles.filterBackground).toBe('rgba(0, 0, 0, 0)')
|
||||
expect(metrics.styles.filterBorderRadius).toBe('0px')
|
||||
expect(metrics.styles.fabShadow).toContain('8px 18px')
|
||||
expect(metrics.styles.fabIconWidth).toBeCloseTo(30, 0)
|
||||
if (width === 721) {
|
||||
expect(metrics.uniqueRows).toHaveLength(1)
|
||||
expect(metrics.environment.height).toBeLessThan(80)
|
||||
expect(metrics.content.width).toBeCloseTo(630, 0)
|
||||
expect(metrics.environment.height).toBeLessThanOrEqual(72)
|
||||
expect(metrics.styles.titleFontSize).toBe('34px')
|
||||
} else {
|
||||
expect(metrics.uniqueRows).toHaveLength(2)
|
||||
expect(metrics.directions.calendar).toBe('row')
|
||||
expect(metrics.directions.weather).toBe('column')
|
||||
expect(metrics.directions.gold).toBe('column')
|
||||
if (width <= 720) {
|
||||
expect(metrics.content.width).toBeCloseTo(width - 58, 0)
|
||||
expect(metrics.content.left).toBeCloseTo(29, 0)
|
||||
expect(metrics.styles.mainPaddingLeft).toBe('29px')
|
||||
expect(metrics.styles.titleFontSize).toBe('24px')
|
||||
expect(metrics.environment.height).toBeCloseTo(width === 375 ? 90 : 93, 0)
|
||||
}
|
||||
}
|
||||
if (width >= 721) {
|
||||
expect(metrics.content.width).toBeLessThanOrEqual(721)
|
||||
|
||||
@@ -1432,7 +1432,7 @@ onUnmounted(() => {
|
||||
<small v-if="error" role="alert">{{ error }}</small>
|
||||
</section>
|
||||
</div>
|
||||
<div v-else class="shell" :class="{ 'sidebar-collapsed': sidebarCollapsed, 'detail-open': Boolean(selectedTask), 'memo-detail-open': activeView==='memos' && memoDetailOpen, 'mobile-sidebar-open': mobileSidebar }">
|
||||
<div v-else class="shell" :class="{ 'today-active': activeView==='today', 'sidebar-collapsed': sidebarCollapsed, 'detail-open': Boolean(selectedTask), 'memo-detail-open': activeView==='memos' && memoDetailOpen, 'mobile-sidebar-open': mobileSidebar }">
|
||||
<div v-if="mobileSidebar || mobileDetail" class="scrim" @click="mobileSidebar=false;mobileDetail=false" />
|
||||
<aside class="sidebar" :inert="memoBackgroundInert ? true : undefined" :class="{ open: mobileSidebar }" @keydown.esc="sidebarCreateOpen=false;closeArchivedListAction();closeSidebarAction()">
|
||||
<div class="brand-row"><div class="brand small brand-lockup"><img class="brand-logo" src="/dodo-logo.svg" alt=""><span class="brand-wordmark">dodo</span></div><button class="icon mobile-only" aria-label="关闭菜单" @click="mobileSidebar=false"><X /></button></div>
|
||||
@@ -1532,14 +1532,14 @@ onUnmounted(() => {
|
||||
</section>
|
||||
<template v-if="activeView==='today'">
|
||||
<section v-if="overdueTaskTree.length" class="overdue-section today-collapsible-section">
|
||||
<button id="today-overdue-heading" class="today-section-toggle" type="button" :aria-expanded="!todaySectionCollapse.overdue" aria-controls="today-overdue" @click="toggleTodaySection('overdue')"><span class="today-section-title">逾期</span><span class="today-section-summary">{{overdueTaskTree.length}} 项</span><ChevronRight v-if="todaySectionCollapse.overdue" aria-hidden="true"/><ChevronDown v-else aria-hidden="true"/></button>
|
||||
<button id="today-overdue-heading" class="today-section-toggle" type="button" :aria-expanded="!todaySectionCollapse.overdue" aria-controls="today-overdue" @click="toggleTodaySection('overdue')"><span class="today-section-title">逾期</span><span class="today-section-summary">{{overdueTaskTree.length}}</span><span class="today-section-chevron" aria-hidden="true">{{ todaySectionCollapse.overdue ? '›' : '⌄' }}</span></button>
|
||||
<div v-show="!todaySectionCollapse.overdue" id="today-overdue" class="task-list overdue-list" role="region" aria-labelledby="today-overdue-heading">
|
||||
<template v-for="node in overdueTaskTree" :key="`overdue-${node.task.id}`">
|
||||
<article :data-task-id="node.task.id" class="task-row overdue-task swipeable" :class="{'just-completed':justCompletedTaskIds.has(node.task.id),'completion-exiting':completionExitingTaskIds.has(node.task.id),ready:Math.abs(taskSwipeOffsets[node.task.id] ?? 0) >= 64}" :style="{ '--swipe-x': `${taskSwipeOffsets[node.task.id] ?? 0}px` }" @pointerdown="startTaskPointer(node.task, $event)" @pointermove="moveTaskPointer(node.task, $event)" @pointerup="finishTaskPointer(node.task, $event)" @pointercancel="cancelTaskPointer(node.task)" @touchstart.passive="startTaskSwipe(node.task, $event)" @touchmove.passive="moveTaskSwipe(node.task, $event)" @touchend="finishTaskSwipe(node.task, $event)" @touchcancel="cancelTaskSwipe(node.task)"><button class="task-check" :aria-label="`完成${node.task.title}`" :aria-pressed="false" @click.stop="toggle(node.task)"><span class="task-check-mark" :class="`p${node.task.priority}`"></span></button><div class="task-main" role="button" tabindex="0" @click="selectTaskUnlessSwiped(node.task)" @keydown.enter="selectTaskUnlessSwiped(node.task)" @keydown.space.prevent="selectTaskUnlessSwiped(node.task)"><strong :title="node.task.title">{{node.task.title}}</strong><span v-if="node.subtasks.length" class="meta"><span class="meta-item"><ListChecks/>{{node.subtasks.filter(t=>t.completed).length}}/{{node.subtasks.length}}</span></span></div><span v-if="node.task.due_at" class="task-tail"><TaskDueDisplay :due-at="node.task.due_at" :due-has-time="node.task.due_has_time" :completed="node.task.completed" :now-ms="taskDueNowMs" /></span></article>
|
||||
</template>
|
||||
</div>
|
||||
</section>
|
||||
<button id="today-tasks-heading" class="today-section-toggle today-section-anchor" type="button" :aria-expanded="!todaySectionCollapse.tasks" aria-controls="today-tasks" @click="toggleTodaySection('tasks')"><span class="today-section-title">今天</span><span class="today-section-summary">{{totalTasks}} 项</span><ChevronRight v-if="todaySectionCollapse.tasks" aria-hidden="true"/><ChevronDown v-else aria-hidden="true"/></button>
|
||||
<button id="today-tasks-heading" class="today-section-toggle today-section-anchor" type="button" :aria-expanded="!todaySectionCollapse.tasks" aria-controls="today-tasks" @click="toggleTodaySection('tasks')"><span class="today-section-title">今天</span><span class="today-section-summary">{{totalTasks}}</span><span class="today-section-chevron" aria-hidden="true">{{ todaySectionCollapse.tasks ? '›' : '⌄' }}</span></button>
|
||||
</template>
|
||||
<div v-if="activeView!=='today'" class="list-toolbar"><span v-if="activeView==='trash' || totalPages > 1 || totalTasks > 0">{{ totalPages > 1 ? `第 ${page} / ${totalPages} 页 · ` : '' }}共 {{totalTasks}} 项</span><span class="list-toolbar-actions"><button v-if="query" class="link" @click="query=''">清除搜索</button><button v-if="taskReorderAvailable" class="soft-button reorder-mode-toggle task-reorder-toggle" type="button" :aria-pressed="taskReorderMode" @click="taskReorderMode=!taskReorderMode;cancelTaskReorder()">{{ taskReorderMode ? '完成' : '调整顺序' }}</button></span></div>
|
||||
<div v-if="activeView!=='trash' && totalPages > 1" class="pager"><button class="secondary" :disabled="page<=1 || loading" @click="previousPage">上一页</button><span>{{page}} / {{totalPages}}</span><button class="secondary" :disabled="page>=totalPages || loading" @click="nextPage">下一页</button></div>
|
||||
@@ -1556,7 +1556,7 @@ onUnmounted(() => {
|
||||
<div v-else-if="!visibleTasks.length&&!loading" class="empty"><ListTodo/><b>{{ query ? '没有匹配的任务' : hiddenCompletedTaskCount > 0 ? '已完成任务已隐藏' : '这里还很安静' }}</b><span>{{query?'换个关键词试试':hiddenCompletedTaskCount > 0 ? '开启“显示已完成”即可查看。':'写下第一件想完成的小事吧'}}</span><button v-if="activeView==='today' && !query && hiddenCompletedTaskCount === 0" class="soft-button empty-action" @click="openTaskCompose"><Plus/>添加今天任务</button></div>
|
||||
</section>
|
||||
<div v-if="activeView==='today'" class="today-habits-section today-section-anchor">
|
||||
<button id="today-habits-heading" class="today-section-toggle" type="button" :aria-expanded="!todaySectionCollapse.habits" aria-controls="today-habits" @click="toggleTodaySection('habits')"><span class="today-section-title">习惯</span><span class="today-section-summary">{{todayHabitCompleted}} / {{todayHabitTotal}}</span><ChevronRight v-if="todaySectionCollapse.habits" aria-hidden="true"/><ChevronDown v-else aria-hidden="true"/></button>
|
||||
<button id="today-habits-heading" class="today-section-toggle" type="button" :aria-expanded="!todaySectionCollapse.habits" aria-controls="today-habits" @click="toggleTodaySection('habits')"><span class="today-section-title">习惯</span><span class="today-section-summary">{{todayHabitTotal}}</span><span class="today-section-chevron" aria-hidden="true">{{ todaySectionCollapse.habits ? '›' : '⌄' }}</span></button>
|
||||
<div v-show="!todaySectionCollapse.habits" id="today-habits" role="region" aria-labelledby="today-habits-heading">
|
||||
<MvpPanel ref="habitComposer" view="today-habits" :show-completed="showCompleted" @changed="refreshAll" @notice="toast" @summary="updateTodayHabitSummary" />
|
||||
</div>
|
||||
|
||||
@@ -56,9 +56,12 @@ describe('Today environment integration', () => {
|
||||
expect(app).not.toContain('today-task-track')
|
||||
expect(app).not.toContain('today-habit-track')
|
||||
expect(app).not.toContain('role="progressbar"')
|
||||
expect(css).toContain('main.today-main{padding-left:max(34px,calc((100% - 720px)/2));padding-right:max(34px,calc((100% - 720px)/2))}')
|
||||
expect(css).toContain('.today-context{margin:0 0 8px;border-bottom:1px solid var(--line)}')
|
||||
expect(css).toContain('.today-remaining{margin:7px 0 10px;color:var(--muted);font-size:13px}')
|
||||
expect(app).toContain("'today-active': activeView==='today'")
|
||||
expect(css).toContain('.shell.today-active .unified-fab{box-shadow:0 8px 18px rgba(174,65,29,.22)}')
|
||||
expect(css).not.toContain('\n.unified-fab{box-shadow:0 8px 18px rgba(174,65,29,.22)}')
|
||||
expect(css).toContain('@media(max-width:720.98px){main.today-main{padding-left:29px!important;padding-right:29px!important}')
|
||||
expect(css).toContain('.today-context{margin:0 0 14px;border-bottom:0}')
|
||||
expect(css).toContain('.today-remaining{margin:0 0 24px;color:var(--muted);font-size:13px}')
|
||||
expect(css).not.toContain('.today-board{')
|
||||
expect(css).not.toContain('.today-board__progress{')
|
||||
expect(css).not.toContain('.today-track{')
|
||||
|
||||
@@ -34,10 +34,10 @@ describe('Today independent collapsible sections', () => {
|
||||
expect(app).toContain('<span class="today-section-title">习惯</span>')
|
||||
expect(app).not.toMatch(/today-section-title"><(?:CalendarDays|ListTodo|Repeat2)\/>/)
|
||||
expect(app.match(/class="today-section-toggle(?: today-section-anchor)?"/g)).toHaveLength(3)
|
||||
expect(app).toContain('<span class="today-section-summary">{{totalTasks}} 项</span>')
|
||||
expect(app).toContain('<span class="today-section-summary">{{totalTasks}}</span>')
|
||||
expect(app).not.toContain('<span class="today-section-summary">{{taskTree.length}} 项</span>')
|
||||
expect(app).toContain('<ChevronRight v-if="todaySectionCollapse.overdue"')
|
||||
expect(app).toContain('<ChevronDown v-else')
|
||||
expect(app).toContain("{{ todaySectionCollapse.overdue ? '›' : '⌄' }}")
|
||||
expect(app).not.toContain('<ChevronRight v-if="todaySectionCollapse.overdue"')
|
||||
expect(css).toMatch(/\.today-section-toggle\{[^}]*min-height:44px/)
|
||||
})
|
||||
|
||||
|
||||
+21
-2
File diff suppressed because one or more lines are too long
@@ -1,2 +1 @@
|
||||
|
||||
.today-section-toggle{width:100%;min-height:44px;display:flex;align-items:center;gap:10px;padding:8px 4px;border:0;background:transparent;color:var(--text-primary);text-align:left;border-radius:10px}.today-section-toggle:hover{background:var(--surface-raised)}.today-section-toggle:focus-visible{outline:2px solid var(--accent);outline-offset:2px}.today-section-title{display:flex;align-items:center;gap:8px;font-weight:800;font-size:15px}.today-section-title svg{width:17px;height:17px;color:var(--accent)}.today-section-summary{margin-left:auto;color:var(--muted);font-size:13px;font-weight:650}.today-section-toggle>svg{flex:0 0 auto;color:var(--muted)}.today-collapsible-section,.today-habits-section{min-width:0}.today-habits-section.today-section-anchor{scroll-margin-top:18px}
|
||||
.today-section-toggle{width:100%;min-height:44px;display:flex;align-items:center;padding:0;border:0;border-bottom:1px solid #e8e0d5;background:transparent;color:var(--text-primary);text-align:left;border-radius:0}.today-section-toggle:hover{background:transparent}.today-section-toggle:focus-visible{outline:2px solid var(--accent);outline-offset:2px}.today-section-title{display:flex;align-items:center;font-weight:700;font-size:13px;letter-spacing:.02em}.today-section-summary{margin-left:auto;color:var(--muted);font-size:12px;font-weight:400}.today-section-chevron{width:14px;margin-left:3px;color:var(--muted);font-size:13px;line-height:1;text-align:right}.today-collapsible-section,.today-habits-section{min-width:0}.today-habits-section.today-section-anchor{scroll-margin-top:18px}
|
||||
|
||||
Reference in New Issue
Block a user