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
5 changes: 5 additions & 0 deletions .changeset/room-nav-mobile-options-sheet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
default: patch
---

Keep the mobile room options sheet open instead of dismissing it as soon as the pointer leaves the room.
6 changes: 6 additions & 0 deletions oxlint.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,11 @@ export default defineConfig({
'typescript/no-unsafe-enum-comparison': 'off',
},
},
{
files: ['tests/e2e/**'],
rules: {
'react/rules-of-hooks': 'off',
},
},
],
});
10 changes: 5 additions & 5 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ export default defineConfig({
snapshotPathTemplate: 'tests/e2e/__screenshots__/{projectName}/{testFileName}/{arg}{ext}',
fullyParallel: false,
workers: 1,
retries: 2,
retries: process.env.CI ? 2 : 0,
reporter: [['html', { open: 'never' }], ['list']],
globalSetup: './tests/e2e/global-setup.ts',
timeout: 240_000,
timeout: 60_000,
expect: {
toHaveScreenshot: { maxDiffPixelRatio: 0.01 },
},
Expand All @@ -29,10 +29,10 @@ export default defineConfig({
},
],
webServer: {
command: 'pnpm dev',
command: 'pnpm run build && pnpm exec vite preview --port 8080 --strictPort',
url: 'http://localhost:8080',
reuseExistingServer: !process.env.CI,
timeout: 240_000,
reuseExistingServer: false,
timeout: 180_000,
env: { NODE_OPTIONS: '--max-old-space-size=8192' },
},
});
2 changes: 1 addition & 1 deletion src/app/features/room-nav/RoomNavItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ export function RoomNavItem({
navigate(linkPath);
};

const optionsVisible = hover || !!menuAnchor;
const optionsVisible = hover || !!menuAnchor || isMobileMenuOpen;
const isMutedRoom = notificationMode === RoomNotificationMode.Mute;
const shouldShowUnreadIndicator = !isMutedRoom && (!!unread || hasRoomUnread);

Expand Down
16 changes: 16 additions & 0 deletions tests/e2e/fixtures/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { test as base } from '@playwright/test';
import { AppShell } from '../pages/AppShell';

type Fixtures = {
app: AppShell;
};

export const test = base.extend<Fixtures>({
app: async ({ page }, use) => {
const app = new AppShell(page);
await app.open();
await use(app);
},
});

export { expect } from '@playwright/test';
8 changes: 8 additions & 0 deletions tests/e2e/leave-room-prompt.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { test, expect } from './fixtures/test';

test('leave room prompt opens from the room options menu', async ({ app }) => {
const menu = await app.openRoomOptions('General');
await menu.leaveRoom();

await expect(app.leaveRoomPrompt).toBeVisible();
});
46 changes: 46 additions & 0 deletions tests/e2e/pages/AppShell.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { Locator, Page } from '@playwright/test';
import { expect } from '@playwright/test';

export const CLIENT_READY_TIMEOUT = 30_000;

export class AppShell {
readonly leaveRoomPrompt: Locator;

readonly createRoomButton: Locator;

constructor(readonly page: Page) {
this.leaveRoomPrompt = page.getByText('Are you sure you want to leave this room?');
this.createRoomButton = page.getByRole('button', { name: 'Create Room' }).first();
}

async open(): Promise<void> {
await this.page.goto('/');
await expect(this.room('General')).toBeVisible({ timeout: CLIENT_READY_TIMEOUT });
await this.dismissDeviceBanner();
}

room(name: string): Locator {
return this.page.getByText(name).first();
}

async dismissDeviceBanner(): Promise<void> {
await this.page
.getByRole('button', { name: 'Dismiss' })
.click({ timeout: 5_000 })
.catch(() => undefined);
}

async openRoomOptions(name: string): Promise<RoomOptionsMenu> {
await this.room(name).hover();
await this.page.getByRole('button', { name: 'More Options' }).first().click();
return new RoomOptionsMenu(this.page);
}
}

export class RoomOptionsMenu {
constructor(readonly page: Page) {}

async leaveRoom(): Promise<void> {
await this.page.getByRole('button', { name: 'Leave Room' }).click();
}
}
33 changes: 10 additions & 23 deletions tests/e2e/shell.spec.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,28 @@
import { test, expect, type Page } from '@playwright/test';
import type { Page } from '@playwright/test';
import { test, expect } from './fixtures/test';

const containerised = Boolean(process.env.PW_TEST_CONNECT_WS_ENDPOINT);

async function dismissDeviceBanner(page: Page): Promise<void> {
await page
.getByRole('button', { name: 'Dismiss' })
.click({ timeout: 5_000 })
.catch(() => undefined);
}

const maskDynamic = (page: Page) => ({
mask: [page.locator('time'), page.getByText(/^Created by /)],
});

test.describe('app shell', () => {
test('shows seeded rooms after login', async ({ page }) => {
await page.goto('/');

await expect(page.getByText('General').first()).toBeVisible({ timeout: 180_000 });
await expect(page.getByText('Random').first()).toBeVisible();
test('shows seeded rooms after login', async ({ app }) => {
await expect(app.room('General')).toBeVisible();
await expect(app.room('Random')).toBeVisible();
});

test('opens a room and renders its timeline', async ({ page }) => {
await page.goto('/');
await dismissDeviceBanner(page);
test('opens a room and renders its timeline', async ({ app, page }) => {
await app.room('General').click();

await page.getByText('General').first().click();
await expect(page.getByText('Welcome to the test room.')).toBeVisible({ timeout: 180_000 });
await expect(page.getByText('Welcome to the test room.')).toBeVisible();
await expect(page.getByText('Layout baseline seed message.')).toBeVisible();
});

test('matches the shell layout baseline', async ({ page }) => {
test('matches the shell layout baseline', async ({ app, page }) => {
test.skip(!containerised, 'run via pnpm test:e2e:docker');

await page.goto('/');
await expect(page.getByText('General').first()).toBeVisible({ timeout: 180_000 });
await dismissDeviceBanner(page);
await expect(app.room('General')).toBeVisible();

await page.evaluate(async () => {
await document.fonts.ready;
Expand Down
Loading