cabloy 5.1.158 → 5.1.159

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.
Files changed (88) hide show
  1. package/.cabloy-version +1 -1
  2. package/.claude/skills/cabloy-contract-loop/SKILL.md +24 -7
  3. package/.claude/skills/cabloy-contract-loop/evals/evals.json +12 -0
  4. package/.claude/skills/cabloy-contract-loop/references/contract-loop-map.md +28 -0
  5. package/.claude/skills/cabloy-contract-loop/references/verification-checklist.md +19 -2
  6. package/.claude/skills/cabloy-resource-field-update/SKILL.md +18 -0
  7. package/.claude/skills/cabloy-zova-source-reading/SKILL.md +14 -0
  8. package/.claude/skills/cabloy-zova-source-reading/references/core-reading-paths.md +10 -0
  9. package/.github/workflows/docs-pages.yml +18 -2
  10. package/CHANGELOG.md +19 -0
  11. package/package.json +2 -1
  12. package/repo-docs/.vitepress/config.mjs +34 -4
  13. package/repo-docs/.vitepress/theme/components/GitHubRepositoriesNav.vue +255 -0
  14. package/repo-docs/.vitepress/theme/custom.css +21 -0
  15. package/repo-docs/.vitepress/theme/index.js +7 -1
  16. package/repo-docs/backend/resource-field-update.md +48 -1
  17. package/repo-docs/blogs/index.md +2 -0
  18. package/repo-docs/frontend/form-layout-guide.md +6 -6
  19. package/repo-docs/frontend/page-guide.md +12 -0
  20. package/repo-docs/frontend/page-meta-guide.md +27 -0
  21. package/repo-docs/frontend/ssr-architecture-overview.md +4 -0
  22. package/repo-docs/frontend/ssr-build-deploy-guide.md +1 -1
  23. package/repo-docs/frontend/zova-form-source-reading-map.md +2 -2
  24. package/repo-docs/frontend/zova-form-under-the-hood.md +1 -1
  25. package/repo-docs/frontend/zova-reactivity-under-the-hood.md +65 -9
  26. package/repo-docs/fullstack/contract-loop-playbook.md +2 -0
  27. package/repo-docs/fullstack/ssr-site-and-flavor-setup.md +198 -0
  28. package/repo-docs/fullstack/vona-zova-integration.md +2 -0
  29. package/repo-e2e/docs/playwright.config.ts +37 -0
  30. package/repo-e2e/docs/specs/blogs-index.spec.ts +180 -0
  31. package/repo-e2e/docs/specs/github-repositories-nav.spec.ts +69 -0
  32. package/repo-e2e/docs/test-results/.last-run.json +4 -0
  33. package/repo-e2e/specs/cabloy-basic.spec.ts +136 -2
  34. package/vona/packages-cli/cli/package.json +1 -1
  35. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/entity/{{resourceName}}.tsx_ +1 -1
  36. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/entity/{{resourceName}}.tsx_ +1 -1
  37. package/vona/packages-cli/cli-set-api/package.json +1 -1
  38. package/vona/packages-vona/vona/package.json +1 -1
  39. package/vona/pnpm-lock.yaml +21 -80
  40. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/en-us.ts +3 -0
  41. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/zh-cn.ts +3 -0
  42. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productCreate.tsx +2 -0
  43. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productUpdate.tsx +2 -0
  44. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productView.tsx +2 -0
  45. package/vona/src/suite/a-training/modules/training-record/src/dto/recordCreate.tsx +1 -0
  46. package/vona/src/suite/a-training/modules/training-record/src/dto/recordSelectReq.tsx +3 -7
  47. package/vona/src/suite/a-training/modules/training-record/src/dto/recordUpdate.tsx +1 -0
  48. package/vona/src/suite/a-training/modules/training-record/src/dto/recordView.tsx +1 -0
  49. package/vona/src/suite/a-training/modules/training-record/src/entity/record.tsx +8 -0
  50. package/vona/src/suite/a-training/modules/training-record/test/record.test.ts +51 -0
  51. package/vona/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +3 -0
  52. package/vona/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +3 -0
  53. package/vona/src/suite/a-training/modules/training-student/src/dto/studentCreate.tsx +1 -0
  54. package/vona/src/suite/a-training/modules/training-student/src/dto/studentSelectResItem.tsx +1 -0
  55. package/vona/src/suite/a-training/modules/training-student/src/dto/studentUpdate.tsx +1 -0
  56. package/vona/src/suite/a-training/modules/training-student/src/dto/studentView.tsx +1 -0
  57. package/vona/src/suite/a-training/modules/training-student/src/entity/student.tsx +10 -1
  58. package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +99 -1
  59. package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/package.json +1 -1
  60. package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
  61. package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
  62. package/vona/src/suite-vendor/a-vona/modules/a-web/package.json +1 -1
  63. package/vona/src/suite-vendor/a-vona/modules/a-web/src/bean/pipe.filter.ts +85 -57
  64. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  65. package/zova/packages-cli/cli/package.json +2 -2
  66. package/zova/packages-cli/cli-set-front/cli/templates/rest/render.ts +2 -0
  67. package/zova/packages-cli/cli-set-front/cli/templates/rest/rest.ts +13 -3
  68. package/zova/packages-cli/cli-set-front/package.json +1 -1
  69. package/zova/packages-zova/zova/package.json +2 -2
  70. package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockPage/controller.tsx +31 -11
  71. package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockTable/controller.tsx +3 -0
  72. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/component/blockPageEntry/controller.tsx +1 -1
  73. package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/table/render.tsx +68 -8
  74. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
  75. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +4 -0
  76. package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
  77. package/zova/src/suite-vendor/a-zova/modules/a-form/package.json +1 -1
  78. package/zova/src/suite-vendor/a-zova/modules/a-form/src/lib/formLayout.ts +0 -6
  79. package/zova/src/suite-vendor/a-zova/modules/a-form/test/lib/formLayout.test.ts +16 -9
  80. package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
  81. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/model/sdk.ts +4 -0
  82. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/rest.ts +13 -2
  83. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/schema.ts +1 -0
  84. package/zova/src/suite-vendor/a-zova/modules/a-table/package.json +1 -1
  85. package/zova/src/suite-vendor/a-zova/modules/a-table/src/component/table/controller.tsx +50 -0
  86. package/zova/src/suite-vendor/a-zova/modules/a-table/src/component/table/render.tsx +60 -7
  87. package/zova/src/suite-vendor/a-zova/modules/a-table/src/types/table.ts +6 -4
  88. package/zova/src/suite-vendor/a-zova/package.json +4 -4
