From 14d5304b1010b152a7594cde898f9b64a9943b90 Mon Sep 17 00:00:00 2001 From: Dominik Date: Thu, 20 Aug 2026 09:54:19 +0200 Subject: [PATCH] fix(skypic): align auth UI and browser mock --- README.md | 5 + .../components/account/AppProfileAuth.test.ts | 80 ++++++++++++++++ .../src/components/account/AppProfileAuth.vue | 32 +++++-- frontend/src/skypic.backend.contract.test.ts | 4 +- .../src/views/apps/SkyPicApp.contract.test.ts | 3 + frontend/src/views/apps/SkyPicApp.vue | 3 + frontend/testserver/index.cjs | 77 +++++++++++---- frontend/testserver/smoke.cjs | 95 +++++++++++++++++++ 8 files changed, 272 insertions(+), 27 deletions(-) create mode 100644 frontend/src/components/account/AppProfileAuth.test.ts diff --git a/README.md b/README.md index 8a0af49..c33237c 100644 --- a/README.md +++ b/README.md @@ -577,6 +577,11 @@ pnpm install pnpm dev ``` +The browser mock links `demo@ifruit.com` and signs in the seeded SkyPic profile +`@alexm` by default. To exercise SkyPic registration with an empty profile, open +`http://localhost:5174/?testScenario=skypic-onboarding#/apps/skypic` while the +development server is running. + Create a production frontend build with: ```powershell diff --git a/frontend/src/components/account/AppProfileAuth.test.ts b/frontend/src/components/account/AppProfileAuth.test.ts new file mode 100644 index 0000000..e7fce26 --- /dev/null +++ b/frontend/src/components/account/AppProfileAuth.test.ts @@ -0,0 +1,80 @@ +import { readFileSync } from 'node:fs' + +import { createSSRApp, h } from 'vue' +import { renderToString } from 'vue/server-renderer' +import { describe, expect, it } from 'vitest' + +import AppProfileAuth from './AppProfileAuth.vue' + +const source = readFileSync( + new URL('./AppProfileAuth.vue', import.meta.url), + 'utf8', +) + +async function renderAuth( + mode: 'login' | 'register', + movingModeHighlight = true, +): Promise { + return renderToString( + createSSRApp({ + render: () => + h(AppProfileAuth, { + avatarUrl: null, + body: 'Use the linked account.', + cameraLabel: 'Camera', + email: 'demo@ifruit.com', + emailLabel: 'SkyPic account', + error: '', + eyebrow: 'Your SkyPic account', + galleryLabel: 'Photos', + loginLabel: 'Continue to SkyPic', + loginModeLabel: 'Login', + mode, + movingModeHighlight, + pending: false, + registerLabel: 'Create profile', + registerModeLabel: 'Register', + title: 'Welcome back', + username: mode === 'login' ? 'alexm' : 'newprofile', + usernameLabel: 'Handle', + }), + }), + ) +} + +describe('AppProfileAuth', () => { + it('uses the rounded Sky UI moving highlight for its mode switch', async () => { + const html = await renderAuth('login') + + expect(html).toContain('sky-segmented--strong') + expect(html).toContain('sky-segmented--rounded') + expect(html).toContain('sky-segmented__highlight') + expect(html).toContain('app-profile-auth__mode--moving-highlight') + expect(html).toContain('width:calc(50% - 4px)') + expect(html).toContain('--sky-segmented-indicator-offset:calc(0% + 0px)') + expect(html).toContain('aria-label="Your SkyPic account"') + }) + + it('moves the highlight and keeps short mode labels separate from actions', async () => { + const html = await renderAuth('register') + + expect(html).toContain('--sky-segmented-indicator-offset:calc(100% + 4px)') + expect(html.match(/Login/g)).toHaveLength(1) + expect(html.match(/Register/g)).toHaveLength(1) + expect(html).toContain('Create profile') + expect(html).not.toContain('>Continue to SkyPic') + }) + + it('keeps the moving highlight opt-in for SkyPic', async () => { + const html = await renderAuth('login', false) + + expect(html).not.toContain('sky-segmented--strong') + expect(html).not.toContain('sky-segmented__highlight') + expect(html).toContain('app-profile-auth__mode-button--login') + expect(html).toContain('app-profile-auth__mode-button--active') + expect(source).toContain(':strong="movingModeHighlight"') + expect(source).toContain( + ':not(.app-profile-auth__mode--moving-highlight)', + ) + }) +}) diff --git a/frontend/src/components/account/AppProfileAuth.vue b/frontend/src/components/account/AppProfileAuth.vue index 794b889..c5fc888 100644 --- a/frontend/src/components/account/AppProfileAuth.vue +++ b/frontend/src/components/account/AppProfileAuth.vue @@ -30,14 +30,17 @@ const props = withDefaults( eyebrow: string galleryLabel: string loginLabel: string + loginModeLabel?: string maxUsernameLength?: number minUsernameLength?: number mode: 'login' | 'register' + movingModeHighlight?: boolean password?: string passwordLabel?: string passwordPlaceholder?: string pending: boolean registerLabel: string + registerModeLabel?: string requirePassword?: boolean submitEnabled?: boolean title: string @@ -62,10 +65,13 @@ const props = withDefaults( emailAsField: false, maxUsernameLength: 40, minUsernameLength: 2, + loginModeLabel: '', + movingModeHighlight: false, password: '', passwordLabel: 'Password', passwordPlaceholder: '', requirePassword: false, + registerModeLabel: '', showConfirmPassword: false, submitEnabled: undefined, usernameAutocomplete: 'username', @@ -101,6 +107,10 @@ const canSubmit = computed(() => { ) ) }) +const modeLoginLabel = computed(() => props.loginModeLabel || props.loginLabel) +const modeRegisterLabel = computed( + () => props.registerModeLabel || props.registerLabel, +)