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
|
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
|
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
|
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
|
activating "Log out" behaves as today: POST /v1/auth/logout in a try/finally, then
|
||||||
session.clear() and a navigation to /login
|
session.clear() and a navigation to /login
|
||||||
And avatar initials are the uppercased first Unicode character of each of the first two words
|
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 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
|
And below it are "Profile" (circle-user), a separator, "System", "Light", "Dark" (the
|
||||||
current preference checked), a separator, and "Log out" (log-out)
|
current preference checked), a separator, and "Log out" (log-out)
|
||||||
|
And every item is 32 px high
|
||||||
And "Profile" is not highlighted
|
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
|
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
|
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
|
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
|
And its header shows a 36 px avatar "MA", the name "marta" at 15 px semibold and the email
|
||||||
"marta@renew.example" muted
|
"marta@renew.example" muted
|
||||||
And its items are 48 px high: "Profile", a separator, "System", "Light", "Dark", a separator,
|
And its items are 48 px high at 15 px text: "Profile", a separator, "System", "Light", "Dark",
|
||||||
"Log out"
|
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
|
Scenario Outline: Closing the menu
|
||||||
Given the account menu is open
|
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
|
"marta", email "marta@renew.example", is_verified true, display_name "" (state-specific
|
||||||
overrides below).
|
overrides below).
|
||||||
|
|
||||||
- [ ] Account menu: /home with `now: boardNow` and the home Default fixtures, session as above,
|
- [ ] 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
|
(id 1, "marta", "marta@renew.example", display_name ""), menu opened by clicking the chip
|
||||||
"marta", initials "MA", email "marta@renew.example". The theme items kept by user decision
|
(on mobile: open the drawer, then the account row). Name "marta", initials "MA", email
|
||||||
are not drawn on the board, so the mobile capture is expected to exceed 10 %. The user has
|
"marta@renew.example". The board now draws System, Light and Dark, with Light checked in
|
||||||
approved a per-state `exception` for it, to be added with `approvedBy` only after the
|
its sample. The `exception` block is removed from web/visual/states.ts, so the default 10 %
|
||||||
real difference is measured and reported to the user with its cause.
|
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.
|
- [ ] 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 %.
|
- [ ] 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.
|
- [ ] 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'
|
import { colorModeOptions, useAccountMenu } from '@/composables/useAccountMenu'
|
||||||
|
|
||||||
const DESKTOP_QUERY = '(min-width: 64rem)'
|
const DESKTOP_QUERY = '(min-width: 64rem)'
|
||||||
|
const ITEM_CLASS = 'h-8 py-0'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
username: string
|
username: string
|
||||||
@@ -60,7 +61,7 @@ const items = computed(() => [
|
|||||||
{
|
{
|
||||||
label: 'Profile',
|
label: 'Profile',
|
||||||
icon: 'i-lucide-circle-user',
|
icon: 'i-lucide-circle-user',
|
||||||
class: onProfile.value ? 'bg-elevated' : undefined,
|
class: onProfile.value ? `${ITEM_CLASS} bg-elevated` : ITEM_CLASS,
|
||||||
onSelect: goToProfile,
|
onSelect: goToProfile,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -68,6 +69,8 @@ const items = computed(() => [
|
|||||||
type: 'checkbox' as const,
|
type: 'checkbox' as const,
|
||||||
label: option.label,
|
label: option.label,
|
||||||
checked: colorMode.preference === option.value,
|
checked: colorMode.preference === option.value,
|
||||||
|
class: ITEM_CLASS,
|
||||||
|
onSelect: (event: Event) => event.preventDefault(),
|
||||||
onUpdateChecked: (checked: boolean) => {
|
onUpdateChecked: (checked: boolean) => {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
colorMode.preference = option.value
|
colorMode.preference = option.value
|
||||||
@@ -78,6 +81,7 @@ const items = computed(() => [
|
|||||||
{
|
{
|
||||||
label: 'Log out',
|
label: 'Log out',
|
||||||
icon: 'i-lucide-log-out',
|
icon: 'i-lucide-log-out',
|
||||||
|
class: ITEM_CLASS,
|
||||||
onSelect: () => emit('logout'),
|
onSelect: () => emit('logout'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { createPinia, setActivePinia } from 'pinia'
|
|||||||
import uiPlugin from '@nuxt/ui/vue-plugin'
|
import uiPlugin from '@nuxt/ui/vue-plugin'
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
import { useApi } from '@/composables/useApi'
|
import { useApi } from '@/composables/useApi'
|
||||||
|
import { useColorMode } from '@/composables/useColorMode'
|
||||||
import DashboardShell from '../DashboardShell.vue'
|
import DashboardShell from '../DashboardShell.vue'
|
||||||
import { DESKTOP_WIDTH, MOBILE_WIDTH, setViewportWidth } from './viewport'
|
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()
|
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 () => {
|
it('logs out from the sheet', async () => {
|
||||||
await mountShell('/home', { width: MOBILE_WIDTH })
|
await mountShell('/home', { width: MOBILE_WIDTH })
|
||||||
;(document.body.querySelector('[data-slot="toggle"]') as HTMLButtonElement).click()
|
;(document.body.querySelector('[data-slot="toggle"]') as HTMLButtonElement).click()
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ describe('Menu items', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('Theme choice stays in the menu', () => {
|
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()
|
const colorMode = useColorMode()
|
||||||
colorMode.preference = 'system'
|
colorMode.preference = 'system'
|
||||||
await mountMenu()
|
await mountMenu()
|
||||||
@@ -248,15 +248,23 @@ describe('Theme choice stays in the menu', () => {
|
|||||||
await flushPromises()
|
await flushPromises()
|
||||||
|
|
||||||
expect(colorMode.preference).toBe('dark')
|
expect(colorMode.preference).toBe('dark')
|
||||||
expect(menu()).toBeNull()
|
expect(menu()).not.toBeNull()
|
||||||
|
|
||||||
await openMenu()
|
|
||||||
|
|
||||||
expect(findItem('Dark').getAttribute('aria-checked')).toBe('true')
|
expect(findItem('Dark').getAttribute('aria-checked')).toBe('true')
|
||||||
expect(findItem('System').getAttribute('aria-checked')).toBe('false')
|
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', () => {
|
describe('Avatar initials', () => {
|
||||||
it.each([
|
it.each([
|
||||||
['marta', '', 'MA', 'marta'],
|
['marta', '', 'MA', 'marta'],
|
||||||
|
|||||||
@@ -779,11 +779,6 @@ export const visualStates: Record<string, Record<string, VisualState>> = {
|
|||||||
'GET /v1/article/': { status: 200, body: sixArticlesFixture },
|
'GET /v1/article/': { status: 200, body: sixArticlesFixture },
|
||||||
},
|
},
|
||||||
steps: openAccountMenu,
|
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: {
|
Loading: {
|
||||||
path: '/profile',
|
path: '/profile',
|
||||||
|
|||||||
Reference in new issue
Block a user