feat: Add user statuses and cards

This commit is contained in:
2026-04-16 22:52:45 +02:00
parent b4ac0cdc92
commit 2927a86fbb
57 changed files with 1964 additions and 185 deletions

View File

@@ -3,7 +3,7 @@ import { type BrowserContext, type Page } from '@playwright/test';
const SERVER_ENDPOINTS_STORAGE_KEY = 'metoyou_server_endpoints';
const REMOVED_DEFAULT_KEYS_STORAGE_KEY = 'metoyou_removed_default_server_keys';
type SeededEndpointStorageState = {
interface SeededEndpointStorageState {
key: string;
removedKey: string;
endpoints: {
@@ -14,7 +14,7 @@ type SeededEndpointStorageState = {
isDefault: boolean;
status: string;
}[];
};
}
function buildSeededEndpointStorageState(
port: number = Number(process.env.TEST_SERVER_PORT) || 3099
@@ -40,7 +40,11 @@ function applySeededEndpointStorageState(storageState: SeededEndpointStorageStat
const storage = window.localStorage;
storage.setItem(storageState.key, JSON.stringify(storageState.endpoints));
storage.setItem(storageState.removedKey, JSON.stringify(['default', 'toju-primary', 'toju-sweden']));
storage.setItem(storageState.removedKey, JSON.stringify([
'default',
'toju-primary',
'toju-sweden'
]));
} catch {
// about:blank and some Playwright UI pages deny localStorage access.
}
@@ -59,7 +63,7 @@ export async function installTestServerEndpoint(
* Seed localStorage with a single signal endpoint pointing at the test server.
* Must be called AFTER navigating to the app origin (localStorage is per-origin)
* but BEFORE the app reads from storage (i.e. before the Angular bootstrap is
* relied upon calling it in the first goto() landing page is fine since the
* relied upon - calling it in the first goto() landing page is fine since the
* page will re-read on next navigation/reload).
*
* Typical usage:

View File

@@ -4,11 +4,11 @@ import {
type Page
} from '@playwright/test';
export type ChatDropFilePayload = {
export interface ChatDropFilePayload {
name: string;
mimeType: string;
base64: string;
};
}
export class ChatMessagesPage {
readonly composer: Locator;
@@ -115,7 +115,8 @@ export class ChatMessagesPage {
getEmbedCardByTitle(title: string): Locator {
return this.page.locator('app-chat-link-embed').filter({
has: this.page.getByText(title, { exact: true })
}).last();
})
.last();
}
async editOwnMessage(originalText: string, updatedText: string): Promise<void> {

View File

@@ -1,4 +1,8 @@
import { expect, type Page, type Locator } from '@playwright/test';
import {
expect,
type Page,
type Locator
} from '@playwright/test';
export class RegisterPage {
readonly usernameInput: Locator;

View File

@@ -13,7 +13,7 @@ export default defineConfig({
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'on-first-retry',
actionTimeout: 15_000,
actionTimeout: 15_000
},
projects: [
{
@@ -22,18 +22,15 @@ export default defineConfig({
...devices['Desktop Chrome'],
permissions: ['microphone', 'camera'],
launchOptions: {
args: [
'--use-fake-device-for-media-stream',
'--use-fake-ui-for-media-stream',
],
},
},
},
args: ['--use-fake-device-for-media-stream', '--use-fake-ui-for-media-stream']
}
}
}
],
webServer: {
command: 'cd ../toju-app && npx ng serve',
port: 4200,
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},
timeout: 120_000
}
});

View File

@@ -1,12 +1,13 @@
import { type Page } from '@playwright/test';
import { test, expect, type Client } from '../../fixtures/multi-client';
import {
test,
expect,
type Client
} from '../../fixtures/multi-client';
import { RegisterPage } from '../../pages/register.page';
import { ServerSearchPage } from '../../pages/server-search.page';
import { ChatRoomPage } from '../../pages/chat-room.page';
import {
ChatMessagesPage,
type ChatDropFilePayload
} from '../../pages/chat-messages.page';
import { ChatMessagesPage, type ChatDropFilePayload } from '../../pages/chat-messages.page';
const MOCK_EMBED_URL = 'https://example.test/mock-embed';
const MOCK_EMBED_TITLE = 'Mock Embed Title';
@@ -133,14 +134,14 @@ test.describe('Chat messaging features', () => {
});
});
type ChatScenario = {
interface ChatScenario {
alice: Client;
bob: Client;
aliceRoom: ChatRoomPage;
bobRoom: ChatRoomPage;
aliceMessages: ChatMessagesPage;
bobMessages: ChatMessagesPage;
};
}
async function createChatScenario(createClient: () => Promise<Client>): Promise<ChatScenario> {
const suffix = uniqueName('chat');
@@ -170,6 +171,7 @@ async function createChatScenario(createClient: () => Promise<Client>): Promise<
aliceCredentials.displayName,
aliceCredentials.password
);
await expect(alice.page).toHaveURL(/\/search/, { timeout: 15_000 });
await bobRegisterPage.goto();
@@ -178,6 +180,7 @@ async function createChatScenario(createClient: () => Promise<Client>): Promise<
bobCredentials.displayName,
bobCredentials.password
);
await expect(bob.page).toHaveURL(/\/search/, { timeout: 15_000 });
const aliceSearchPage = new ServerSearchPage(alice.page);
@@ -185,6 +188,7 @@ async function createChatScenario(createClient: () => Promise<Client>): Promise<
await aliceSearchPage.createServer(serverName, {
description: 'E2E chat server for messaging feature coverage'
});
await expect(alice.page).toHaveURL(/\/room\//, { timeout: 15_000 });
const bobSearchPage = new ServerSearchPage(bob.page);
@@ -259,6 +263,7 @@ async function installChatFeatureMocks(page: Page): Promise<void> {
siteName: 'Mock Docs'
})
});
return;
}
@@ -291,5 +296,6 @@ function buildMockSvgMarkup(label: string): string {
}
function uniqueName(prefix: string): string {
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
return `${prefix}-${Date.now()}-${Math.random().toString(36)
.slice(2, 8)}`;
}