@@ -0,0 +1,180 @@
1
+ import type { Page } from '@playwright/test';
2
+
3
+ import { expect, test } from '@playwright/test';
4
+
5
+ interface IRectangle {
6
+ bottom: number;
7
+ height: number;
8
+ left: number;
9
+ right: number;
10
+ top: number;
11
+ width: number;
12
+ }
13
+
14
+ interface IBlogsGeometry {
15
+ cards: IRectangle[];
16
+ grid: IRectangle & { display: string };
17
+ }
18
+
19
+ interface IArticleGeometry {
20
+ aside: IRectangle;
21
+ container: IRectangle;
22
+ content: IRectangle;
23
+ contentContainer: IRectangle;
24
+ body: IRectangle;
25
+ }
26
+
27
+ const blogArticlePaths = [
28
+ '/blogs/ai-react-nextjs-enterprise-architecture-cabloy/',
29
+ '/blogs/cabloy-fullstack-resource-addressing/',
30
+ '/blogs/nextjs-integrated-fullstack-cabloy-contract-loop/',
31
+ '/blogs/vue-object-oriented-zova-beginner-mental-model/',
32
+ ];
33
+
34
+ function collectPageErrors(page: Page) {
35
+ const errors: Error[] = [];
36
+ page.on('pageerror', error => {
37
+ errors.push(error);
38
+ });
39
+ return errors;
40
+ }
41
+
42
+ function collectConsoleErrors(page: Page) {
43
+ const errors: string[] = [];
44
+ page.on('console', message => {
45
+ const text = message.text();
46
+ if (message.type() === 'error' || /hydration mismatch/i.test(text)) {
47
+ errors.push(text);
48
+ }
49
+ });
50
+ return errors;
51
+ }
52
+
53
+ function getDocumentHorizontalOverflow(page: Page) {
54
+ return page.evaluate(() => {
55
+ return (
56
+ Math.max(document.documentElement.scrollWidth, document.body.scrollWidth) - window.innerWidth
57
+ );
58
+ });
59
+ }
60
+
61
+ function getBlogsGeometry(page: Page) {
62
+ return page.locator('.cabloy-blog-grid').evaluate(grid => {
63
+ const toRectangle = (element: Element) => {
64
+ const { bottom, height, left, right, top, width } = element.getBoundingClientRect();
65
+ return { bottom, height, left, right, top, width };
66
+ };
67
+ const cards = Array.from(grid.querySelectorAll('.cabloy-blog-card'), toRectangle);
68
+ return {
69
+ grid: {
70
+ ...toRectangle(grid),
71
+ display: getComputedStyle(grid).display,
72
+ },
73
+ cards,
74
+ };
75
+ }) as Promise<IBlogsGeometry>;
76
+ }
77
+
78
+ function getArticleGeometry(page: Page) {
79
+ return page.locator('.VPDoc').evaluate(doc => {
80
+ const getRequiredChild = (parent: Element, selector: string) => {
81
+ const element = parent.querySelector(`:scope > ${selector}`);
82
+ if (!element) throw new Error(`Could not find ${selector}`);
83
+ return element;
84
+ };
85
+ const toRectangle = (element: Element) => {
86
+ const { bottom, height, left, right, top, width } = element.getBoundingClientRect();
87
+ return { bottom, height, left, right, top, width };
88
+ };
89
+ const container = getRequiredChild(doc, '.container');
90
+ const aside = getRequiredChild(container, '.aside');
91
+ const content = getRequiredChild(container, '.content');
92
+ const contentContainer = getRequiredChild(content, '.content-container');
93
+ const body = getRequiredChild(contentContainer, '.main').querySelector('.vp-doc');
94
+ if (!body) throw new Error('Could not find .vp-doc');
95
+ return {
96
+ aside: toRectangle(aside),
97
+ container: toRectangle(container),
98
+ content: toRectangle(content),
99
+ contentContainer: toRectangle(contentContainer),
100
+ body: toRectangle(body),
101
+ };
102
+ }) as Promise<IArticleGeometry>;
103
+ }
104
+
105
+ test(
106
+ 'DOCS-BLOGS-01: Blogs index uses a wide desktop card grid',
107
+ { tag: '@layout' },
108
+ async ({ page }) => {
109
+ const pageErrors = collectPageErrors(page);
110
+ const consoleErrors = collectConsoleErrors(page);
111
+
112
+ const documentResponse = await page.goto('/blogs/', { waitUntil: 'load' });
113
+ expect(documentResponse?.ok()).toBeTruthy();
114
+
115
+ await expect(page.locator('.Layout.cabloy-blogs-index')).toBeVisible();
116
+ await expect(page.getByRole('heading', { name: 'Blogs', level: 1 })).toBeVisible();
117
+ await expect(page.locator('.VPDoc.has-sidebar')).toHaveCount(0);
118
+ await expect(page.locator('.VPDoc.has-aside')).toHaveCount(0);
119
+
120
+ const cards = page.locator('.cabloy-blog-card');
121
+ await expect(cards).toHaveCount(4);
122
+ for (let index = 0; index < 4; index++) {
123
+ await expect(cards.nth(index)).toBeVisible();
124
+ await expect(cards.nth(index).locator('.cabloy-blog-card__cover img')).toBeVisible();
125
+ }
126
+
127
+ const geometry = await getBlogsGeometry(page);
128
+ expect(geometry.grid.display).toBe('grid');
129
+ expect(geometry.cards).toHaveLength(4);
130
+
131
+ const [firstCard, ...remainingCards] = geometry.cards;
132
+ expect(firstCard.width).toBeGreaterThanOrEqual(279);
133
+ for (const card of remainingCards) {
134
+ expect(Math.abs(card.top - firstCard.top)).toBeLessThanOrEqual(1);
135
+ expect(Math.abs(card.width - firstCard.width)).toBeLessThanOrEqual(1);
136
+ expect(card.left).toBeGreaterThan(firstCard.left);
137
+ expect(card.left).toBeGreaterThanOrEqual(geometry.grid.left - 1);
138
+ expect(card.right).toBeLessThanOrEqual(geometry.grid.right + 1);
139
+ expect(card.right).toBeLessThanOrEqual(1441);
140
+ }
141
+ expect(firstCard.left).toBeGreaterThanOrEqual(geometry.grid.left - 1);
142
+ expect(firstCard.right).toBeLessThanOrEqual(geometry.grid.right + 1);
143
+ expect(firstCard.right).toBeLessThanOrEqual(1441);
144
+
145
+ await expect.poll(() => getDocumentHorizontalOverflow(page)).toBeLessThanOrEqual(1);
146
+ expect(pageErrors).toEqual([]);
147
+ expect(consoleErrors).toEqual([]);
148
+ },
149
+ );
150
+
151
+ test(
152
+ 'DOCS-BLOGS-02: Blog articles use a wide desktop body with an outline aside',
153
+ { tag: '@layout' },
154
+ async ({ page }) => {
155
+ const pageErrors = collectPageErrors(page);
156
+ const consoleErrors = collectConsoleErrors(page);
157
+
158
+ for (const path of blogArticlePaths) {
159
+ const documentResponse = await page.goto(path, { waitUntil: 'load' });
160
+ expect(documentResponse?.ok()).toBeTruthy();
161
+
162
+ await expect(page.locator('.Layout.cabloy-blogs-article')).toBeVisible();
163
+ await expect(page.locator('.VPDoc.has-sidebar')).toHaveCount(0);
164
+ await expect(page.locator('.VPDoc.has-aside')).toHaveCount(1);
165
+ await expect(page.locator('.aside')).toBeVisible();
166
+
167
+ const geometry = await getArticleGeometry(page);
168
+ expect(geometry.container.width).toBeCloseTo(1216, 0);
169
+ expect(geometry.content.width).toBeCloseTo(960, 0);
170
+ expect(geometry.contentContainer.width).toBeCloseTo(896, 0);
171
+ expect(geometry.body.width).toBeCloseTo(896, 0);
172
+ expect(geometry.aside.left).toBeGreaterThanOrEqual(geometry.content.right - 1);
173
+ expect(geometry.aside.right).toBeLessThanOrEqual(geometry.container.right + 1);
174
+ await expect.poll(() => getDocumentHorizontalOverflow(page)).toBeLessThanOrEqual(1);
175
+ }
176
+
177
+ expect(pageErrors).toEqual([]);
178
+ expect(consoleErrors).toEqual([]);
179
+ },
180
+ );
@@ -0,0 +1,69 @@
1
+ import { expect, test } from '@playwright/test';
2
+
3
+ const repositories = [
4
+ {
5
+ text: 'github.com/cabloy/cabloy',
6
+ link: 'https://github.com/cabloy/cabloy',
7
+ },
8
+ {
9
+ text: 'github.com/cabloy/cabloy-start',
10
+ link: 'https://github.com/cabloy/cabloy-start',
11
+ },
12
+ ];
13
+
14
+ test('DOCS-NAV-01: GitHub icon menu exposes both repository links on desktop', async ({ page }) => {
15
+ const response = await page.goto('/', { waitUntil: 'load' });
16
+ expect(response?.ok()).toBeTruthy();
17
+
18
+ const trigger = page.getByRole('button', { name: 'GitHub repositories' });
19
+ await expect(trigger).toBeVisible();
20
+ await expect(trigger).toHaveAttribute('aria-expanded', 'false');
21
+ await expect(trigger).not.toHaveText(/GitHub/);
22
+
23
+ await trigger.focus();
24
+ await page.keyboard.press('ArrowDown');
25
+ await expect(trigger).toHaveAttribute('aria-expanded', 'true');
26
+
27
+ const menu = page.getByRole('menu', { name: 'GitHub repositories' });
28
+ await expect(menu).toBeVisible();
29
+ for (const repository of repositories) {
30
+ const link = menu.getByRole('menuitem', { name: repository.text, exact: true });
31
+ await expect(link).toHaveAttribute('href', repository.link);
32
+ await expect(link).toHaveAttribute('target', '_blank');
33
+ await expect(link).toHaveAttribute('rel', 'noopener noreferrer');
34
+ }
35
+
36
+ await expect(
37
+ menu.getByRole('menuitem', { name: repositories[0].text, exact: true }),
38
+ ).toBeFocused();
39
+ await page.keyboard.press('ArrowDown');
40
+ await expect(
41
+ menu.getByRole('menuitem', { name: repositories[1].text, exact: true }),
42
+ ).toBeFocused();
43
+ await page.keyboard.press('Escape');
44
+ await expect(menu).toHaveCount(0);
45
+ await expect(trigger).toBeFocused();
46
+
47
+ await trigger.click();
48
+ await expect(menu).toBeVisible();
49
+ await page.mouse.click(10, 200);
50
+ await expect(menu).toHaveCount(0);
51
+ });
52
+
53
+ test('DOCS-NAV-02: GitHub repositories remain readable in mobile navigation', async ({ page }) => {
54
+ await page.setViewportSize({ width: 375, height: 812 });
55
+ const response = await page.goto('/', { waitUntil: 'load' });
56
+ expect(response?.ok()).toBeTruthy();
57
+
58
+ await page.locator('.VPNavBarHamburger').click();
59
+
60
+ const screenMenu = page.locator('.VPNavScreen .cabloy-github-repositories--screen');
61
+ await expect(screenMenu).toBeVisible();
62
+ await expect(screenMenu.getByText('GitHub repositories')).toBeVisible();
63
+ for (const repository of repositories) {
64
+ const link = screenMenu.getByRole('link', { name: repository.text, exact: true });
65
+ await expect(link).toHaveAttribute('href', repository.link);
66
+ await expect(link).toHaveAttribute('target', '_blank');
67
+ await expect(link).toHaveAttribute('rel', 'noopener noreferrer');
68
+ }
69
+ });
@@ -0,0 +1,4 @@
1
+ {
2
+ "status": "passed",
3
+ "failedTests": []
4
+ }
@@ -44,12 +44,12 @@ async function loginAsAdmin(page: Page) {
44
44
  return dashboardResponse;
45
45
  }
