Files
opengroupware/tests/e2e/board.spec.ts
Ken Yasue 2017585f84 feat(m6): board with threads/comments, markdown, tests, e2e
- BoardRepository (thread/comment CRUD, soft-delete, search, pagination,
  pinned-first ordering) + BoardService (membership permission,
  author/admin edit/delete, comment->board_commented notification,
  board_posted/comment_added activity logs, category validation)
- APIs: threads list/create/detail/edit/delete, comments create/edit/delete
- MarkdownBody (react-markdown + remark-gfm + rehype-sanitize),
  ThreadForm, CommentForm, board list + thread detail screens, ProjectNav link
- Unit tests (BoardRepository, BoardService) + e2e board.spec
2026-06-25 01:28:08 +02:00

81 lines
2.9 KiB
TypeScript

import { test, expect } from '@playwright/test';
function unique(prefix: string): string {
return `${prefix}-${Date.now()}-${Math.floor(Math.random() * 1_000_000)}`;
}
async function setupOwner(page: import('@playwright/test').Page) {
const email = unique('owner') + '@example.com';
await page.goto('/login');
await page.getByRole('button', { name: '新規登録はこちら' }).click();
await page.getByLabel('表示名').fill('Owner');
await page.getByLabel('メールアドレス').fill(email);
await page.getByLabel('パスワード').fill('password123');
await page.getByRole('button', { name: '登録する' }).click();
await expect(page).toHaveURL(/\/dashboard/);
await page.getByLabel('プロジェクト名').fill(unique('Proj'));
await page.getByRole('button', { name: '新規プロジェクト' }).click();
await expect(page).toHaveURL(/\/projects\/\d+$/);
return Number(page.url().match(/\/projects\/(\d+)/)![1]);
}
test.describe('board', () => {
test('create thread, view detail, comment, and search', async ({ page }) => {
const projectId = await setupOwner(page);
const title = unique('Thread');
// スレッド作成(API)
const res = await page.request.post(
`/api/projects/${projectId}/board/threads`,
{
data: {
title,
bodyMd: '# Hello\n\nthis is **markdown**',
category: 'notice',
},
}
);
expect(res.ok()).toBeTruthy();
const { thread } = (await res.json()) as { thread: { id: number } };
// 一覧に表示される
await page.goto(`/projects/${projectId}/board`);
await expect(
page.getByRole('link', { name: new RegExp(title) })
).toBeVisible();
// 詳細: Markdown本文が表示される
await page.goto(`/projects/${projectId}/board/${thread.id}`);
await expect(
page.getByRole('heading', { name: 'Hello', level: 1 })
).toBeVisible();
await expect(page.getByText('markdown')).toBeVisible();
// コメント追加(API) → 詳細に表示される
const commentRes = await page.request.post(
`/api/projects/${projectId}/board/threads/${thread.id}/comments`,
{ data: { bodyMd: 'first comment' } }
);
expect(commentRes.ok()).toBeTruthy();
await page.reload();
await expect(page.getByText('first comment')).toBeVisible();
// 検索: 別スレッドを作成し、q で絞り込める
const otherTitle = unique('ZZZ');
await page.request.post(`/api/projects/${projectId}/board/threads`, {
data: { title: otherTitle, bodyMd: 'body', category: 'question' },
});
await page.goto(
`/projects/${projectId}/board?q=${encodeURIComponent(otherTitle)}`
);
await expect(
page.getByRole('link', { name: new RegExp(otherTitle) })
).toBeVisible();
// 前のスレッドは検索結果に含まれない
await expect(
page.getByRole('link', { name: new RegExp(title) })
).toHaveCount(0);
});
});