fix: language buttons real design / conformance
This commit is contained in:
1 parent
2c8f39273c
commit
0490a4c494
23 files changed
+1408
-412
No files matched your search
@@ -0,0 +1,172 @@
|
||||
Feature: Language switcher conforms to the official design
|
||||
|
||||
Background:
|
||||
Given the design in docs/design is the visual spec of record for LanguageSwitcher and UserMenu
|
||||
And the interface supports exactly two languages, English and Français
|
||||
And each language is always written in its own name, whatever the interface language
|
||||
And the language rules of features/spa_internationalization.feature still apply
|
||||
And the language control no longer uses a form select anywhere
|
||||
And no language preference is sent to the server
|
||||
|
||||
Scenario Outline: Account menu lists Profile, Theme, Language and Log out in design order
|
||||
Given a signed-in user opens the account menu in the <surface>
|
||||
Then the entries appear in this order
|
||||
| Profile |
|
||||
| separator |
|
||||
| "Theme" section label |
|
||||
| System, Light, Dark |
|
||||
| separator |
|
||||
| "Language" section label |
|
||||
| English, Français |
|
||||
| separator |
|
||||
| Log out |
|
||||
And the "Theme" and "Language" labels are 11 px, weight 500, uppercase, letter-spacing 0.04em and --ui-text-muted
|
||||
And the label padding is <label_padding>
|
||||
And the menu is <size>
|
||||
And every item row is <row_height> high
|
||||
|
||||
Examples:
|
||||
| surface | label_padding | size | row_height |
|
||||
| desktop UserMenu popover | 6px 8px 2px | a 224 px popover above the chip | 32 px |
|
||||
| mobile AccountMenuSheet | 8px 16px 2px | a bottom sheet with grab handle | 48 px |
|
||||
|
||||
Scenario Outline: Language rows are a radio group naming each language in its own name
|
||||
Given the interface language is <locale>
|
||||
When the account menu is open in the <surface>
|
||||
Then the "Language" label names a group of two menuitemradio rows
|
||||
And the rows read "English" with lang "en" and "Français" with lang "fr"
|
||||
And the row for <locale_name> has aria-checked "true" and a check icon at the right edge
|
||||
And the other row has aria-checked "false" and no check icon
|
||||
And no Nuxt UI select, combobox or languages icon row is present in the menu
|
||||
|
||||
Examples:
|
||||
| surface | locale | locale_name |
|
||||
| desktop UserMenu popover | en | English |
|
||||
| desktop UserMenu popover | fr | Français |
|
||||
| mobile AccountMenuSheet | en | English |
|
||||
| mobile AccountMenuSheet | fr | Français |
|
||||
|
||||
Scenario Outline: Theme rows are radio items that keep the menu open
|
||||
Given the theme preference is <preference>
|
||||
When the account menu is open in the <surface>
|
||||
Then System, Light and Dark are menuitemradio rows in a group named "Theme"
|
||||
And only the <preference> row has aria-checked "true" and a check icon
|
||||
When the user chooses another theme row
|
||||
Then the theme applies at once and the menu stays open
|
||||
|
||||
Examples:
|
||||
| surface | preference |
|
||||
| desktop UserMenu popover | light |
|
||||
| mobile AccountMenuSheet | dark |
|
||||
|
||||
Scenario Outline: Choosing a language in the account menu applies it at once and keeps the menu open
|
||||
Given the interface language is en and the user is on /profile with an unsaved display name "Alice B."
|
||||
And the account menu is open in the <surface>
|
||||
When the user chooses "Français"
|
||||
Then all interface text, including the menu labels "Thème" and "Langue", is French
|
||||
And the document lang is "fr" and localStorage "renewcms-language" is "fr"
|
||||
And the Français row is checked and the menu stays open
|
||||
And the route, session, theme and unsaved display name are unchanged
|
||||
And no reload, navigation or /v1 request occurs
|
||||
When the user chooses "English"
|
||||
Then the interface is English, the document lang is "en" and the menu stays open
|
||||
|
||||
Examples:
|
||||
| surface |
|
||||
| desktop UserMenu popover |
|
||||
| mobile AccountMenuSheet |
|
||||
|
||||
Scenario Outline: Account menu keyboard and dismissal
|
||||
Given the account menu is open in the <surface>
|
||||
When the user moves with ArrowDown and ArrowUp
|
||||
Then focus visits Profile, the theme rows, the language rows and Log out in visual order and wraps at the ends
|
||||
When the user presses Enter or Space on a language row
|
||||
Then that language is applied and the menu stays open
|
||||
When the user presses Escape or clicks outside
|
||||
Then the menu closes without changing the language
|
||||
|
||||
Examples:
|
||||
| surface |
|
||||
| desktop UserMenu popover |
|
||||
| mobile AccountMenuSheet |
|
||||
|
||||
Scenario Outline: Auth footer shows the language chip beside the theme chip
|
||||
Given the user is signed out on <route>
|
||||
And the interface language is en
|
||||
Then the footer outside the card contains the language chip and the System theme chip aligned right with an 8 px gap
|
||||
And any existing footer slot content stays to the left of the chips
|
||||
And the chip shows the lucide languages icon at 14 px followed by "English"
|
||||
And the chip has a 1 px --ui-border-accented border, --ui-text-toned text, aria-haspopup "menu", aria-expanded "false" and aria-label "Language: English"
|
||||
And no language select is rendered in the card or the footer
|
||||
|
||||
Examples:
|
||||
| route |
|
||||
| /login |
|
||||
| /register |
|
||||
| /register/pending |
|
||||
| /register/validate |
|
||||
| /register/reset/request |
|
||||
| /register/reset/validate |
|
||||
| /invite/accept |
|
||||
|
||||
Scenario Outline: Auth footer is always rendered, on mobile too
|
||||
Given the viewport is <viewport>
|
||||
When an auth screen with no footer slot content renders
|
||||
Then the footer with the language chip and the theme chip is visible
|
||||
And the chip is <chip_height> high
|
||||
|
||||
Examples:
|
||||
| viewport | chip_height |
|
||||
| 1440 x 900 | 28 px |
|
||||
| 390 x 844 | 44 px |
|
||||
|
||||
Scenario: Chip reflects the current language
|
||||
Given the stored language is fr
|
||||
When /login renders
|
||||
Then the chip reads "Français" and its aria-label is "Langue : Français"
|
||||
|
||||
Scenario Outline: Auth language popover
|
||||
Given the user is signed out on /login in <locale_name>
|
||||
When the user activates the language chip
|
||||
Then the chip has aria-expanded "true" and --ui-bg-elevated
|
||||
And a popover with role "menu" named "Language" opens above the chip, right-aligned and 36 px above its bottom edge, 168 px wide, --ui-bg, 1 px --ui-border, --rc-shadow-pop and 4 px padding
|
||||
And it holds menuitemradio rows "English" (lang "en") and "Français" (lang "fr") that are 32 px high on desktop and 44 px on mobile
|
||||
And the current language row has aria-checked "true" and a 16 px check icon at the right edge
|
||||
|
||||
Examples:
|
||||
| locale_name |
|
||||
| English |
|
||||
| Français |
|
||||
|
||||
Scenario: Choosing a language in the auth popover closes it
|
||||
Given the auth language popover is open on /login with English current
|
||||
When the user chooses "Français"
|
||||
Then the whole screen is French at once, including the card, the footer and the document title
|
||||
And the document lang is "fr" and localStorage "renewcms-language" is "fr"
|
||||
And the popover closes and focus returns to the language chip
|
||||
And the chip reads "Français"
|
||||
And the form values already typed, the route and the theme are unchanged
|
||||
And no /v1 request occurs
|
||||
|
||||
Scenario: Dismiss the auth popover without changing language
|
||||
Given the auth language popover is open
|
||||
When the user presses Escape or clicks outside it
|
||||
Then it closes, aria-expanded is "false" and the language is unchanged
|
||||
And focus returns to the chip on Escape
|
||||
|
||||
Scenario: Auth popover keyboard
|
||||
Given the language chip has focus
|
||||
When the user presses Enter, Space or ArrowDown
|
||||
Then the popover opens with focus on the current language row
|
||||
And ArrowUp and ArrowDown move between the two rows and Enter or Space chooses one
|
||||
|
||||
Scenario: Language persists across public and signed-in screens
|
||||
Given the user chose Français on /login
|
||||
When they sign in, open the account menu and log out
|
||||
Then Français is checked in the account menu and the /login chip reads "Français" again
|
||||
And a storage failure still lets the choice apply for the current session, as in spa_internationalization
|
||||
|
||||
Scenario: Catalog parity for the new strings
|
||||
Then the en and fr catalogs contain "Theme" and "Language: {0}"
|
||||
And "Language: {0}" keeps its {0} parameter in French
|
||||
And the strings "English" and "Français" are not translated
|
||||
@@ -0,0 +1,35 @@
|
||||
# Language switcher conformance QA
|
||||
|
||||
Accepted by the user on 2026-10-04 together with the slice specification. Decisions folded in: spa_internationalization picker scenarios are amended; theme and language rows are menuitemradio and the account menu and sheet stay open after a choice; the auth popover is specified from docs/design/components.md because the boards draw only the chip.
|
||||
|
||||
## Automated (Vitest + Vue Test Utils, `task test:web`)
|
||||
|
||||
Mock `/v1` per case and assert that a language change makes no `/v1` request. Reset the stored `renewcms-language` between tests. Do not freeze the system date.
|
||||
|
||||
1. UserMenu (desktop) and AccountMenuSheet (mobile): open the menu and assert the entry order Profile, separator, "Theme" label, System/Light/Dark, separator, "Language" label, English/Français, separator, Log out. Assert the label classes. LanguagePicker and any combobox must be absent.
|
||||
2. Roles and ARIA: theme rows and language rows are menuitemradio with aria-checked on the current one and a check icon. Language rows carry lang "en"/"fr" and native names. With locale fr the names stay "English"/"Français" while "Theme"/"Language" become "Thème"/"Langue".
|
||||
3. Account menu selection: choosing Français in each surface switches the visible copy, sets document lang and storage, keeps the menu open, and leaves route, theme and an unsaved display-name draft on /profile unchanged. Choosing English restores it. Choosing a theme keeps the mobile sheet open.
|
||||
4. Account menu keyboard: arrow order and wrap across all entries, Enter/Space applies and keeps open, Escape and outside click close with no language change.
|
||||
5. AuthShell: the footer is rendered with the language chip next to UColorModeSelect when belowSm is true and false, with no footer slot content, and slot content stays to the left. Assert aria-haspopup, aria-expanded, aria-label "Language: English" / "Langue : Français", the icon, the native name and the absence of a select.
|
||||
6. Auth popover: opens via click, Enter, Space and ArrowDown; role menu named "Language", two menuitemradio rows with lang and checked state; selection applies the language, closes it and returns focus to the chip; Escape and outside click dismiss with no change. Typed form values on /login survive the switch.
|
||||
7. Each auth screen (login, register, pending, verify, forgot-password, reset-password, accept-invite) mounts with the chip present.
|
||||
8. Persistence: stored fr gives chip "Français"; the choice survives log out and re-render of /login; a localStorage write failure still switches for the session.
|
||||
9. Catalog parity: en/fr keys include "Theme" and "Language: {0}" with matching parameters.
|
||||
10. Update web/src/i18n/spa.spec.ts, web/src/i18n/i18n.spec.ts and any AuthShell, UserMenu or AccountMenuSheet spec asserting menuitemcheckbox or the old picker. Remove LanguagePicker and its tests if the component is removed.
|
||||
11. Run task test:web and the type check; inspect the diff for introduced comments.
|
||||
|
||||
## Visual (`task visual:check -- <screen>`, hard gate at 10 %)
|
||||
|
||||
Desktop and mobile, light and dark, reusing the boards' sample data.
|
||||
|
||||
- profile, state "Account menu": board `deskMenu` and `mobMenu` with the Theme and Language labels, Light and English checked.
|
||||
- Every auth screen and state (log-in, register, pending, verify, forgot-password, reset-password, accept-invite): all captures change because of the language chip (`hasFooter: true`), including mobile where the footer is new. Clock-sensitive states stay `skip: 'clock-sensitive'`.
|
||||
- The boards draw no open auth language popover, so it has no visual gate. It is covered by the automated and manual checks.
|
||||
|
||||
## Manual (screenshots cannot cover)
|
||||
|
||||
- Keyboard focus order and visible focus ring in the account menu, the chip and the popover; focus return to the chip.
|
||||
- Hover, press and motion on the rows and the chip.
|
||||
- Popover placement above the chip and containment at 390 px width, including the 36 px offset next to the 44 px mobile chip. Touch targets at 44 px on mobile.
|
||||
- French readability of "Thème", "Langue" and "Langue : Français" in the labels, chip and popover.
|
||||
- Language retained after reload and logout.
|
||||
@@ -4,8 +4,7 @@ Feature: English and French throughout the SPA
|
||||
Given all existing SPA interface text supports English and French
|
||||
And English preserves existing copy and French provides equivalent meaning
|
||||
And user content, API data and transactional emails remain unchanged
|
||||
And visual checks are waived by the user for this slice
|
||||
And the language control uses existing design tokens
|
||||
And the language control follows features/language_switcher_conformance.feature
|
||||
|
||||
Scenario Outline: Resolve the language before rendering
|
||||
Given the browser-local stored language is <stored>
|
||||
@@ -21,12 +20,10 @@ Feature: English and French throughout the SPA
|
||||
| unsupported | en |
|
||||
| unreadable | en |
|
||||
|
||||
Scenario Outline: Open the language picker in the profile submenu
|
||||
Scenario Outline: Offer the language choice in the account menu
|
||||
Given the user opens the <menu>
|
||||
When they activate the localized Language control
|
||||
Then a compact scrollable picker offers English and Français
|
||||
Then a localized Language section offers English and Français as radio items
|
||||
And the selected language is visibly and accessibly identified
|
||||
And the picker uses Nuxt UI and existing design tokens
|
||||
|
||||
Examples:
|
||||
| menu |
|
||||
@@ -65,13 +62,12 @@ Feature: English and French throughout the SPA
|
||||
And an unavailable French message falls back to English
|
||||
And user content, account details, filenames, URLs, API data and emails remain unchanged
|
||||
|
||||
Scenario Outline: Operate the picker accessibly
|
||||
Given the picker is open in the <menu>
|
||||
When the user navigates its options with the keyboard and confirms a choice
|
||||
Then the language changes and the picker closes
|
||||
And focus returns to its language control
|
||||
When they reopen it and press Escape or click outside
|
||||
Then the picker closes without changing language
|
||||
Scenario Outline: Operate the language choice accessibly
|
||||
Given the <menu> is open
|
||||
When the user navigates the language items with the keyboard and confirms a choice
|
||||
Then the language changes and the menu stays open
|
||||
When they press Escape or click outside
|
||||
Then the menu closes without changing language
|
||||
|
||||
Examples:
|
||||
| menu |
|
||||
|
||||
@@ -5,7 +5,7 @@ Accepted by the user on 2026-10-03 together with the slice specification.
|
||||
## Automated
|
||||
|
||||
1. Verify absent, English, French, unsupported and unreadable stored preferences; English defaults independently of browser language and document lang follows selection.
|
||||
2. Verify desktop UserMenu and mobile AccountMenuSheet offer a localized language entry and a compact bounded scrollable picker with English and Français, selected-state semantics, selection, dismissal and focus return. Preserve profile, theme and logout behavior.
|
||||
2. Verify desktop UserMenu and mobile AccountMenuSheet offer a localized Language section with English and Français radio items, selected-state semantics, selection with the menu staying open, and dismissal. Preserve profile, theme and logout behavior. Details live in features/language_switcher_conformance.qa.md.
|
||||
3. Switch both ways while editing an unsaved profile display name. Verify translated UI and unchanged draft, route, session and theme without additional API requests.
|
||||
4. Verify browser-local persistence across reload initialization, navigation and logout/public authentication. Verify storage write failure allows switching for the current session.
|
||||
5. Preserve existing English tests and add French checks for login, validation, profile save failure, integration copy feedback, not-found, representative dialogs and notifications.
|
||||
@@ -16,6 +16,4 @@ Accepted by the user on 2026-10-03 together with the slice specification.
|
||||
|
||||
## Visual and manual
|
||||
|
||||
Visual checks are SKIPPED by explicit user waiver for this slice because the designer is unavailable. The user authorized a coherent language control using existing design tokens. Do not edit design sources or claim pixel parity.
|
||||
|
||||
Manual checklist: desktop/mobile and light/dark coherence, viewport containment, constrained scrolling, French text readability, touch and mouse selection, keyboard movement and confirmation, Escape/outside dismissal, focus return and visible focus. Confirm language retention after reload and logout. Do not freeze dates or investigate clock-sensitive states.
|
||||
The language control now has an official design. Its visual and manual checks live in features/language_switcher_conformance.qa.md. For the rest of this slice, confirm French text readability and language retention after reload and logout. Do not freeze dates or investigate clock-sensitive states.
|
||||
Reference in new issue
Block a user