style: add habit row state feedback
This commit is contained in:
@@ -33,7 +33,9 @@ function bottomTab(page: Page, name: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function openSidebarView(page: Page, name: string) {
|
async function openSidebarView(page: Page, name: string) {
|
||||||
await page.locator('.topbar').getByRole('button', { name: /展开菜单|收起菜单/ }).click()
|
if ((page.viewportSize()?.width ?? 0) <= 930) {
|
||||||
|
await page.locator('.topbar').getByRole('button', { name: /展开菜单|收起菜单/ }).click()
|
||||||
|
}
|
||||||
const target = page.locator('.sidebar').getByRole('button', { name, exact: true })
|
const target = page.locator('.sidebar').getByRole('button', { name, exact: true })
|
||||||
await expect(target).toBeInViewport()
|
await expect(target).toBeInViewport()
|
||||||
await target.click()
|
await target.click()
|
||||||
@@ -72,7 +74,8 @@ test('boolean habits complete in place while paused and unscheduled rows explain
|
|||||||
expect(pause.ok(), await pause.text()).toBeTruthy()
|
expect(pause.ok(), await pause.text()).toBeTruthy()
|
||||||
|
|
||||||
await page.goto('/')
|
await page.goto('/')
|
||||||
await bottomTab(page, '习惯').click()
|
if ((page.viewportSize()?.width ?? 0) <= 930) await bottomTab(page, '习惯').click()
|
||||||
|
else await page.locator('.sidebar').getByRole('button', { name: '习惯', exact: true }).click()
|
||||||
const booleanRow = page.locator('.habit-row').filter({ hasText: booleanName })
|
const booleanRow = page.locator('.habit-row').filter({ hasText: booleanName })
|
||||||
const booleanCheck = booleanRow.getByRole('button', { name: `完成${booleanName}一次` })
|
const booleanCheck = booleanRow.getByRole('button', { name: `完成${booleanName}一次` })
|
||||||
const checkGeometry = await booleanCheck.evaluate((element) => {
|
const checkGeometry = await booleanCheck.evaluate((element) => {
|
||||||
@@ -87,7 +90,14 @@ test('boolean habits complete in place while paused and unscheduled rows explain
|
|||||||
expect(checkGeometry.height).toBeGreaterThanOrEqual(44)
|
expect(checkGeometry.height).toBeGreaterThanOrEqual(44)
|
||||||
await booleanCheck.click()
|
await booleanCheck.click()
|
||||||
await expect(booleanRow).toHaveClass(/done/)
|
await expect(booleanRow).toHaveClass(/done/)
|
||||||
|
await expect(booleanRow).toHaveCSS('background-color', 'rgb(251, 247, 240)')
|
||||||
await expect(booleanRow.getByRole('button', { name: `减少${booleanName}一次` })).toHaveAttribute('aria-pressed', 'true')
|
await expect(booleanRow.getByRole('button', { name: `减少${booleanName}一次` })).toHaveAttribute('aria-pressed', 'true')
|
||||||
|
await booleanRow.locator('.habit-main').click()
|
||||||
|
await expect(booleanRow).toHaveClass(/selected/)
|
||||||
|
await expect(booleanRow).toHaveCSS('background-color', 'rgb(255, 244, 229)')
|
||||||
|
await expect(page.getByRole('dialog', { name: booleanName })).toBeVisible()
|
||||||
|
await page.getByRole('button', { name: '关闭习惯详情' }).click()
|
||||||
|
await expect(booleanRow).not.toHaveClass(/selected/)
|
||||||
await page.reload()
|
await page.reload()
|
||||||
const persistedBooleanRow = page.locator('.habit-row').filter({ hasText: booleanName })
|
const persistedBooleanRow = page.locator('.habit-row').filter({ hasText: booleanName })
|
||||||
await expect(persistedBooleanRow).toHaveClass(/done/)
|
await expect(persistedBooleanRow).toHaveClass(/done/)
|
||||||
|
|||||||
@@ -808,7 +808,7 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
<!-- 今日习惯:只展示“今天该做”的习惯,复用正式习惯行样式 -->
|
<!-- 今日习惯:只展示“今天该做”的习惯,复用正式习惯行样式 -->
|
||||||
<div v-if="view === 'today-habits' && (habits.length || !busy)" class="habit-list plain-list today-habit-list">
|
<div v-if="view === 'today-habits' && (habits.length || !busy)" class="habit-list plain-list today-habit-list">
|
||||||
<article v-for="h in visibleTodayHabits" :key="h.id" :data-habit-id="h.id" class="habit-row today-habit-row swipeable" :class="{ done: isDone(h, todayKey), 'just-completed': justCompletedHabitIds.has(h.id), 'completion-exiting': completionExitingHabitIds.has(h.id), ready: Math.abs(habitSwipeOffsets[h.id] ?? 0) >= 64 }" :style="{ '--swipe-x': `${habitSwipeOffsets[h.id] ?? 0}px` }" @pointerdown="startHabitPointer(h, $event)" @pointermove="moveHabitPointer(h, $event)" @pointerup="finishHabitPointer(h, $event)" @pointercancel="cancelHabitPointer(h)" @touchstart.passive="startHabitSwipe(h, $event)" @touchmove.passive="moveHabitSwipe(h, $event)" @touchend="finishHabitSwipe(h, $event)" @touchcancel="cancelHabitSwipe(h)">
|
<article v-for="h in visibleTodayHabits" :key="h.id" :data-habit-id="h.id" class="habit-row today-habit-row swipeable" :class="{ done: isDone(h, todayKey), selected:selectedHabit?.id===h.id, 'just-completed': justCompletedHabitIds.has(h.id), 'completion-exiting': completionExitingHabitIds.has(h.id), ready: Math.abs(habitSwipeOffsets[h.id] ?? 0) >= 64 }" :style="{ '--swipe-x': `${habitSwipeOffsets[h.id] ?? 0}px` }" @pointerdown="startHabitPointer(h, $event)" @pointermove="moveHabitPointer(h, $event)" @pointerup="finishHabitPointer(h, $event)" @pointercancel="cancelHabitPointer(h)" @touchstart.passive="startHabitSwipe(h, $event)" @touchmove.passive="moveHabitSwipe(h, $event)" @touchend="finishHabitSwipe(h, $event)" @touchcancel="cancelHabitSwipe(h)">
|
||||||
<button class="task-check habit-check" :disabled="!habitAction(h).writable" :aria-disabled="!habitAction(h).writable" :title="habitAction(h).reason" :aria-label="habitAction(h).reason || (isDone(h, todayKey) ? `减少${h.name}一次` : `完成${h.name}一次`)" :aria-pressed="isDone(h, todayKey)" @click.stop="toggleHabitFromButton(h)"><span class="task-check-mark"><Check v-if="isDone(h, todayKey)" /></span></button>
|
<button class="task-check habit-check" :disabled="!habitAction(h).writable" :aria-disabled="!habitAction(h).writable" :title="habitAction(h).reason" :aria-label="habitAction(h).reason || (isDone(h, todayKey) ? `减少${h.name}一次` : `完成${h.name}一次`)" :aria-pressed="isDone(h, todayKey)" @click.stop="toggleHabitFromButton(h)"><span class="task-check-mark"><Check v-if="isDone(h, todayKey)" /></span></button>
|
||||||
<div class="habit-main" role="button" tabindex="0" :aria-label="`查看习惯详情:${h.name}`" @click="openHabitDetail(h, $event.currentTarget as HTMLElement)" @keydown.enter.prevent="openHabitDetail(h, $event.currentTarget as HTMLElement)" @keydown.space.prevent="openHabitDetail(h, $event.currentTarget as HTMLElement)">
|
<div class="habit-main" role="button" tabindex="0" :aria-label="`查看习惯详情:${h.name}`" @click="openHabitDetail(h, $event.currentTarget as HTMLElement)" @keydown.enter.prevent="openHabitDetail(h, $event.currentTarget as HTMLElement)" @keydown.space.prevent="openHabitDetail(h, $event.currentTarget as HTMLElement)">
|
||||||
<span class="habit-name" :title="h.name">{{ h.name }}</span>
|
<span class="habit-name" :title="h.name">{{ h.name }}</span>
|
||||||
@@ -840,7 +840,7 @@ onBeforeUnmount(() => {
|
|||||||
<div v-if="view === 'habits'" class="list-section-heading habit-section-heading"><span class="list-section-title">今日习惯</span><span class="list-section-count">{{ todayHabitSummary.total }}</span><button v-if="habitReorderAvailable" class="list-section-action" type="button" :aria-pressed="habitReorderMode" @click="habitReorderMode=!habitReorderMode;cancelHabitReorder()">{{ habitReorderMode ? '完成' : '调整顺序' }}</button></div>
|
<div v-if="view === 'habits'" class="list-section-heading habit-section-heading"><span class="list-section-title">今日习惯</span><span class="list-section-count">{{ todayHabitSummary.total }}</span><button v-if="habitReorderAvailable" class="list-section-action" type="button" :aria-pressed="habitReorderMode" @click="habitReorderMode=!habitReorderMode;cancelHabitReorder()">{{ habitReorderMode ? '完成' : '调整顺序' }}</button></div>
|
||||||
<!-- 习惯列表支持整行滑动记录。 -->
|
<!-- 习惯列表支持整行滑动记录。 -->
|
||||||
<div v-if="view === 'habits'" class="habit-list plain-list">
|
<div v-if="view === 'habits'" class="habit-list plain-list">
|
||||||
<article v-for="h in visibleHabits" :key="h.id" :data-habit-id="h.id" class="habit-row habit-row--full swipeable" :class="{ done: isDone(h, todayKey), 'just-completed': justCompletedHabitIds.has(h.id), 'completion-exiting': completionExitingHabitIds.has(h.id), ready: Math.abs(habitSwipeOffsets[h.id] ?? 0) >= 64, reordering: habitReorder?.id === h.id, 'reorder-target': habitReorderTarget === h.id }" :style="{ '--swipe-x': `${habitSwipeOffsets[h.id] ?? 0}px`, '--reorder-y': `${habitReorder?.id === h.id ? habitReorder.offsetY : 0}px` }" @pointerdown="startHabitPointer(h, $event)" @pointermove="moveHabitPointer(h, $event)" @pointerup="finishHabitPointer(h, $event)" @pointercancel="cancelHabitPointer(h)" @touchstart.passive="startHabitSwipe(h, $event)" @touchmove.passive="moveHabitSwipe(h, $event)" @touchend="finishHabitSwipe(h, $event)" @touchcancel="cancelHabitSwipe(h)">
|
<article v-for="h in visibleHabits" :key="h.id" :data-habit-id="h.id" class="habit-row habit-row--full swipeable" :class="{ done: isDone(h, todayKey), selected:selectedHabit?.id===h.id, 'just-completed': justCompletedHabitIds.has(h.id), 'completion-exiting': completionExitingHabitIds.has(h.id), ready: Math.abs(habitSwipeOffsets[h.id] ?? 0) >= 64, reordering: habitReorder?.id === h.id, 'reorder-target': habitReorderTarget === h.id }" :style="{ '--swipe-x': `${habitSwipeOffsets[h.id] ?? 0}px`, '--reorder-y': `${habitReorder?.id === h.id ? habitReorder.offsetY : 0}px` }" @pointerdown="startHabitPointer(h, $event)" @pointermove="moveHabitPointer(h, $event)" @pointerup="finishHabitPointer(h, $event)" @pointercancel="cancelHabitPointer(h)" @touchstart.passive="startHabitSwipe(h, $event)" @touchmove.passive="moveHabitSwipe(h, $event)" @touchend="finishHabitSwipe(h, $event)" @touchcancel="cancelHabitSwipe(h)">
|
||||||
<button v-if="habitReorderMode && habitReorderAvailable" class="drag-handle habit-drag-handle" aria-label="上下拖动习惯排序" title="上下拖动排序" @pointerdown.stop="startHabitReorder(h, $event)" @pointermove.stop="moveHabitReorder(h, $event)" @pointerup.stop="finishHabitReorder(h, $event)" @pointercancel.stop="cancelHabitReorder"><GripVertical/></button>
|
<button v-if="habitReorderMode && habitReorderAvailable" class="drag-handle habit-drag-handle" aria-label="上下拖动习惯排序" title="上下拖动排序" @pointerdown.stop="startHabitReorder(h, $event)" @pointermove.stop="moveHabitReorder(h, $event)" @pointerup.stop="finishHabitReorder(h, $event)" @pointercancel.stop="cancelHabitReorder"><GripVertical/></button>
|
||||||
<button class="task-check habit-check" :disabled="!habitAction(h).writable" :aria-disabled="!habitAction(h).writable" :title="habitAction(h).reason" :aria-label="habitAction(h).reason || (isDone(h, todayKey) ? `减少${h.name}一次` : `完成${h.name}一次`)" :aria-pressed="isDone(h, todayKey)" @click.stop="toggleHabitFromButton(h)"><span class="task-check-mark"><Check v-if="isDone(h, todayKey)" /></span></button>
|
<button class="task-check habit-check" :disabled="!habitAction(h).writable" :aria-disabled="!habitAction(h).writable" :title="habitAction(h).reason" :aria-label="habitAction(h).reason || (isDone(h, todayKey) ? `减少${h.name}一次` : `完成${h.name}一次`)" :aria-pressed="isDone(h, todayKey)" @click.stop="toggleHabitFromButton(h)"><span class="task-check-mark"><Check v-if="isDone(h, todayKey)" /></span></button>
|
||||||
<div class="habit-main" role="button" tabindex="0" :aria-label="`查看习惯详情:${h.name}`" @click="openHabitDetail(h, $event.currentTarget as HTMLElement)" @keydown.enter.prevent="openHabitDetail(h, $event.currentTarget as HTMLElement)" @keydown.space.prevent="openHabitDetail(h, $event.currentTarget as HTMLElement)">
|
<div class="habit-main" role="button" tabindex="0" :aria-label="`查看习惯详情:${h.name}`" @click="openHabitDetail(h, $event.currentTarget as HTMLElement)" @keydown.enter.prevent="openHabitDetail(h, $event.currentTarget as HTMLElement)" @keydown.space.prevent="openHabitDetail(h, $event.currentTarget as HTMLElement)">
|
||||||
|
|||||||
+10
-12
File diff suppressed because one or more lines are too long
@@ -153,14 +153,14 @@ describe('approved five-detail polish', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
it('gives task rows distinct hover, selected, completed, and overdue feedback while habits stay quiet', () => {
|
it('gives task and habit rows distinct hover, selected, and completed feedback', () => {
|
||||||
expect(css).toContain('@media(hover:hover) and (pointer:fine){.plain-list>.task-row:not(.task-row--trash):hover{background:#fff8ee}}')
|
expect(css).toContain('@media(hover:hover) and (pointer:fine){.plain-list>.task-row:not(.task-row--trash):hover,.plain-list>.habit-row:hover{background:#fff8ee}}')
|
||||||
expect(css).toContain('@media(hover:hover) and (pointer:fine){.task-row:hover:not(.reordering):not(.task-row--trash){transform:translate(calc(var(--swipe-x) + 2px),var(--reorder-y,0px))}}')
|
expect(css).toContain('@media(hover:hover) and (pointer:fine){.task-row:hover:not(.reordering):not(.task-row--trash),.habit-row:hover:not(.reordering){transform:translate(calc(var(--swipe-x) + 2px),var(--reorder-y,0px))}}')
|
||||||
expect(css).toContain('.plain-list>.task-row:not(.task-row--trash).selected,.plain-list>.task-row:not(.task-row--trash).selected.done,.plain-list>.task-row:not(.task-row--trash).selected.overdue-task{background:#fff4e5}')
|
expect(css).toContain('.plain-list>.task-row:not(.task-row--trash).selected,.plain-list>.task-row:not(.task-row--trash).selected.done,.plain-list>.task-row:not(.task-row--trash).selected.overdue-task,.plain-list>.habit-row.selected,.plain-list>.habit-row.selected.done{background:#fff4e5}')
|
||||||
expect(css).toContain('.plain-list>.task-row:not(.task-row--trash).selected::before{background:var(--accent)}')
|
expect(css).toContain('.plain-list>.task-row:not(.task-row--trash).selected::before,.plain-list>.habit-row.selected::before{background:var(--accent)}')
|
||||||
expect(css).toContain('.plain-list>.task-row:not(.task-row--trash).done{background:#fbf7f0}')
|
expect(css).toContain('.plain-list>.task-row:not(.task-row--trash).done,.plain-list>.habit-row.done{background:#fbf7f0}')
|
||||||
expect(css).toContain('.plain-list>.task-row:not(.task-row--trash).overdue-task{background:#fff7f4}')
|
expect(css).toContain('.plain-list>.task-row:not(.task-row--trash).overdue-task{background:#fff7f4}')
|
||||||
expect(css).toContain('.plain-list>.habit-row:hover,.plain-list>.habit-row.done{background:transparent}')
|
expect(habits.match(/selected:selectedHabit\?\.id===h\.id/g)?.length).toBe(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders memo rows with title and time only at a fixed compact height', () => {
|
it('renders memo rows with title and time only at a fixed compact height', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user