fix: account menu matches the redesigned board: System, Light, Dark stay in the menu and the sheet, and the visual-check exception on profile "Account menu" in web/visual/states.ts is removed
This commit is contained in:
1 parent
6dc47789ef
commit
27dadbbfa6
6 files changed
+68
-20
No files matched your search
@@ -92,7 +92,7 @@ Feature: Profile page, display name and the account menu
|
||||
checked (kept from the shipped menu), a separator, and "Log out" (icon log-out); items are
|
||||
32 px on desktop and 48 px at 15 px text on mobile
|
||||
And activating "Profile" navigates to /profile and closes the menu (when already on /profile
|
||||
it only closes); activating a theme item sets the preference and closes the menu;
|
||||
it only closes); activating a theme item sets the preference and, in the desktop popover, keeps the menu open (the mobile sheet closes);
|
||||
activating "Log out" behaves as today: POST /v1/auth/logout in a try/finally, then
|
||||
session.clear() and a navigation to /login
|
||||
And avatar initials are the uppercased first Unicode character of each of the first two words
|
||||
@@ -296,7 +296,10 @@ Feature: Profile page, display name and the account menu
|
||||
And its header shows "marta" in semibold and "marta@renew.example" in muted 12 px
|
||||
And below it are "Profile" (circle-user), a separator, "System", "Light", "Dark" (the
|
||||
current preference checked), a separator, and "Log out" (log-out)
|
||||
And every item is 32 px high
|
||||
And "Profile" is not highlighted
|
||||
And "System", "Light" and "Dark" form one group where choosing one applies the theme at once
|
||||
and keeps the menu open
|
||||
|
||||
Scenario: Account menu — opened from the drawer on mobile
|
||||
Given the user is on /home on a viewport below lg and the navigation drawer is open
|
||||
@@ -305,8 +308,17 @@ Feature: Profile page, display name and the account menu
|
||||
with the Home page behind it
|
||||
And its header shows a 36 px avatar "MA", the name "marta" at 15 px semibold and the email
|
||||
"marta@renew.example" muted
|
||||
And its items are 48 px high: "Profile", a separator, "System", "Light", "Dark", a separator,
|
||||
"Log out"
|
||||
And its items are 48 px high at 15 px text: "Profile", a separator, "System", "Light", "Dark",
|
||||
a separator, "Log out"
|
||||
And the sheet has the same items in the same order as the desktop popover, so it is no taller
|
||||
than the board's sheet
|
||||
|
||||
Scenario: Account menu matches the board with no visual exception
|
||||
Given web/visual/states.ts has no exception on the profile state "Account menu"
|
||||
When `task visual:check -- profile "Account menu"` runs at desktop and mobile, light and dark
|
||||
Then every capture is within 10 %
|
||||
And System, Light and Dark are present in the popover and in the sheet
|
||||
And item heights are 32 px on desktop and 48 px on mobile
|
||||
|
||||
Scenario Outline: Closing the menu
|
||||
Given the account menu is open
|
||||
|
||||
@@ -102,12 +102,17 @@ capture must be within 10 %. Base session mock: GET /v1/auth/me 200 with id 1, u
|
||||
"marta", email "marta@renew.example", is_verified true, display_name "" (state-specific
|
||||
overrides below).
|
||||
|
||||
- [ ] Account menu: /home with `now: boardNow` and the home Default fixtures, session as above,
|
||||
menu opened by clicking the chip (on mobile: open the drawer, then the account row). Name
|
||||
"marta", initials "MA", email "marta@renew.example". The theme items kept by user decision
|
||||
are not drawn on the board, so the mobile capture is expected to exceed 10 %. The user has
|
||||
approved a per-state `exception` for it, to be added with `approvedBy` only after the
|
||||
real difference is measured and reported to the user with its cause.
|
||||
- [ ] Account menu: /home with `now: boardNow` and the home Default fixtures, session as above
|
||||
(id 1, "marta", "marta@renew.example", display_name ""), menu opened by clicking the chip
|
||||
(on mobile: open the drawer, then the account row). Name "marta", initials "MA", email
|
||||
"marta@renew.example". The board now draws System, Light and Dark, with Light checked in
|
||||
its sample. The `exception` block is removed from web/visual/states.ts, so the default 10 %
|
||||
gate applies. `task visual:check -- profile "Account menu"` must PASS on all four captures
|
||||
(desktop and mobile, light and dark). If the only remaining diff is the board's Profile
|
||||
highlight on Home (the app highlights Profile only on /profile), do NOT re-add an exception
|
||||
and do NOT highlight Profile on Home. Stop and ask the user.
|
||||
- [ ] Manual: item heights measured 32 px (desktop popover) and 48 px (mobile sheet); arrow keys move
|
||||
between items; Escape and outside click close; choosing a theme keeps the menu open.
|
||||
- [ ] Loading: /profile with GET /v1/auth/me hanging; three skeleton cards, 16 px apart.
|
||||
- [ ] Default, no display name: empty field, "0 / 60", the username footer, Save at 40 %.
|
||||
- [ ] Editing: type "Marta Alves"; field focused; "11 / 60"; "Unsaved changes"; Save enabled.
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useMediaQuery } from '@vueuse/core'
|
||||
import { colorModeOptions, useAccountMenu } from '@/composables/useAccountMenu'
|
||||
|
||||
const DESKTOP_QUERY = '(min-width: 64rem)'
|
||||
const ITEM_CLASS = 'h-8 py-0'
|
||||
|
||||
const props = defineProps<{
|
||||
username: string
|
||||
@@ -60,7 +61,7 @@ const items = computed(() => [
|
||||
{
|
||||
label: 'Profile',
|
||||
icon: 'i-lucide-circle-user',
|
||||
class: onProfile.value ? 'bg-elevated' : undefined,
|
||||
class: onProfile.value ? `${ITEM_CLASS} bg-elevated` : ITEM_CLASS,
|
||||
onSelect: goToProfile,
|
||||
},
|
||||
],
|
||||
@@ -68,6 +69,8 @@ const items = computed(() => [
|
||||
type: 'checkbox' as const,
|
||||
label: option.label,
|
||||
checked: colorMode.preference === option.value,
|
||||
class: ITEM_CLASS,
|
||||
onSelect: (event: Event) => event.preventDefault(),
|
||||
onUpdateChecked: (checked: boolean) => {
|
||||
if (checked) {
|
||||
colorMode.preference = option.value
|
||||
@@ -78,6 +81,7 @@ const items = computed(() => [
|
||||
{
|
||||
label: 'Log out',
|
||||
icon: 'i-lucide-log-out',
|
||||
class: ITEM_CLASS,
|
||||
onSelect: () => emit('logout'),
|
||||
},
|
||||
],
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createPinia, setActivePinia } from 'pinia'
|
||||
import uiPlugin from '@nuxt/ui/vue-plugin'
|
||||
import router from '@/router'
|
||||
import { useApi } from '@/composables/useApi'
|
||||
import { useColorMode } from '@/composables/useColorMode'
|
||||
import DashboardShell from '../DashboardShell.vue'
|
||||
import { DESKTOP_WIDTH, MOBILE_WIDTH, setViewportWidth } from './viewport'
|
||||
|
||||
@@ -272,6 +273,29 @@ describe('Account menu: opened from the drawer on mobile', () => {
|
||||
expect(document.body.querySelector('[role="menu"]')).toBeNull()
|
||||
})
|
||||
|
||||
it('applies a theme choice and closes the sheet', async () => {
|
||||
const colorMode = useColorMode()
|
||||
colorMode.preference = 'system'
|
||||
await mountShell('/home', { width: MOBILE_WIDTH })
|
||||
;(document.body.querySelector('[data-slot="toggle"]') as HTMLButtonElement).click()
|
||||
await flushPromises()
|
||||
;(
|
||||
document.body.querySelector(
|
||||
'[role="dialog"] button[aria-haspopup]',
|
||||
) as HTMLButtonElement
|
||||
).click()
|
||||
await flushPromises()
|
||||
|
||||
const dark = Array.from(
|
||||
document.body.querySelectorAll<HTMLElement>('[role="menu"] [role="menuitemcheckbox"]'),
|
||||
).find((item) => item.textContent?.trim() === 'Dark')!
|
||||
dark.click()
|
||||
await flushPromises()
|
||||
|
||||
expect(colorMode.preference).toBe('dark')
|
||||
expect(document.body.querySelector('[role="menu"]')).toBeNull()
|
||||
})
|
||||
|
||||
it('logs out from the sheet', async () => {
|
||||
await mountShell('/home', { width: MOBILE_WIDTH })
|
||||
;(document.body.querySelector('[data-slot="toggle"]') as HTMLButtonElement).click()
|
||||
|
||||
@@ -236,7 +236,7 @@ describe('Menu items', () => {
|
||||
})
|
||||
|
||||
describe('Theme choice stays in the menu', () => {
|
||||
it('checks the current preference, sets Dark, closes, and shows Dark checked on reopening', async () => {
|
||||
it('checks the current preference, sets Dark at once and keeps the menu open', async () => {
|
||||
const colorMode = useColorMode()
|
||||
colorMode.preference = 'system'
|
||||
await mountMenu()
|
||||
@@ -248,15 +248,23 @@ describe('Theme choice stays in the menu', () => {
|
||||
await flushPromises()
|
||||
|
||||
expect(colorMode.preference).toBe('dark')
|
||||
expect(menu()).toBeNull()
|
||||
|
||||
await openMenu()
|
||||
|
||||
expect(menu()).not.toBeNull()
|
||||
expect(findItem('Dark').getAttribute('aria-checked')).toBe('true')
|
||||
expect(findItem('System').getAttribute('aria-checked')).toBe('false')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Item heights', () => {
|
||||
it('gives every desktop item 32 px', async () => {
|
||||
await mountMenu()
|
||||
await openMenu()
|
||||
|
||||
for (const item of itemsIn(menu()!)) {
|
||||
expect(item.classList.contains('h-8')).toBe(true)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('Avatar initials', () => {
|
||||
it.each([
|
||||
['marta', '', 'MA', 'marta'],
|
||||
|
||||
@@ -779,11 +779,6 @@ export const visualStates: Record<string, Record<string, VisualState>> = {
|
||||
'GET /v1/article/': { status: 200, body: sixArticlesFixture },
|
||||
},
|
||||
steps: openAccountMenu,
|
||||
exception: {
|
||||
maxPercent: 26,
|
||||
reason: 'Mobile sheet is ~153px taller than the board because the System/Light/Dark items are kept by user decision, and the board draws Profile highlighted on /home while the feature highlights it only on /profile',
|
||||
approvedBy: 'Florian Sylvain, 2026-09-29',
|
||||
},
|
||||
},
|
||||
Loading: {
|
||||
path: '/profile',
|
||||
|
||||
Reference in new issue
Block a user