cabloy 5.1.147 → 5.1.149
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cabloy-version +1 -1
- package/.claude/skills/cabloy-workflow/evals/evals.json +1 -1
- package/.claude/skills/cabloy-worktree-environment/SKILL.md +2 -5
- package/.claude/skills/cabloy-worktree-environment/evals/evals.json +1 -1
- package/CHANGELOG.md +17 -0
- package/package.json +1 -1
- package/repo-docs/.vitepress/config.mjs +4 -0
- package/repo-docs/backend/markdown-guide.md +1 -0
- package/repo-docs/backend/relations-guide.md +1 -0
- package/repo-docs/frontend/form-layout-guide.md +15 -1
- package/repo-docs/frontend/markdown-guide.md +1 -0
- package/repo-docs/frontend/scripts.md +11 -11
- package/repo-docs/fullstack/edition-collaboration-differences.md +1 -0
- package/repo-docs/fullstack/one-to-one-companion-resource-guide.md +447 -0
- package/repo-docs/fullstack/parallel-worktree-environment.md +4 -4
- package/repo-docs/fullstack/quickstart.md +4 -4
- package/repo-docs/reference/repo-scripts.md +12 -14
- package/repo-e2e/specs/cabloy-basic.spec.ts +176 -1
- package/vona/src/suite/a-training/modules/training-student/package.json +1 -0
- package/vona/src/suite/a-training/modules/training-student/src/.metadata/index.ts +177 -103
- package/vona/src/suite/a-training/modules/training-student/src/bean/meta.index.ts +1 -0
- package/vona/src/suite/a-training/modules/training-student/src/bean/meta.version.ts +19 -1
- package/vona/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +3 -0
- package/vona/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +3 -0
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentCreate.tsx +21 -2
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentSummary.tsx +7 -1
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentUpdate.tsx +21 -2
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentView.tsx +21 -2
- package/vona/src/suite/a-training/modules/training-student/src/entity/student.tsx +0 -3
- package/vona/src/suite/a-training/modules/training-student/src/entity/studentContent.tsx +36 -0
- package/vona/src/suite/a-training/modules/training-student/src/model/student.ts +6 -0
- package/vona/src/suite/a-training/modules/training-student/src/model/studentContent.ts +22 -0
- package/vona/src/suite/a-training/modules/training-student/src/service/student.ts +63 -6
- package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +117 -7
- package/zova/packages-zova/zova/package.json +2 -2
- package/zova/pnpm-lock.yaml +19 -15
- package/zova/src/suite/a-training/modules/training-student/package.json +3 -1
- package/zova/src/suite/a-training/modules/training-student/src/api/openapi/schemas.ts +40 -40
- package/zova/src/suite/a-training/modules/training-student/src/api/openapi/types.ts +358 -337
- package/zova/src/suite/a-training/modules/training-student/src/bean/tableCell.actionSummary.tsx +18 -7
- package/zova/src/suite-vendor/a-zova/modules/a-form/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/lib/formLayout.ts +13 -5
- package/zova/src/suite-vendor/a-zova/package.json +2 -2
|
@@ -75,10 +75,10 @@ For privacy, the skill writes only to absent or empty permitted broad local file
|
|
|
75
75
|
|
|
76
76
|
Use commands from the active repository root.
|
|
77
77
|
|
|
78
|
-
| Edition | Marker | Frontend command | Managed clean E2E
|
|
79
|
-
| ------------ | ------------------ | -------------------------------------------------- |
|
|
80
|
-
| Cabloy Basic | `__CABLOY_BASIC__` | `npm run dev:zova:admin` or `npm run dev:zova:web` | `npm run test:e2e`
|
|
81
|
-
| Cabloy Start | `__CABLOY_START__` | `npm run dev:zova:admin` or `npm run dev:zova:web` | `npm run test:e2e
|
|
78
|
+
| Edition | Marker | Frontend command | Managed clean E2E |
|
|
79
|
+
| ------------ | ------------------ | -------------------------------------------------- | ------------------ |
|
|
80
|
+
| Cabloy Basic | `__CABLOY_BASIC__` | `npm run dev:zova:admin` or `npm run dev:zova:web` | `npm run test:e2e` |
|
|
81
|
+
| Cabloy Start | `__CABLOY_START__` | `npm run dev:zova:admin` or `npm run dev:zova:web` | `npm run test:e2e` |
|
|
82
82
|
|
|
83
83
|
Run one frontend command, not both, in each worktree. Both managed clean E2E workflows read Vona's effective local `SERVER_LISTEN_PORT`, then reset, start, and target that local runtime.
|
|
84
84
|
|
|
@@ -128,20 +128,20 @@ Cabloy Start is checked out from the licensed private repository rather than cre
|
|
|
128
128
|
```text
|
|
129
129
|
repo-e2e/config/
|
|
130
130
|
repo-e2e/scripts/
|
|
131
|
-
repo-e2e/specs/
|
|
131
|
+
repo-e2e/specs/
|
|
132
132
|
```
|
|
133
133
|
|
|
134
|
-
The public Cabloy upgrade flow does not synchronize or repair those Start E2E files,
|
|
134
|
+
The public Cabloy upgrade flow does not synchronize or repair those private Start E2E files, its root E2E scripts, or `@playwright/test`. Keep project-owned browser scenarios in the flat `repo-e2e/specs/` directory under distinct filenames, for example `repo-e2e/specs/my-project.spec.ts`.
|
|
135
135
|
|
|
136
136
|
Prepare and run the managed Start baseline locally with:
|
|
137
137
|
|
|
138
138
|
```bash
|
|
139
139
|
npm run build:zova
|
|
140
140
|
npm run deps:vona
|
|
141
|
-
npm run test:e2e
|
|
141
|
+
npm run test:e2e
|
|
142
142
|
```
|
|
143
143
|
|
|
144
|
-
The managed command
|
|
144
|
+
The managed command checks the effective local server port and owns the local reset and Vona worker lifecycle. For a separately managed target, set `E2E_BASE_URL` and use `npm run test:e2e:fast`; this mode does not reset, build, start, or stop the target. Select surfaces with `--tag @web` or `--tag @admin`, and exact scenarios with native `--grep` arguments. Install Chromium once when needed with `npx playwright install chromium`. See [Repo Scripts](/reference/repo-scripts#ssr-browser-checks) for the complete command variants.
|
|
145
145
|
|
|
146
146
|
## 7. Next steps for framework-aware development
|
|
147
147
|
|
|
@@ -63,10 +63,8 @@ Cabloy Start exposes the equivalent Start repository surface:
|
|
|
63
63
|
- `npm run start:one`
|
|
64
64
|
- `npm run test`
|
|
65
65
|
- `npm run db:reset`
|
|
66
|
-
- `npm run test:e2e
|
|
67
|
-
- `npm run test:e2e:
|
|
68
|
-
- `npm run test:e2e:start:admin`
|
|
69
|
-
- `npm run test:e2e:start:clean`
|
|
66
|
+
- `npm run test:e2e`
|
|
67
|
+
- `npm run test:e2e:fast`
|
|
70
68
|
- `npm run tsc`
|
|
71
69
|
|
|
72
70
|
Cabloy Start does not expose Basic Commerce or root documentation wrappers.
|
|
@@ -96,10 +94,10 @@ The Start E2E baseline is maintained in the private repository:
|
|
|
96
94
|
```text
|
|
97
95
|
repo-e2e/config/
|
|
98
96
|
repo-e2e/scripts/
|
|
99
|
-
repo-e2e/specs/
|
|
97
|
+
repo-e2e/specs/
|
|
100
98
|
```
|
|
101
99
|
|
|
102
|
-
The public-package upgrade flow does not source or reconcile the Start baseline, its
|
|
100
|
+
The public-package upgrade flow does not source or reconcile the private Start baseline, its root E2E scripts, or `@playwright/test`. Keep project browser tests in the flat `repo-e2e/specs/` directory under distinct filenames, for example `repo-e2e/specs/my-project.spec.ts`.
|
|
103
101
|
|
|
104
102
|
## SSR browser checks
|
|
105
103
|
|
|
@@ -158,24 +156,24 @@ The Start suite exercises Web at `/` and Admin at `/admin` through Vona's SSR di
|
|
|
158
156
|
```bash
|
|
159
157
|
npm run build:zova
|
|
160
158
|
npm run deps:vona
|
|
161
|
-
npm run test:e2e
|
|
159
|
+
npm run test:e2e
|
|
162
160
|
```
|
|
163
161
|
|
|
164
162
|
```bash
|
|
165
163
|
# Exact acceptance scenario
|
|
166
|
-
npm run test:e2e:start -- --grep ATP-START-FLOW-01
|
|
164
|
+
npm run test:e2e:fast cabloy-start -- --grep ATP-START-FLOW-01
|
|
167
165
|
|
|
168
166
|
# Category or surface selection
|
|
169
|
-
npm run test:e2e:start
|
|
170
|
-
npm run test:e2e:
|
|
167
|
+
npm run test:e2e:fast cabloy-start -- --tag @smoke
|
|
168
|
+
npm run test:e2e:fast cabloy-admin -- --tag @admin --tag @cabloy-admin
|
|
171
169
|
```
|
|
172
170
|
|
|
173
|
-
For an externally managed Start target, set `
|
|
171
|
+
For an externally managed Start target, set `E2E_BASE_URL` and use `npm run test:e2e:fast`. The target owner is responsible for data, cache, artifact freshness, and process lifecycle:
|
|
174
172
|
|
|
175
173
|
```bash
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
174
|
+
E2E_BASE_URL=http://127.0.0.1:7102 npm run test:e2e:fast
|
|
175
|
+
E2E_BASE_URL=http://127.0.0.1:7102 npm run test:e2e:fast cabloy-admin -- --tag @admin
|
|
176
|
+
E2E_BASE_URL=http://127.0.0.1:7102 npm run test:e2e:fast cabloy-start -- --tag @web
|
|
179
177
|
```
|
|
180
178
|
|
|
181
179
|
Browser commands consume existing SSR and REST artifacts; they never rebuild them. Install Chromium once when needed with `npx playwright install chromium`.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Locator, Page } from '@playwright/test';
|
|
1
|
+
import type { Locator, Page, Route } from '@playwright/test';
|
|
2
2
|
|
|
3
3
|
import { expect, test } from '@playwright/test';
|
|
4
4
|
|
|
@@ -56,6 +56,39 @@ function waitForStudentSelect(page: Page) {
|
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
function installStudentCreateResponseCapture(page: Page) {
|
|
60
|
+
let studentId: string | number | undefined;
|
|
61
|
+
const handler = async (route: Route) => {
|
|
62
|
+
const request = route.request();
|
|
63
|
+
const url = new URL(request.url());
|
|
64
|
+
if (
|
|
65
|
+
request.method() !== 'POST' ||
|
|
66
|
+
url.pathname !== '/api/training/student' ||
|
|
67
|
+
request.headers()['x-vona-openapi-schema']
|
|
68
|
+
) {
|
|
69
|
+
await route.continue();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const response = await route.fetch();
|
|
73
|
+
const payload = await response.json();
|
|
74
|
+
if (response.ok() && ['string', 'number'].includes(typeof payload.data)) {
|
|
75
|
+
studentId = payload.data;
|
|
76
|
+
}
|
|
77
|
+
await route.fulfill({ response });
|
|
78
|
+
};
|
|
79
|
+
return {
|
|
80
|
+
async install() {
|
|
81
|
+
await page.route('**/api/training/student', handler);
|
|
82
|
+
},
|
|
83
|
+
async uninstall() {
|
|
84
|
+
await page.unroute('**/api/training/student', handler);
|
|
85
|
+
},
|
|
86
|
+
get studentId() {
|
|
87
|
+
return studentId;
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
59
92
|
async function getFieldGeometry(locator: Locator): Promise<IFieldGeometry> {
|
|
60
93
|
return locator.evaluate(element => {
|
|
61
94
|
const field = element.closest('label')?.parentElement;
|
|
@@ -348,3 +381,145 @@ test(
|
|
|
348
381
|
expect(pageErrors).toEqual([]);
|
|
349
382
|
},
|
|
350
383
|
);
|
|
384
|
+
|
|
385
|
+
test(
|
|
386
|
+
'ATP-BASIC-FORM-01: Training Student content remains in the Basic Information tab',
|
|
387
|
+
{ tag: ['@admin', '@flow'] },
|
|
388
|
+
async ({ page }) => {
|
|
389
|
+
const pageErrors = collectPageErrors(page);
|
|
390
|
+
await loginAsAdmin(page);
|
|
391
|
+
|
|
392
|
+
const response = await page.goto('/admin/rest/resource/training-student%3Astudent/create', {
|
|
393
|
+
waitUntil: 'load',
|
|
394
|
+
});
|
|
395
|
+
expect(response?.ok()).toBeTruthy();
|
|
396
|
+
await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'admin');
|
|
397
|
+
|
|
398
|
+
const basicInformationTab = page.getByRole('tab', { name: 'Basic Information', exact: true });
|
|
399
|
+
const studentContentGroup = page
|
|
400
|
+
.locator('fieldset')
|
|
401
|
+
.filter({ has: page.locator('legend').filter({ hasText: /^Student Content$/ }) });
|
|
402
|
+
await expect(studentContentGroup).toHaveCount(1);
|
|
403
|
+
await expect(
|
|
404
|
+
studentContentGroup.getByText('Description Markdown', { exact: true }),
|
|
405
|
+
).toBeVisible();
|
|
406
|
+
|
|
407
|
+
const studentContentPanel = studentContentGroup.locator('xpath=ancestor::*[@role="tabpanel"]');
|
|
408
|
+
await expect(studentContentPanel).toHaveCount(1);
|
|
409
|
+
const basicInformationTabId = await basicInformationTab.getAttribute('id');
|
|
410
|
+
expect(basicInformationTabId).not.toBeNull();
|
|
411
|
+
await expect(studentContentPanel).toHaveAttribute('aria-labelledby', basicInformationTabId!);
|
|
412
|
+
expect(pageErrors).toEqual([]);
|
|
413
|
+
},
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
test(
|
|
417
|
+
'ATP-BASIC-SUMMARY-01: Training Student summary renders Markdown HTML in a dialog',
|
|
418
|
+
{ tag: ['@admin', '@flow'] },
|
|
419
|
+
async ({ page }) => {
|
|
420
|
+
const pageErrors = collectPageErrors(page);
|
|
421
|
+
await loginAsAdmin(page);
|
|
422
|
+
|
|
423
|
+
const studentName = `Summary E2E ${Date.now()}`;
|
|
424
|
+
const createResponseCapture = installStudentCreateResponseCapture(page);
|
|
425
|
+
let studentId: string | number | undefined;
|
|
426
|
+
await createResponseCapture.install();
|
|
427
|
+
try {
|
|
428
|
+
const createPageResponse = await page.goto(
|
|
429
|
+
'/admin/rest/resource/training-student%3Astudent/create',
|
|
430
|
+
{ waitUntil: 'load' },
|
|
431
|
+
);
|
|
432
|
+
expect(createPageResponse?.ok()).toBeTruthy();
|
|
433
|
+
await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'admin');
|
|
434
|
+
await page
|
|
435
|
+
.getByRole('group', { name: 'Student Name *', exact: true })
|
|
436
|
+
.getByRole('textbox')
|
|
437
|
+
.fill(studentName);
|
|
438
|
+
await page
|
|
439
|
+
.getByRole('group', { name: 'Mobile *', exact: true })
|
|
440
|
+
.getByRole('textbox')
|
|
441
|
+
.fill('13812345678');
|
|
442
|
+
await page
|
|
443
|
+
.locator('.ProseMirror')
|
|
444
|
+
.last()
|
|
445
|
+
.fill(`## Summary heading\n\nSummary paragraph ${studentName}`);
|
|
446
|
+
await page.getByRole('tab', { name: 'Student Training Records', exact: true }).click();
|
|
447
|
+
await page.getByText('Foundation Track', { exact: true }).click();
|
|
448
|
+
|
|
449
|
+
const submitButton = page.getByRole('button', { name: 'Submit', exact: true });
|
|
450
|
+
await expect(submitButton).toBeEnabled();
|
|
451
|
+
await submitButton.click();
|
|
452
|
+
await expect(page).toHaveURL(/\/admin\/(?:\?|$)/);
|
|
453
|
+
studentId = createResponseCapture.studentId;
|
|
454
|
+
expect(['string', 'number']).toContain(typeof studentId);
|
|
455
|
+
|
|
456
|
+
const response = await page.goto('/admin/rest/resource/training-student%3Astudent', {
|
|
457
|
+
waitUntil: 'load',
|
|
458
|
+
});
|
|
459
|
+
expect(response?.ok()).toBeTruthy();
|
|
460
|
+
await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'admin');
|
|
461
|
+
|
|
462
|
+
const filterName = page.getByLabel('Student Name');
|
|
463
|
+
await expect(filterName).toBeVisible();
|
|
464
|
+
await filterName.fill(studentName);
|
|
465
|
+
const searchResponse = waitForStudentSelect(page);
|
|
466
|
+
await page.getByRole('button', { name: 'Search', exact: true }).click();
|
|
467
|
+
await searchResponse;
|
|
468
|
+
|
|
469
|
+
const row = page.locator('tr').filter({ hasText: studentName });
|
|
470
|
+
await expect(row).toHaveCount(1);
|
|
471
|
+
const summaryResponse = page.waitForResponse(response => {
|
|
472
|
+
const url = new URL(response.url());
|
|
473
|
+
return (
|
|
474
|
+
response.request().method() === 'GET' &&
|
|
475
|
+
response.ok() &&
|
|
476
|
+
url.pathname === `/api/training/student/summary/${studentId}` &&
|
|
477
|
+
!response.request().headers()['x-vona-openapi-schema']
|
|
478
|
+
);
|
|
479
|
+
});
|
|
480
|
+
await row.getByRole('button', { name: 'Summary', exact: true }).click();
|
|
481
|
+
await summaryResponse;
|
|
482
|
+
|
|
483
|
+
const description = page.locator('.student-summary-description');
|
|
484
|
+
await expect(description).toBeVisible();
|
|
485
|
+
await expect(description.locator('h2')).toHaveText('Summary heading');
|
|
486
|
+
await expect(description).toContainText(`Summary paragraph ${studentName}`);
|
|
487
|
+
expect(pageErrors).toEqual([]);
|
|
488
|
+
} finally {
|
|
489
|
+
try {
|
|
490
|
+
if (studentId !== undefined) {
|
|
491
|
+
const description = page.locator('.student-summary-description');
|
|
492
|
+
if (await description.count()) {
|
|
493
|
+
await description.locator('xpath=../..').getByRole('button').click();
|
|
494
|
+
}
|
|
495
|
+
const row = page.locator('tr').filter({ hasText: studentName });
|
|
496
|
+
if (!(await row.count())) {
|
|
497
|
+
const response = await page.goto('/admin/rest/resource/training-student%3Astudent', {
|
|
498
|
+
waitUntil: 'load',
|
|
499
|
+
});
|
|
500
|
+
expect(response?.ok()).toBeTruthy();
|
|
501
|
+
await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'admin');
|
|
502
|
+
await page.getByLabel('Student Name').fill(studentName);
|
|
503
|
+
const searchResponse = waitForStudentSelect(page);
|
|
504
|
+
await page.getByRole('button', { name: 'Search', exact: true }).click();
|
|
505
|
+
await searchResponse;
|
|
506
|
+
}
|
|
507
|
+
const deleteResponse = page.waitForResponse(response => {
|
|
508
|
+
const url = new URL(response.url());
|
|
509
|
+
return (
|
|
510
|
+
response.request().method() === 'DELETE' &&
|
|
511
|
+
response.ok() &&
|
|
512
|
+
url.pathname === `/api/training/student/deleteForce/${studentId}` &&
|
|
513
|
+
!response.request().headers()['x-vona-openapi-schema']
|
|
514
|
+
);
|
|
515
|
+
});
|
|
516
|
+
await row.getByRole('button', { name: 'Force Delete', exact: true }).click();
|
|
517
|
+
await page.getByRole('button', { name: 'Yes', exact: true }).click();
|
|
518
|
+
await deleteResponse;
|
|
519
|
+
}
|
|
520
|
+
} finally {
|
|
521
|
+
await createResponseCapture.uninstall();
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
},
|
|
525
|
+
);
|