46
46
 
47
- function waitForStudentSelect(page: Page) {
47
+ function waitForStudentSelect(page: Page, requireSuccess = true) {
48
48
  return page.waitForResponse(response => {
49
49
  const url = new URL(response.url());
50
50
  return (
51
51
  response.request().method() === 'GET' &&
52
- response.ok() &&
52
+ (!requireSuccess || response.ok()) &&
53
53
  url.pathname === '/api/training/student' &&
54
54
  !response.request().headers()['x-vona-openapi-schema']
55
55
  );
@@ -382,6 +382,140 @@ test(
382
382
  },
383
383
  );
384
384
 
385
+ test(
386
+ 'ATP-BASIC-TABLE-01: Training Student table submits server sorting and pins configured columns',
387
+ { tag: ['@admin', '@flow'] },
388
+ async ({ page }) => {
389
+ await page.setViewportSize({ width: 1440, height: 900 });
390
+ const pageErrors = collectPageErrors(page);
391
+ await loginAsAdmin(page);
392
+
393
+ const studentName = `Table E2E ${Date.now()}`;
394
+ const accessToken = (await page.context().cookies()).find(
395
+ cookie => cookie.name === 'token',
396
+ )?.value;
397
+ expect(accessToken).toBeTruthy();
398
+ const headers = { Authorization: `Bearer ${accessToken}` };
399
+ let studentId: string | number | undefined;
400
+ try {
401
+ const createResponse = await page.request.post('/api/training/student', {
402
+ data: { name: studentName, mobile: '13812345678', level: 1 },
403
+ headers,
404
+ });
405
+ expect(createResponse.ok()).toBeTruthy();
406
+ studentId = (await createResponse.json()).data;
407
+ expect(['string', 'number']).toContain(typeof studentId);
408
+
409
+ const initialSelect = waitForStudentSelect(page);
410
+ await page.getByRole('link', { name: 'Student', exact: true }).click();
411
+ await expect(page).toHaveURL(studentResourceUrl);
412
+ await initialSelect;
413
+ await page.setViewportSize({ width: 800, height: 900 });
414
+
415
+ const nameSort = page.getByRole('button', { name: 'Sort by name', exact: true });
416
+ const nameHeader = nameSort.locator('xpath=ancestor::th');
417
+ const operationsHeader = page.getByRole('columnheader', { name: 'Operations', exact: true });
418
+ await expect(nameSort).toBeVisible();
419
+ await expect(nameHeader).toHaveAttribute('aria-sort', 'none');
420
+ await expect(nameHeader).toHaveCSS('position', 'sticky');
421
+ await expect(nameHeader).toHaveCSS('left', '0px');
422
+ await expect(nameHeader).toHaveCSS('min-width', '240px');
423
+ await expect(nameHeader).toHaveCSS('text-align', 'left');
424
+ await expect(operationsHeader).toHaveCSS('position', 'sticky');
425
+ await expect(operationsHeader).toHaveCSS('right', '0px');
426
+ await expect(operationsHeader).toHaveCSS('min-width', '360px');
427
+ await expect(operationsHeader).toHaveCSS('text-align', 'center');
428
+
429
+ const ascendingResponse = waitForStudentSelect(page, false);
430
+ await nameSort.click();
431
+ await expect(nameHeader).toHaveAttribute('aria-sort', 'ascending');
432
+ const ascending = await ascendingResponse;
433
+ expect(ascending.status()).toBe(200);
434
+ const ascendingUrl = new URL(ascending.url());
435
+ expect(JSON.parse(ascendingUrl.searchParams.get('orders')!)).toEqual([['name', 'asc']]);
436
+
437
+ const descendingResponse = waitForStudentSelect(page, false);
438
+ await nameSort.click();
439
+ await expect(nameHeader).toHaveAttribute('aria-sort', 'descending');
440
+ const descending = await descendingResponse;
441
+ expect(descending.status()).toBe(200);
442
+ const descendingUrl = new URL(descending.url());
443
+ expect(JSON.parse(descendingUrl.searchParams.get('orders')!)).toEqual([['name', 'desc']]);
444
+
445
+ const tableWrapper = page
446
+ .locator('div.overflow-x-auto')
447
+ .filter({ has: page.locator('table') })
448
+ .first();
449
+ await expect(tableWrapper).toBeVisible();
450
+ const overflow = await tableWrapper.evaluate(element => {
451
+ return element.scrollWidth > element.clientWidth;
452
+ });
453
+ expect(overflow).toBeTruthy();
454
+ await tableWrapper.evaluate(element => {
455
+ element.scrollLeft = element.scrollWidth;
456
+ });
457
+ await expect(operationsHeader).toHaveCSS('right', '0px');
458
+
459
+ const operationsRow = page
460
+ .locator('tbody tr')
461
+ .filter({
462
+ has: page.getByRole('button', { name: 'Summary', exact: true }),
463
+ })
464
+ .first();
465
+ const operationActions = operationsRow.getByRole('button');
466
+ await expect(operationActions).toHaveCount(4);
467
+ for (const action of await operationActions.all()) {
468
+ await expect(action).toBeVisible();
469
+ }
470
+ const operationsGeometry = await operationActions.evaluateAll(actions => {
471
+ const cell = actions[0]?.closest('td');
472
+ const join = actions[0]?.parentElement;
473
+ const wrapper = cell?.closest('table')?.parentElement;
474
+ if (!cell || !join || !wrapper)
475
+ throw new Error('Could not resolve the Operations action cell');
476
+ const cellRect = cell.getBoundingClientRect();
477
+ const joinRect = join.getBoundingClientRect();
478
+ const wrapperRect = wrapper.getBoundingClientRect();
479
+ const tolerance = 2;
480
+ return {
481
+ fitsCell: join.scrollWidth <= cell.clientWidth,
482
+ actionsFitCell: actions.every(action => {
483
+ const rect = action.getBoundingClientRect();
484
+ return (
485
+ rect.left >= cellRect.left - tolerance && rect.right <= cellRect.right + tolerance
486
+ );
487
+ }),
488
+ joinFitsCell:
489
+ joinRect.left >= cellRect.left - tolerance &&
490
+ joinRect.right <= cellRect.right + tolerance,
491
+ actionsFitWrapper: actions.every(action => {
492
+ const rect = action.getBoundingClientRect();
493
+ return (
494
+ rect.left >= wrapperRect.left - tolerance &&
495
+ rect.right <= wrapperRect.right + tolerance
496
+ );
497
+ }),
498
+ };
499
+ });
500
+ expect(operationsGeometry).toEqual({
501
+ fitsCell: true,
502
+ actionsFitCell: true,
503
+ joinFitsCell: true,
504
+ actionsFitWrapper: true,
505
+ });
506
+ expect(pageErrors).toEqual([]);
507
+ } finally {
508
+ if (studentId !== undefined) {
509
+ const deleteResponse = await page.request.delete(
510
+ `/api/training/student/deleteForce/${studentId}`,
511
+ { headers },
512
+ );
513
+ expect(deleteResponse.ok()).toBeTruthy();
514
+ }
515
+ }
516
+ },
517
+ );
518
+
385
519
  test(
386
520
  'ATP-BASIC-FORM-01: Training Student content remains in the Basic Information tab',
387
521
  { tag: ['@admin', '@flow'] },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-cli",
3
- "version": "1.1.143",
3
+ "version": "1.1.144",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "vona cli",
6
6
  "keywords": [
@@ -11,7 +11,7 @@ export interface IEntityOptions<%=argv.resourceNameCapitalize%> extends IDecorat
11
11
  @Entity<IEntityOptions<%=argv.resourceNameCapitalize%>>('<%=argv.moduleResourceName%>', {
12
12
  openapi: { title: $locale('<%=argv.resourceNameCapitalize%>') },
13
13
  fields: {
14
- id: $makeMetadata(ZovaRender.order(1, 'core')),
14
+ id: $makeMetadata(ZovaRender.order(1, 'core'), ZovaRender.column({ width: 50, fixed: 'left' })),
15
15
  iid: $makeMetadata(ZovaRender.visible(false)),
16
16
  deleted: $makeMetadata(ZovaRender.visible(false)),
17
17
  createdAt: $makeMetadata(
@@ -11,7 +11,7 @@ export interface IEntityOptions<%=argv.resourceNameCapitalize%> extends IDecorat
11
11
  @Entity<IEntityOptions<%=argv.resourceNameCapitalize%>>('<%=argv.moduleResourceName%>', {
12
12
  openapi: { title: $locale('<%=argv.resourceNameCapitalize%>') },
13
13
  fields: {
14
- id: $makeMetadata(ZovaRender.order(1, 'core')),
14
+ id: $makeMetadata(ZovaRender.order(1, 'core'), ZovaRender.column({ width: 50, fixed: 'left' })),
15
15
  iid: $makeMetadata(ZovaRender.visible(false)),
16
16
  deleted: $makeMetadata(ZovaRender.visible(false)),
17
17
  createdAt: $makeMetadata(
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-cli-set-api",
3
- "version": "1.1.141",
3
+ "version": "1.1.142",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "vona cli-set-api",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona",
3
- "version": "5.1.91",
3
+ "version": "5.1.92",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "Vona is an intuitive, elegant and powerful Node.js framework for rapidly developing enterprise applications of any size",
6
6
  "keywords": [
@@ -548,6 +548,9 @@ importers:
548
548
  '@tanstack/vue-query':
549
549
  specifier: ^5.101.1
550
550
  version: 5.101.2(vue@3.5.39(typescript@5.9.3))
551
+ '@tanstack/vue-table':
552
+ specifier: ^8.21.3
553
+ version: 8.21.3(vue@3.5.39(typescript@5.9.3))
551
554
  openapi3-ts:
552
555
  specifier: ^4.6.0
553
556
  version: 4.6.0
@@ -615,17 +618,17 @@ importers:
615
618
  specifier: ^5.0.5
616
619
  version: 5.0.5
617
620
  zova-module-rest-resource:
618
- specifier: ^5.1.48
621
+ specifier: ^5.1.49
619
622
  version: 5.1.49
620
623
 
621
624
  .zova-rest/cabloy-basic-web:
622
625
  dependencies:
623
- '@tanstack/query-core':
624
- specifier: ^5.101.1
625
- version: 5.101.2
626
626
  '@tanstack/vue-query':
627
627
  specifier: ^5.101.1
628
628
  version: 5.101.2(vue@3.5.39(typescript@5.9.3))
629
+ '@tanstack/vue-table':
630
+ specifier: ^8.21.3
631
+ version: 8.21.3(vue@3.5.39(typescript@5.9.3))
629
632
  openapi3-ts:
630
633
  specifier: ^4.6.0
631
634
  version: 4.6.0
@@ -639,8 +642,8 @@ importers:
639
642
  specifier: npm:@cabloy/zod@4.3.8
640
643
  version: '@cabloy/zod@4.3.8'
641
644
  zova:
642
- specifier: ^5.1.153
643
- version: 5.1.153(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3))
645
+ specifier: ^5.1.155
646
+ version: 5.1.155(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3))
644
647
  zova-jsx:
645
648
  specifier: ^1.1.88
646
649
  version: 1.1.88(typescript@5.9.3)
@@ -657,8 +660,8 @@ importers:
657
660
  specifier: ^5.1.34
658
661
  version: 5.1.34
659
662
  zova-module-a-form:
660
- specifier: ^5.1.48
661
- version: 5.1.48(vue@3.5.39(typescript@5.9.3))
663
+ specifier: ^5.1.50
664
+ version: 5.1.50(vue@3.5.39(typescript@5.9.3))
662
665
  zova-module-a-icon:
663
666
  specifier: ^5.1.27
664
667
  version: 5.1.27
@@ -666,11 +669,11 @@ importers:
666
669
  specifier: ^5.1.31
667
670
  version: 5.1.31
668
671
  zova-module-a-model:
669
- specifier: ^5.1.35
670
- version: 5.1.35(vue@3.5.39(typescript@5.9.3))
672
+ specifier: ^5.1.36
673
+ version: 5.1.36(vue@3.5.39(typescript@5.9.3))
671
674
  zova-module-a-openapi:
672
- specifier: ^5.1.50
673
- version: 5.1.50
675
+ specifier: ^5.1.51
676
+ version: 5.1.51
674
677
  zova-module-a-pay:
675
678
  specifier: ^5.0.7
676
679
  version: 5.0.7
@@ -693,8 +696,8 @@ importers:
693
696
  specifier: ^5.0.5
694
697
  version: 5.0.5
695
698
  zova-module-rest-resource:
696
- specifier: ^5.1.46
697
- version: 5.1.46
699
+ specifier: ^5.1.49
700
+ version: 5.1.49
698
701
 
699
702
  .zova-rest/cabloy-commerce:
700
703
  dependencies:
@@ -1610,7 +1613,7 @@ importers:
1610
1613
  specifier: ^5.0.3
1611
1614
  version: link:modules/a-markdown
1612
1615
  vona-module-a-rbac:
1613
- specifier: ^5.0.2
1616
+ specifier: ^5.0.4
1614
1617
  version: link:modules/a-rbac
1615
1618
  vona-module-a-socket:
1616
1619
  specifier: workspace:^
@@ -8977,9 +8980,6 @@ packages:
8977
8980
  zova-module-a-fetch@5.1.25:
8978
8981
  resolution: {integrity: sha512-kbMB/jIynN0dhivEZLLaY+ic9mU0rZJBeyjXwzAiWuOS330YsxipasxBZhACJD8s29RmLC9EQajI+vVLoBEveQ==}
8979
8982
 
8980
- zova-module-a-form@5.1.48:
8981
- resolution: {integrity: sha512-/YuNFocyP3P6THVrSebi1jRm5+og7YOleBWc7gPzqKUFWTmxN7XBbomRidQWXSRenSd7AWBjNgO3OajhOdswlw==}
8982
-
8983
8983
  zova-module-a-form@5.1.49:
8984
8984
  resolution: {integrity: sha512-p/faHaDELk2xH4TbuXQ9E+vIUZ4yZYS4URFF0HtrJ+bXDBMCJmjOl3H0encedLh9ZS0dGYpvejHPh3nLvw27JA==}
8985
8985
 
@@ -9055,18 +9055,12 @@ packages:
9055
9055
  zova-module-rest-resource@5.1.49:
9056
9056
  resolution: {integrity: sha512-tLf3BLov4GnV1QPivvLro6T2sNdriDZ8vZMsghuUN7dl+PRCG2pI0q9hBCrqe2faRSIcVUXowwnEbEynmaM7Zg==}
9057
9057
 
9058
- zova-suite-a-zova@5.1.152:
9059
- resolution: {integrity: sha512-P/dcMb2fcMpnmDMlrgvpddWUbupnvohD1Jp60VYWoqI0JcWMlYTJvWQWrwszDqiMvMiOTH5mmQBTws0KzbsXpA==}
9060
-
9061
9058
  zova-suite-a-zova@5.1.153:
9062
9059
  resolution: {integrity: sha512-Q7R6GL5O/Tlqk4cnFYumMR2eeCwAqfnTtFrYQuuQMpz7BmYhAPmMmJQWF2reHlRjG6B4dbOPZ5pxl1iWagY7PA==}
9063
9060
 
9064
9061
  zova-suite-a-zova@5.1.154:
9065
9062
  resolution: {integrity: sha512-ii1qFxaBNc4PpNSj+sKgiH0Ryefr4KE0Gb5M8o2Df0hoe8//3uM7QK02GyQDKAK+bu6Q+7mwBZFW8hcD8TrEYw==}
9066
9063
 
9067
- zova@5.1.153:
9068
- resolution: {integrity: sha512-2c+4lhgZgIzM6a066eRU/Ru6CvrSTwRsq1SgsSj+NuLOxUXoj+JPvwUezuRRmBCTukpRMd2nfw8u6vWHzVArTA==}
9069
-
9070
9064
  zova@5.1.154:
9071
9065
  resolution: {integrity: sha512-QF0f+wwLeuf18hsmZj1/3G/AQ9cNQYMKZiCoBBsTDh7oIhaxsmI5wxtjo59HiOEkk5ZmWPHqwjA4iepTLoKqeg==}
9072
9066
 
@@ -15411,14 +15405,6 @@ snapshots:
15411
15405
  - debug
15412
15406
  - supports-color
15413
15407
 
15414
- zova-module-a-form@5.1.48(vue@3.5.39(typescript@5.9.3)):
15415
- dependencies:
15416
- '@tanstack/vue-form': 1.33.2(vue@3.5.39(typescript@5.9.3))
15417
- '@tanstack/vue-store': 0.11.0(vue@3.5.39(typescript@5.9.3))
15418
- transitivePeerDependencies:
15419
- - '@vue/composition-api'
15420
- - vue
15421
-
15422
15408
  zova-module-a-form@5.1.49(vue@3.5.39(typescript@5.9.3)):
15423
15409
  dependencies:
15424
15410
  '@tanstack/vue-form': 1.33.2(vue@3.5.39(typescript@5.9.3))
@@ -15546,40 +15532,6 @@ snapshots:
15546
15532
 
15547
15533
  zova-module-rest-resource@5.1.49: {}
15548
15534
 
15549
- zova-suite-a-zova@5.1.152(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3)):
15550
- dependencies:
15551
- zova-module-a-api: 5.1.22
15552
- zova-module-a-app: 5.1.26
15553
- zova-module-a-bean: 5.1.33
15554
- zova-module-a-behavior: 5.1.26
15555
- zova-module-a-behaviors: 5.1.22
15556
- zova-module-a-boundary: 5.1.22
15557
- zova-module-a-command: 5.1.34
15558
- zova-module-a-fetch: 5.1.25
15559
- zova-module-a-form: 5.1.49(vue@3.5.39(typescript@5.9.3))
15560
- zova-module-a-icon: 5.1.27
15561
- zova-module-a-interceptor: 5.1.31
15562
- zova-module-a-logger: 5.1.28
15563
- zova-module-a-meta: 5.1.22
15564
- zova-module-a-model: 5.1.35(vue@3.5.39(typescript@5.9.3))
15565
- zova-module-a-openapi: 5.1.50
15566
- zova-module-a-router: 5.1.34
15567
- zova-module-a-routerstack: 5.1.27
15568
- zova-module-a-routertabs: 5.1.34
15569
- zova-module-a-ssr: 5.1.38
15570
- zova-module-a-ssrhmr: 5.1.23
15571
- zova-module-a-ssrserver: 5.1.26
15572
- zova-module-a-style: 5.1.35
15573
- zova-module-a-table: 5.1.38(vue@3.5.39(typescript@5.9.3))
15574
- zova-module-a-zod: 5.1.37
15575
- zova-module-a-zova: 5.1.103(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3))
15576
- transitivePeerDependencies:
15577
- - '@vue/composition-api'
15578
- - debug
15579
- - supports-color
15580
- - typescript
15581
- - vue
15582
-
15583
15535
  zova-suite-a-zova@5.1.153(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3)):
15584
15536
  dependencies:
15585
15537
  zova-module-a-api: 5.1.22
@@ -15590,13 +15542,13 @@ snapshots:
15590
15542
  zova-module-a-boundary: 5.1.22
15591
15543
  zova-module-a-command: 5.1.34
15592
15544
  zova-module-a-fetch: 5.1.25
15593
- zova-module-a-form: 5.1.49(vue@3.5.39(typescript@5.9.3))
15545
+ zova-module-a-form: 5.1.50(vue@3.5.39(typescript@5.9.3))
15594
15546
  zova-module-a-icon: 5.1.27
15595
15547
  zova-module-a-interceptor: 5.1.31
15596
15548
  zova-module-a-logger: 5.1.28
15597
15549
  zova-module-a-meta: 5.1.22
15598
- zova-module-a-model: 5.1.35(vue@3.5.39(typescript@5.9.3))
15599
- zova-module-a-openapi: 5.1.50
15550
+ zova-module-a-model: 5.1.36(vue@3.5.39(typescript@5.9.3))
15551
+ zova-module-a-openapi: 5.1.51
15600
15552
  zova-module-a-router: 5.1.34
15601
15553
  zova-module-a-routerstack: 5.1.27
15602
15554
  zova-module-a-routertabs: 5.1.34
@@ -15648,17 +15600,6 @@ snapshots:
15648
15600
  - typescript
15649
15601
  - vue
15650
15602
 
15651
- zova@5.1.153(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3)):
15652
- dependencies:
15653
- zova-core: 5.1.88(patch_hash=144450bd7a567a6067e5857c0cf4c780981b7e90bc48745c09e1696a9b6f1b97)(typescript@5.9.3)
15654
- zova-suite-a-zova: 5.1.152(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3))
15655
- transitivePeerDependencies:
15656
- - '@vue/composition-api'
15657
- - debug
15658
- - supports-color
15659
- - typescript
15660
- - vue
15661
-
15662
15603
  zova@5.1.154(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3)):
15663
15604
  dependencies:
15664
15605
  zova-core: 5.1.88(patch_hash=144450bd7a567a6067e5857c0cf4c780981b7e90bc48745c09e1696a9b6f1b97)(typescript@5.9.3)
@@ -1,6 +1,7 @@
1
1
  export default {
2
2
  Available: 'Available',
3
3
  Category: 'Category',
4
+ CreateProduct: 'Create product',
4
5
  Description: 'Description',
5
6
  DescriptionHtml: 'Product description (HTML)',
6
7
  DescriptionMarkdown: 'Product description (Markdown)',
@@ -15,6 +16,8 @@ export default {
15
16
  Unpublished: 'Unpublished',
16
17
  Sku: 'SKU',
17
18
  SkuAttributeName: 'SKU attribute name',
19
+ UpdateProduct: 'Update product',
20
+ ViewProduct: 'Product details',
18
21
  SkuAttributeValue: 'SKU attribute value',
19
22
  SkuAttributes: 'SKU attributes',
20
23
  SkuCode: 'SKU code',