feat(exam): Assessment dashboard + /exam-marker route; remove old CCExamMarker (S4-8)
- New examRepository (R2.1 seam): the only module talking to /api/exam (Supabase JWT → Bearer, axios to API_BASE). list/get/create/archive templates. - ExamDashboardPage (/exam-marker): lists institute templates, create dialog, archive; cards link to setup (S4-9). Wrapped in ErrorBoundary (R6.4). - exam.types.ts mirrors the API contract. - Dashboard 'Exam Marker' quick action (top-level discovery, R1.3/R6.1). Note: the in-canvas TeacherNavigation is unsuitable for a nav section (it's the worker prev/next tab bar) — flagged; used the dashboard entry instead. - R1.1 removal: deleted src/pages/tldraw/CCExamMarker/ (old 3-PDF viewer) and the now-dead CCExamMarkerPanel + its wiring in CCPanel/BasePanel (examMarkerProps was only ever passed by the deleted page). - Fixed pre-existing broken AppRoutes test (missing TimetableListPage mock export). Build green (vite); AppRoutes route tests pass; typecheck clean for new files. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
434643596e
commit
3afe95fd72
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"status": "failed",
|
||||
"failedTests": [
|
||||
"2c0ead3a44e83872f07a-f13a2ce7ca4c925d4768"
|
||||
]
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
# Instructions
|
||||
|
||||
- Following Playwright test failed.
|
||||
- Explain why, be concise, respect Playwright best practices.
|
||||
- Provide a snippet of code with the fix, if possible.
|
||||
|
||||
# Test info
|
||||
|
||||
- Name: canvas-mount.spec.js >> Canvas mount >> login and show lesson canvas within 5s
|
||||
- Location: src/__tests__/e2e/canvas-mount.spec.js:37:3
|
||||
|
||||
# Error details
|
||||
|
||||
```
|
||||
TimeoutError: page.waitForURL: Timeout 15000ms exceeded.
|
||||
=========================== logs ===========================
|
||||
waiting for navigation until "load"
|
||||
============================================================
|
||||
```
|
||||
|
||||
# Page snapshot
|
||||
|
||||
```yaml
|
||||
- generic [ref=e3]:
|
||||
- banner [ref=e4]:
|
||||
- generic [ref=e5]:
|
||||
- generic [ref=e6] [cursor=pointer]:
|
||||
- img [ref=e7]
|
||||
- text: Classroom Copilot
|
||||
- button "menu" [ref=e10] [cursor=pointer]:
|
||||
- img [ref=e11]
|
||||
- main [ref=e13]:
|
||||
- generic [ref=e14]:
|
||||
- heading "ClassroomCopilot.ai" [level=1] [ref=e15]
|
||||
- heading "Login" [level=4] [ref=e16]
|
||||
- generic [ref=e18]:
|
||||
- generic [ref=e19]:
|
||||
- generic:
|
||||
- text: Email
|
||||
- generic: "*"
|
||||
- generic [ref=e20]:
|
||||
- textbox "Email" [ref=e21]
|
||||
- group:
|
||||
- generic: Email *
|
||||
- generic [ref=e22]:
|
||||
- generic:
|
||||
- text: Password
|
||||
- generic: "*"
|
||||
- generic [ref=e23]:
|
||||
- textbox "Password" [ref=e24]
|
||||
- group:
|
||||
- generic: Password *
|
||||
- button "Login" [ref=e25] [cursor=pointer]: Login
|
||||
```
|
||||
|
||||
# Test source
|
||||
|
||||
```ts
|
||||
1 | import { test, expect, request } from '@playwright/test';
|
||||
2 |
|
||||
3 | const BASE = process.env.PLAYWRIGHT_BASE_URL || 'http://192.168.0.251:13000';
|
||||
4 | const TEST_EMAIL = process.env.VITE_TEST_TEACHER_EMAIL;
|
||||
5 | const TEST_PASSWORD = process.env.VITE_TEST_TEACHER_PASSWORD;
|
||||
6 |
|
||||
7 | async function serverReachable() {
|
||||
8 | try {
|
||||
9 | const ctx = await request.newContext();
|
||||
10 | const res = await ctx.get(BASE, { timeout: 3000 }).catch(() => null);
|
||||
11 | await ctx.dispose();
|
||||
12 | return Boolean(res);
|
||||
13 | } catch {
|
||||
14 | return false;
|
||||
15 | }
|
||||
16 | }
|
||||
17 |
|
||||
18 | async function login(page) {
|
||||
19 | await page.goto(`${BASE}/login`);
|
||||
20 | await page.getByLabel('Email').fill(TEST_EMAIL);
|
||||
21 | await page.getByLabel('Password').fill(TEST_PASSWORD);
|
||||
22 | await page.getByRole('button', { name: 'Login' }).click();
|
||||
> 23 | await page.waitForURL((url) => /\/dashboard|\/node\//.test(url.pathname), { timeout: 15000 });
|
||||
| ^ TimeoutError: page.waitForURL: Timeout 15000ms exceeded.
|
||||
24 | }
|
||||
25 |
|
||||
26 | function skipIfUnconfigured() {
|
||||
27 | if (!TEST_EMAIL || !TEST_PASSWORD) test.skip(true, 'VITE_TEST_TEACHER_EMAIL / VITE_TEST_TEACHER_PASSWORD not set');
|
||||
28 | }
|
||||
29 |
|
||||
30 | test.describe('Canvas mount', () => {
|
||||
31 | test.beforeAll(async () => {
|
||||
32 | skipIfUnconfigured();
|
||||
33 | const reachable = await serverReachable();
|
||||
34 | test.skip(!reachable, `Dev server not reachable at ${BASE}`);
|
||||
35 | });
|
||||
36 |
|
||||
37 | test('login and show lesson canvas within 5s', async ({ page }) => {
|
||||
38 | await login(page);
|
||||
39 | const canvasVisible = await page
|
||||
40 | .locator('[data-testid="tldraw-canvas"], .tl-container')
|
||||
41 | .waitFor({ timeout: 10000 })
|
||||
42 | .then(() => true)
|
||||
43 | .catch(() => false);
|
||||
44 | expect(canvasVisible).toBe(true);
|
||||
45 | await expect(page.locator('[class*="error-state"], [data-testid="error"]').first()).toBeHidden({ timeout: 2000 }).catch(() => {});
|
||||
46 | });
|
||||
47 | });
|
||||
48 |
|
||||
```
|
||||
BIN
Binary file not shown.
+108
@@ -0,0 +1,108 @@
|
||||
# Instructions
|
||||
|
||||
- Following Playwright test failed.
|
||||
- Explain why, be concise, respect Playwright best practices.
|
||||
- Provide a snippet of code with the fix, if possible.
|
||||
|
||||
# Test info
|
||||
|
||||
- Name: canvas-mount.spec.js >> Canvas mount >> login and show lesson canvas within 5s
|
||||
- Location: src/__tests__/e2e/canvas-mount.spec.js:37:3
|
||||
|
||||
# Error details
|
||||
|
||||
```
|
||||
TimeoutError: page.waitForURL: Timeout 15000ms exceeded.
|
||||
=========================== logs ===========================
|
||||
waiting for navigation until "load"
|
||||
============================================================
|
||||
```
|
||||
|
||||
# Page snapshot
|
||||
|
||||
```yaml
|
||||
- generic [ref=e3]:
|
||||
- banner [ref=e4]:
|
||||
- generic [ref=e5]:
|
||||
- generic [ref=e6] [cursor=pointer]:
|
||||
- img [ref=e7]
|
||||
- text: Classroom Copilot
|
||||
- button "menu" [ref=e10] [cursor=pointer]:
|
||||
- img [ref=e11]
|
||||
- main [ref=e13]:
|
||||
- generic [ref=e14]:
|
||||
- heading "ClassroomCopilot.ai" [level=1] [ref=e15]
|
||||
- heading "Login" [level=4] [ref=e16]
|
||||
- generic [ref=e18]:
|
||||
- generic [ref=e19]:
|
||||
- generic:
|
||||
- text: Email
|
||||
- generic: "*"
|
||||
- generic [ref=e20]:
|
||||
- textbox "Email" [ref=e21]
|
||||
- group:
|
||||
- generic: Email *
|
||||
- generic [ref=e22]:
|
||||
- generic:
|
||||
- text: Password
|
||||
- generic: "*"
|
||||
- generic [ref=e23]:
|
||||
- textbox "Password" [ref=e24]
|
||||
- group:
|
||||
- generic: Password *
|
||||
- button "Login" [ref=e25] [cursor=pointer]: Login
|
||||
```
|
||||
|
||||
# Test source
|
||||
|
||||
```ts
|
||||
1 | import { test, expect, request } from '@playwright/test';
|
||||
2 |
|
||||
3 | const BASE = process.env.PLAYWRIGHT_BASE_URL || 'http://192.168.0.251:13000';
|
||||
4 | const TEST_EMAIL = process.env.VITE_TEST_TEACHER_EMAIL;
|
||||
5 | const TEST_PASSWORD = process.env.VITE_TEST_TEACHER_PASSWORD;
|
||||
6 |
|
||||
7 | async function serverReachable() {
|
||||
8 | try {
|
||||
9 | const ctx = await request.newContext();
|
||||
10 | const res = await ctx.get(BASE, { timeout: 3000 }).catch(() => null);
|
||||
11 | await ctx.dispose();
|
||||
12 | return Boolean(res);
|
||||
13 | } catch {
|
||||
14 | return false;
|
||||
15 | }
|
||||
16 | }
|
||||
17 |
|
||||
18 | async function login(page) {
|
||||
19 | await page.goto(`${BASE}/login`);
|
||||
20 | await page.getByLabel('Email').fill(TEST_EMAIL);
|
||||
21 | await page.getByLabel('Password').fill(TEST_PASSWORD);
|
||||
22 | await page.getByRole('button', { name: 'Login' }).click();
|
||||
> 23 | await page.waitForURL((url) => /\/dashboard|\/node\//.test(url.pathname), { timeout: 15000 });
|
||||
| ^ TimeoutError: page.waitForURL: Timeout 15000ms exceeded.
|
||||
24 | }
|
||||
25 |
|
||||
26 | function skipIfUnconfigured() {
|
||||
27 | if (!TEST_EMAIL || !TEST_PASSWORD) test.skip(true, 'VITE_TEST_TEACHER_EMAIL / VITE_TEST_TEACHER_PASSWORD not set');
|
||||
28 | }
|
||||
29 |
|
||||
30 | test.describe('Canvas mount', () => {
|
||||
31 | test.beforeAll(async () => {
|
||||
32 | skipIfUnconfigured();
|
||||
33 | const reachable = await serverReachable();
|
||||
34 | test.skip(!reachable, `Dev server not reachable at ${BASE}`);
|
||||
35 | });
|
||||
36 |
|
||||
37 | test('login and show lesson canvas within 5s', async ({ page }) => {
|
||||
38 | await login(page);
|
||||
39 | const canvasVisible = await page
|
||||
40 | .locator('[data-testid="tldraw-canvas"], .tl-container')
|
||||
41 | .waitFor({ timeout: 10000 })
|
||||
42 | .then(() => true)
|
||||
43 | .catch(() => false);
|
||||
44 | expect(canvasVisible).toBe(true);
|
||||
45 | await expect(page.locator('[class*="error-state"], [data-testid="error"]').first()).toBeHidden({ timeout: 2000 }).catch(() => {});
|
||||
46 | });
|
||||
47 | });
|
||||
48 |
|
||||
```
|
||||
Reference in New Issue
Block a user