Files
opengroupware/tests/e2e/meetings.spec.ts
Ken Yasue 29e4bc6650 feat(m12): meetings + schedule conflict detection, tests, e2e
- MeetingRepository (CRUD, soft-delete, members, findMeetingsByUserInRange
  overlap) + CalendarRepository.findByCreatorInRange + TodoRepository.
  findHighPriorityByAssignee
- MeetingService (createMeeting tx+members, meeting_invited notify,
  meeting_created activity, SSE meeting.created; updateMeeting/updateMinutes/
  deleteMeeting; checkScheduleConflicts = other meetings overlap + calendar
  events overlap + high-priority todos due +-3d, time-overlap logic,
  excludeMeetingId; conflicts are warnings)
- APIs: meetings list/create/edit + /check conflict check
- Screens: meetings page + MeetingForm(member checkboxes, conflict display)
  + ConflictWarning, ProjectNav link
- Unit tests (MeetingRepository, MeetingService incl. conflict algorithm)
  + e2e meetings
2026-06-25 02:19:25 +02:00

78 lines
2.8 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('meetings & schedule conflict', () => {
test('create meetings and detect a schedule conflict for a member', async ({
page,
request,
}) => {
const projectId = await setupOwner(page);
const memberEmail = unique('member') + '@example.com';
// メンバー登録してプロジェクトに追加
await request.post('/api/auth/register', {
data: { name: 'Member', email: memberEmail, password: 'password123' },
});
const addRes = await page.request.post(
`/api/projects/${projectId}/members`,
{ data: { email: memberEmail, role: 'member' } }
);
const memberId = ((await addRes.json()) as { member: { userId: number } })
.member.userId;
// 1件目ミーティング(10:00-11:00)に member 参加
const m1 = await page.request.post(`/api/projects/${projectId}/meetings`, {
data: {
title: unique('M1'),
startAt: '2026-06-15T10:00:00',
endAt: '2026-06-15T11:00:00',
memberIds: [memberId],
},
});
expect(m1.ok()).toBeTruthy();
expect(((await m1.json()) as { conflicts: unknown[] }).conflicts).toEqual(
[]
);
// 2件目(10:30-11:30)は member にとって重複 → 警告が返る
const m2 = await page.request.post(`/api/projects/${projectId}/meetings`, {
data: {
title: unique('M2'),
startAt: '2026-06-15T10:30:00',
endAt: '2026-06-15T11:30:00',
memberIds: [memberId],
},
});
expect(m2.ok()).toBeTruthy();
const m2Body = (await m2.json()) as {
meeting: { id: number };
conflicts: { type: string }[];
};
expect(m2Body.conflicts.some((c) => c.type === 'meeting')).toBe(true);
// ミーティング一覧画面に両方表示
await page.goto(`/projects/${projectId}/meetings`);
await expect(
page.getByTestId(`meeting-${m2Body.meeting.id}`)
).toBeVisible();
});
});