diff --git a/resources/js/react/layouts/AuthCardLayout.tsx b/resources/js/react/layouts/AuthCardLayout.tsx index bd6d703..9c25cdf 100644 --- a/resources/js/react/layouts/AuthCardLayout.tsx +++ b/resources/js/react/layouts/AuthCardLayout.tsx @@ -47,14 +47,14 @@ export default function AuthCardLayout({ {description} - {(status || error) && ( + {status || error ? (
- )} + ) : null} {children}
diff --git a/tests/e2e/tests/sidebar.spec.ts b/tests/e2e/tests/sidebar.spec.ts new file mode 100644 index 0000000..b01110b --- /dev/null +++ b/tests/e2e/tests/sidebar.spec.ts @@ -0,0 +1,116 @@ +import { test, expect } from '@e2e/fixtures'; + +test.describe.parallel('Sidebar layout', () => { + test('renders tenant switcher in sidebar header', async ({ page, credentials, loginAs }) => { + await loginAs(credentials.user); + await page.goto('/dashboard'); + await expect(page).toHaveURL('/dashboard'); + + await expect(page.getByTestId('tenant-switcher')).toBeVisible(); + }); + + test('user dropdown contains language and theme selectors', async ({ page, credentials, loginAs }) => { + await loginAs(credentials.user); + await page.goto('/dashboard'); + + await page.getByTestId('user-menu-trigger').click(); + + await expect(page.getByTestId('language-selector-trigger')).toBeVisible(); + await expect(page.getByTestId('theme-selector-trigger')).toBeVisible(); + }); + + test('language selector submenu opens', async ({ page, credentials, loginAs }) => { + await loginAs(credentials.user); + await page.goto('/dashboard'); + + await page.getByTestId('user-menu-trigger').click(); + await page.getByTestId('language-selector-trigger').click(); + + await expect(page.locator('[data-slot="dropdown-menu-sub-content"]')).toBeVisible(); + }); + + test('theme selector submenu opens', async ({ page, credentials, loginAs }) => { + await loginAs(credentials.user); + await page.goto('/dashboard'); + + await page.getByTestId('user-menu-trigger').click(); + await page.getByTestId('theme-selector-trigger').click(); + + await expect(page.locator('[data-slot="dropdown-menu-sub-content"]')).toBeVisible(); + }); +}); + +test.describe('Theme persistence', () => { + test('dark theme persists across navigation via cookie', async ({ page, credentials, loginAs }) => { + await loginAs(credentials.user); + await page.goto('/dashboard'); + await expect(page).toHaveURL(/dashboard/); + + // Select dark via the UI — this writes localStorage + cookie + await page.getByTestId('user-menu-trigger').click(); + await page.getByTestId('theme-selector-trigger').click(); + await page.getByRole('menuitem', { name: 'Dark' }).click(); + + await expect(page.locator('html')).toHaveClass(/dark/); + + // Navigate to another page and back — server should render class="dark" from cookie + await page.goto(page.url()); + + await expect(page.locator('html')).toHaveClass(/dark/); + }); + + test('light theme does not add dark class after navigation', async ({ page, credentials, loginAs }) => { + await loginAs(credentials.user); + await page.goto('/dashboard'); + await expect(page).toHaveURL(/dashboard/); + + // Select light via the UI + await page.getByTestId('user-menu-trigger').click(); + await page.getByTestId('theme-selector-trigger').click(); + await page.getByRole('menuitem', { name: 'Light' }).click(); + + await expect(page.locator('html')).not.toHaveClass(/dark/); + + await page.goto(page.url()); + + await expect(page.locator('html')).not.toHaveClass(/dark/); + }); + + test('system preference dark mode applied before hydration', async ({ page, credentials, loginAs }) => { + await page.emulateMedia({ colorScheme: 'dark' }); + await loginAs(credentials.user); + await page.goto('/dashboard'); + + await expect(page.locator('html')).toHaveClass(/dark/); + }); + + test('dark mode toggle writes to appearance localStorage key, not vueuse-dark', async ({ page, credentials, loginAs }) => { + await loginAs(credentials.user); + await page.goto('/dashboard'); + + await page.getByTestId('user-menu-trigger').click(); + await page.getByTestId('theme-selector-trigger').click(); + await page.getByTestId('color-mode-dark').click(); + await page.waitForFunction(() => localStorage.getItem('appearance') === 'dark'); + + const stored = await page.evaluate(() => localStorage.getItem('appearance')); + expect(stored).toBe('dark'); + + const wrongKey = await page.evaluate(() => localStorage.getItem('vueuse-dark')); + expect(wrongKey).toBeNull(); + }); + + test('dark mode toggle writes appearance cookie for server-side persistence', async ({ page, credentials, loginAs }) => { + await loginAs(credentials.user); + await page.goto('/dashboard'); + + await page.getByTestId('user-menu-trigger').click(); + await page.getByTestId('theme-selector-trigger').click(); + await page.getByTestId('color-mode-dark').click(); + await page.waitForFunction(() => document.cookie.includes('appearance=dark')); + + const cookies = await page.context().cookies(); + const appearanceCookie = cookies.find((c) => c.name === 'appearance'); + expect(appearanceCookie?.value).toBe('dark'); + }); +});