Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions resources/js/react/layouts/AuthCardLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@ export default function AuthCardLayout({
<CardDescription>{description}</CardDescription>
</CardHeader>
<CardContent className="px-8">
{(status || error) && (
{status || error ? (
<div data-testid="alert">
<AlertMessage
message={status || error}
variant={status ? 'success' : 'error'}
/>
</div>
)}
) : null}
{children}
</CardContent>
</Card>
Expand Down
116 changes: 116 additions & 0 deletions tests/e2e/tests/sidebar.spec.ts
Original file line number Diff line number Diff line change
@@ -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());

Comment on lines +56 to +58
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');
Comment on lines +110 to +114
});
});
Loading