Files
makelore/tests/e2e/message-timestamps.spec.ts
2026-09-29 20:40:09 +08:00

82 lines
4.4 KiB
TypeScript

import { test, expect } from '@playwright/test';
import { createServer, type ViteDevServer } from 'vite';
import { resolve } from 'node:path';
let server: ViteDevServer;
let baseUrl: string;
test.use({ headless: true, channel: process.env.MAKELORE_LAYOUT_BROWSER_CHANNEL, video: 'off' });
test.beforeAll(async () => {
server = await createServer({
configFile: false, cacheDir: 'node_modules/.vite-message-timestamps',
resolve: { alias: { '@': resolve('src') } }, esbuild: { jsx: 'automatic' },
server: { host: '127.0.0.1', port: 0 },
plugins: [{
name: 'isolated-message-timestamps', enforce: 'pre',
load(id) {
if (id.endsWith('/src/lib/coding-teacher.ts')) return `
export function teacherTopicsPath() { return '/fixture'; }
export function teacherConversationsPath(projectId) { return '/fixture/' + projectId + '/conversations'; }
export function legacyTopicBase(base) { return base; }
export const teacherApi = {
models: async () => ({ supported: true, models: [{ id: 'fixture' }] }),
conversation: async () => ({ topic: window.__timestampTopic, before: null }),
seenConversation: async () => window.__timestampTopic,
events: async () => ({ close() {}, addEventListener() {} }),
};
`;
if (id.endsWith('/src/lib/host-events.ts')) return 'export function subscribeHostEvent() { return () => {}; }';
if (id.endsWith('/src/lib/host-api.ts')) return `
export async function createHostEventSource() { return { close() {}, addEventListener() {} }; }
export async function ensureHostApiToken() { return 'fixture'; }
export function getHostApiBase() { return ''; }
export function reportDesktopActivity() {}
export function setDesktopBackgroundLease() {}
export async function hostApiFetch() { return {}; }
export async function hostApiFetchBytes() { return new Uint8Array(); }
`;
},
configureServer(vite) {
vite.middlewares.use((req, res, next) => {
if (req.url !== '/' && !req.url?.startsWith('/?')) return next();
res.setHeader('Content-Type', 'text/html');
res.end('<html><head><meta charset="utf-8"></head><body><div id="root"></div><script type="module" src="/tests/e2e/fixtures/message-timestamps.tsx"></script></body></html>');
});
},
}],
});
await server.listen();
baseUrl = server.resolvedUrls!.local[0];
});
test.afterAll(async () => { await server?.close(); });
test.beforeEach(async ({ page }) => {
page.on('pageerror', error => { throw error; });
await page.addInitScript(() => {
const scope = window as typeof window & { __communityCalls: unknown[][] };
scope.__communityCalls = [];
Object.defineProperty(window, 'electron', { value: { ipcRenderer: {
invoke: async (...args: unknown[]) => { scope.__communityCalls.push(args); return undefined; },
} } });
});
});
for (const width of [375, 1280]) {
test(`both chat surfaces show stable local times at ${width}px`, async ({ page }) => {
await page.setViewportSize({ width, height: 900 });
await page.goto(baseUrl);
await expect(page.locator('time')).toHaveCount(4);
const times = await page.locator('time').evaluateAll(elements => elements.map(element => ({ time: element.getAttribute('datetime'), title: element.getAttribute('title') })));
expect(times.map(item => item.time)).toEqual(['2026-09-28T08:01:02.000Z', '2026-09-28T08:01:42.000Z', '2026-09-28T08:01:02.000Z', '2026-09-28T08:01:42.000Z']);
for (const item of times) expect(item.title).toMatch(/^2026年9月28日 \d{2}:\d{2}:(02|42)$/);
for (const time of await page.locator('time').all()) {
await expect(time).toHaveCSS('font-size', '11px');
expect(await time.evaluate(el => el.scrollWidth <= el.clientWidth)).toBe(true);
}
await expect(page.getByRole('button', { name: '我想让它陪我讲故事。' })).toBeEnabled();
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(width);
await page.screenshot({ path: test.info().outputPath(`message-timestamps-${width}.png`), fullPage: true });
await page.reload();
await expect(page.locator('time')).toHaveCount(4);
expect(await page.locator('time').evaluateAll(elements => elements.map(element => ({ time: element.getAttribute('datetime'), title: element.getAttribute('title') })))).toEqual(times);
});
}