cabloy 5.1.158 → 5.1.160
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-contract-loop/SKILL.md +24 -7
- package/.claude/skills/cabloy-contract-loop/evals/evals.json +12 -0
- package/.claude/skills/cabloy-contract-loop/references/contract-loop-map.md +28 -0
- package/.claude/skills/cabloy-contract-loop/references/verification-checklist.md +19 -2
- package/.claude/skills/cabloy-resource-field-update/SKILL.md +18 -0
- package/.claude/skills/cabloy-zova-source-reading/SKILL.md +14 -0
- package/.claude/skills/cabloy-zova-source-reading/references/core-reading-paths.md +10 -0
- package/.github/workflows/docs-pages.yml +18 -2
- package/CHANGELOG.md +31 -0
- package/package.json +2 -1
- package/repo-docs/.vitepress/config.mjs +45 -4
- package/repo-docs/.vitepress/theme/components/GitHubRepositoriesNav.vue +255 -0
- package/repo-docs/.vitepress/theme/custom.css +21 -0
- package/repo-docs/.vitepress/theme/index.js +7 -1
- package/repo-docs/backend/department-management.md +98 -0
- package/repo-docs/backend/menu-authorization.md +124 -0
- package/repo-docs/backend/rbac-authorization.md +117 -0
- package/repo-docs/backend/resource-field-update.md +49 -2
- package/repo-docs/backend/role-management.md +92 -0
- package/repo-docs/backend/shared-rbac-architecture.md +339 -0
- package/repo-docs/backend/user-management.md +92 -0
- package/repo-docs/blogs/index.md +2 -0
- package/repo-docs/frontend/form-layout-guide.md +6 -6
- package/repo-docs/frontend/model-resource-owner-pattern.md +2 -0
- package/repo-docs/frontend/page-guide.md +12 -0
- package/repo-docs/frontend/page-meta-guide.md +27 -0
- package/repo-docs/frontend/permission-formscene-action-visibility-guide.md +5 -3
- package/repo-docs/frontend/ssr-architecture-overview.md +4 -0
- package/repo-docs/frontend/ssr-build-deploy-guide.md +1 -1
- package/repo-docs/frontend/table-action-visibility-permission-flow-guide.md +2 -0
- package/repo-docs/frontend/table-guide.md +75 -22
- package/repo-docs/frontend/table-resource-crud-cookbook.md +25 -4
- package/repo-docs/frontend/zova-form-source-reading-map.md +2 -2
- package/repo-docs/frontend/zova-form-under-the-hood.md +1 -1
- package/repo-docs/frontend/zova-reactivity-under-the-hood.md +65 -9
- package/repo-docs/frontend/zova-table-source-reading-map.md +54 -19
- package/repo-docs/frontend/zova-table-under-the-hood.md +70 -39
- package/repo-docs/fullstack/contract-loop-playbook.md +2 -0
- package/repo-docs/fullstack/ssr-site-and-flavor-setup.md +198 -0
- package/repo-docs/fullstack/vona-zova-integration.md +2 -0
- package/repo-e2e/docs/playwright.config.ts +37 -0
- package/repo-e2e/docs/specs/blogs-index.spec.ts +180 -0
- package/repo-e2e/docs/specs/github-repositories-nav.spec.ts +69 -0
- package/repo-e2e/docs/test-results/.last-run.json +4 -0
- package/repo-e2e/specs/cabloy-basic.spec.ts +136 -2
- package/test-results/.last-run.json +20 -2
- package/test-results/a-commerce-ATP-ADDR-01-aut-11dea-ss-through-Web-self-service/error-context.md +238 -0
- package/test-results/a-commerce-ATP-SPC-01-Coup-ef380-mantic-Admin-field-controls/error-context.md +240 -0
- package/test-results/a-commerce-ATP-SPC-02-Cate-9f120-on-and-publication-controls/error-context.md +238 -0
- package/test-results/a-commerce-ATP-SPC-02-Prod-5f92a-on-and-publication-controls/error-context.md +238 -0
- package/test-results/a-commerce-ATP-SPC-02-SKU--2e61e-ency-and-lifecycle-controls/error-context.md +238 -0
- package/test-results/a-commerce-ATP-SPC-04-Stoc-fa895--readonly-and-mutation-free/error-context.md +235 -0
- package/test-results/a-commerce-ATP-SPC-05-syst-fb406-e-without-mutation-controls/error-context.md +238 -0
- package/test-results/a-commerce-Commerce-sessio-4e963-ie-selects-raw-server-theme/error-context.md +226 -0
- package/test-results/a-commerce-Commerce-theme--062fe--without-hydration-mismatch/error-context.md +229 -0
- package/test-results/a-commerce-PayPal-browser--8672c-t-or-open-an-awaiting-order/error-context.md +238 -0
- package/test-results/a-commerce-Payment-callbac-f56c2--reconciles-after-hydration/error-context.md +238 -0
- package/test-results/a-commerce-Payment-cancell-3fc74-ified-provider-confirmation/error-context.md +238 -0
- package/test-results/a-commerce-Phase-50-60-aut-71d61--observes-operator-shipment/error-context.md +238 -0
- package/test-results/a-commerce-Phase-60-custom-407cd-ecutes-a-whole-order-refund/error-context.md +238 -0
- package/test-results/cabloy-basic-ATP-BASIC-TAB-e7253-and-pins-configured-columns/error-context.md +240 -0
- package/test-results/markdown-ATP-SPC-02-Produc-9d5c2-link-toolbar-edits-Markdown/error-context.md +211 -0
- package/test-results/markdown-ATP-SPC-02-Produc-9d6ea-t-editor-and-saves-Markdown/error-context.md +238 -0
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/entity/{{resourceName}}.tsx_ +1 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/entity/{{resourceName}}.tsx_ +1 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/pnpm-lock.yaml +79 -52
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/en-us.ts +3 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/zh-cn.ts +3 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productCreate.tsx +2 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productUpdate.tsx +2 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productView.tsx +2 -0
- package/vona/src/suite/a-training/modules/training-record/src/dto/recordCreate.tsx +1 -0
- package/vona/src/suite/a-training/modules/training-record/src/dto/recordSelectReq.tsx +3 -7
- package/vona/src/suite/a-training/modules/training-record/src/dto/recordUpdate.tsx +1 -0
- package/vona/src/suite/a-training/modules/training-record/src/dto/recordView.tsx +1 -0
- package/vona/src/suite/a-training/modules/training-record/src/entity/record.tsx +8 -0
- package/vona/src/suite/a-training/modules/training-record/test/record.test.ts +51 -0
- 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 +1 -0
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentSelectResItem.tsx +1 -0
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentUpdate.tsx +1 -0
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentView.tsx +1 -0
- package/vona/src/suite/a-training/modules/training-student/src/entity/student.tsx +10 -1
- package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +99 -1
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-web/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-web/src/bean/pipe.filter.ts +85 -57
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/packages-cli/cli/package.json +2 -2
- package/zova/packages-cli/cli-set-front/cli/templates/rest/render.ts +2 -0
- package/zova/packages-cli/cli-set-front/cli/templates/rest/rest.ts +13 -3
- package/zova/packages-cli/cli-set-front/package.json +1 -1
- package/zova/packages-zova/zova/package.json +2 -2
- package/zova/pnpm-lock.yaml +16 -16
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockPage/controller.tsx +31 -11
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockTable/controller.tsx +3 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/component/blockPageEntry/controller.tsx +1 -1
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/table/render.tsx +68 -8
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +4 -0
- package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
- 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 +0 -6
- package/zova/src/suite-vendor/a-zova/modules/a-form/test/lib/formLayout.test.ts +16 -9
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/model/sdk.ts +4 -0
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/rest.ts +13 -2
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/schema.ts +1 -0
- package/zova/src/suite-vendor/a-zova/modules/a-table/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-table/src/component/table/controller.tsx +50 -0
- package/zova/src/suite-vendor/a-zova/modules/a-table/src/component/table/render.tsx +60 -7
- package/zova/src/suite-vendor/a-zova/modules/a-table/src/types/table.ts +6 -4
- package/zova/src/suite-vendor/a-zova/package.json +4 -4
|
@@ -58,13 +58,14 @@ This page is that bridge.
|
|
|
58
58
|
|
|
59
59
|
For a typical Zova table, the shortest accurate model is:
|
|
60
60
|
|
|
61
|
-
1.
|
|
62
|
-
2. the
|
|
63
|
-
3. the controller
|
|
64
|
-
4. the controller
|
|
65
|
-
5.
|
|
66
|
-
6.
|
|
67
|
-
7.
|
|
61
|
+
1. backend schema helpers such as `ZovaRender.column(...)` attach table-column metadata to the field contract
|
|
62
|
+
2. the `ZTable` wrapper creates a table controller bean through the normal Zova controller path
|
|
63
|
+
3. the table controller loads effective table-scene schema properties from the row schema
|
|
64
|
+
4. the controller builds table metadata with visible properties and per-column render functions
|
|
65
|
+
5. the controller creates TanStack table options through Zova’s `$useTable(...)` wrapper
|
|
66
|
+
6. each cell render resolves either to text fallback, a general JSX render target, or a `tableCell` bean
|
|
67
|
+
7. the cell runtime evaluates JSX/CEL props with table-aware column and cell scope
|
|
68
|
+
8. resource pages feed schema, data, permissions, and page scope into the same table runtime through `basic-page:blockTable`
|
|
68
69
|
|
|
69
70
|
That is why Zova Table is not only a thin wrapper around TanStack Table. The business-facing runtime surface is still Zova-native.
|
|
70
71
|
|
|
@@ -98,25 +99,27 @@ These three files already show the core architecture:
|
|
|
98
99
|
|
|
99
100
|
When you want to trace the full mechanism, read these files in order:
|
|
100
101
|
|
|
101
|
-
1. `zova/
|
|
102
|
-
2. `zova/src/suite-vendor/a-zova/modules/a-table/src/component/table
|
|
103
|
-
3. `zova/src/suite-vendor/a-zova/modules/a-table/src/
|
|
104
|
-
4. `zova/src/suite-vendor/a-zova/modules/a-table/src/
|
|
105
|
-
5. `zova/src/suite-vendor/a-zova/modules/a-
|
|
106
|
-
6. `zova/src/suite-vendor/a-zova/modules/a-
|
|
107
|
-
7. `zova/src/suite/
|
|
108
|
-
8. `zova/src/suite/cabloy-basic/modules/basic-page/src/component/
|
|
102
|
+
1. `zova/packages-cli/cli-set-front/cli/templates/rest/rest.ts`
|
|
103
|
+
2. `zova/src/suite-vendor/a-zova/modules/a-table/src/.metadata/component/table.ts`
|
|
104
|
+
3. `zova/src/suite-vendor/a-zova/modules/a-table/src/component/table/controller.tsx`
|
|
105
|
+
4. `zova/src/suite-vendor/a-zova/modules/a-table/src/lib/beanControllerTableBase.ts`
|
|
106
|
+
5. `zova/src/suite-vendor/a-zova/modules/a-table/src/component/table/render.tsx`
|
|
107
|
+
6. `zova/src/suite-vendor/a-zova/modules/a-openapi/src/lib/schema.ts`
|
|
108
|
+
7. `zova/src/suite-vendor/a-zova/modules/a-table/src/types/tableCell.ts`
|
|
109
|
+
8. `zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockTable/controller.tsx`
|
|
110
|
+
9. `zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockPage/controller.tsx`
|
|
109
111
|
|
|
110
112
|
A compact role map is:
|
|
111
113
|
|
|
114
|
+
- `cli/templates/rest/rest.ts` shows that `ZovaRender.column(...)` stores its options under `rest.table`
|
|
112
115
|
- `table.ts` shows how the public wrapper enters `useController(...)`
|
|
113
|
-
- `component/table/controller.tsx` owns schema properties, metadata refresh, TanStack bridge, and cell rendering
|
|
116
|
+
- `component/table/controller.tsx` owns schema properties, metadata refresh, TanStack bridge, pinning, sorting eligibility, and cell rendering
|
|
114
117
|
- `beanControllerTableBase.ts` shows the Zova wrapper around `useVueTable(...)`
|
|
115
|
-
- `component/table/render.tsx` shows the default table DOM render path
|
|
116
|
-
- `schema.ts` shows how table-scene
|
|
118
|
+
- `component/table/render.tsx` shows the default table DOM render path, layout metadata, sortable headers, and `FlexRender`
|
|
119
|
+
- `schema.ts` shows how top-level and table-scene metadata are merged and ordered
|
|
117
120
|
- `types/tableCell.ts` shows the `tableCell` scene contract
|
|
118
|
-
- `blockTable/controller.tsx` shows how page blocks feed
|
|
119
|
-
- `blockPage/controller.tsx` shows where resource data, permissions,
|
|
121
|
+
- `blockTable/controller.tsx` shows how Basic page blocks feed data, schemas, scope, and sorting state into `ZTable`
|
|
122
|
+
- `blockPage/controller.tsx` shows where resource data, permissions, page scope, and backend `orders` mapping come from
|
|
120
123
|
|
|
121
124
|
## Step-by-step runtime path
|
|
122
125
|
|
|
@@ -205,16 +208,19 @@ zova/src/suite-vendor/a-zova/modules/a-openapi/src/lib/schema.ts
|
|
|
205
208
|
|
|
206
209
|
The important runtime path is:
|
|
207
210
|
|
|
208
|
-
1. the
|
|
209
|
-
2.
|
|
210
|
-
3. `
|
|
211
|
-
4. `
|
|
212
|
-
5. `
|
|
211
|
+
1. `ZovaRender.column(options)` stores the field's column options under `rest.table`
|
|
212
|
+
2. the table receives `schema`
|
|
213
|
+
3. `_createProperties()` computes `this.$sdk.loadSchemaProperties(this.schema, 'table')`
|
|
214
|
+
4. `loadSchemaProperties(...)` resolves `$ref`, merges shared `rest` metadata with the `rest.table` overlay, and sorts by effective `rest.order`
|
|
215
|
+
5. `_createTableMeta()` iterates those properties and decides visibility and render behavior
|
|
216
|
+
6. `_createColumnsMiddle()` converts the surviving properties into TanStack column definitions
|
|
217
|
+
|
|
218
|
+
`ZovaRender.column(...)` is a shared Zova API available in both Cabloy Basic and Cabloy Start. It supplies metadata only; it neither registers a frontend column nor selects a cell renderer. `ZovaRender.cell(...)` supplies the render target, and `tableCell` beans implement reusable cell behavior.
|
|
213
219
|
|
|
214
220
|
A practical reading takeaway is:
|
|
215
221
|
|
|
216
222
|
- **schema is not only validation truth**
|
|
217
|
-
- **schema also drives table order, visibility, and cell render metadata**
|
|
223
|
+
- **schema also drives table order, physical-column behavior, visibility, and cell render metadata**
|
|
218
224
|
|
|
219
225
|
## 5. How table metadata is built
|
|
220
226
|
|
|
@@ -257,6 +263,21 @@ A practical reading takeaway is:
|
|
|
257
263
|
- **TanStack owns the row-model mechanics**
|
|
258
264
|
- **the controller still owns which data and columns TanStack sees**
|
|
259
265
|
|
|
266
|
+
### Column metadata becomes TanStack and DOM behavior
|
|
267
|
+
|
|
268
|
+
When `_createColumnsMiddle()` creates each surviving property column, it maps effective table metadata as follows:
|
|
269
|
+
|
|
270
|
+
| Metadata | Controller and render behavior |
|
|
271
|
+
| -------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
272
|
+
| `rest.order` | Orders properties during schema-property loading. |
|
|
273
|
+
| `rest.width` | Becomes the TanStack `size`; render beans apply pixel `width` and `min-width`. |
|
|
274
|
+
| `rest.align` | Remains in column metadata; render beans apply header/cell text alignment. |
|
|
275
|
+
| `rest.fixed` | Builds left/right TanStack pinning arrays; render beans use sticky positions and calculated offsets. |
|
|
276
|
+
| `rest.enableSorting` | Enables sorting only when the field's key or aliases are also present in `schemaOrder`. |
|
|
277
|
+
| `rest.sortDescFirst` | Becomes TanStack's first-toggle direction. |
|
|
278
|
+
|
|
279
|
+
The sorting gate is intentional: metadata can request sorting, but the order schema remains the contract that authorizes the field. The standard resource-page table is controlled and `manualSorting`; it does not locally reorder the fetched data.
|
|
280
|
+
|
|
260
281
|
## 7. Column and cell render context are explicitly separated
|
|
261
282
|
|
|
262
283
|
The controller creates two related but distinct runtime contexts.
|
|
@@ -392,12 +413,12 @@ That contract defines:
|
|
|
392
413
|
The decorator itself is:
|
|
393
414
|
|
|
394
415
|
```typescript
|
|
395
|
-
createBeanDecorator('tableCell', '
|
|
416
|
+
createBeanDecorator('tableCell', 'app', true, options);
|
|
396
417
|
```
|
|
397
418
|
|
|
398
|
-
That means `tableCell` is not only a naming convention. It is
|
|
419
|
+
That means `tableCell` is not only a naming convention. It is an app-scoped frontend bean scene with:
|
|
399
420
|
|
|
400
|
-
-
|
|
421
|
+
- app-scoped reusable bean resolution
|
|
401
422
|
- scene-level typing
|
|
402
423
|
- CLI boilerplate support
|
|
403
424
|
- metadata-driven resource identity
|
|
@@ -437,9 +458,12 @@ The default render bean lives in:
|
|
|
437
458
|
zova/src/suite-vendor/a-zova/modules/a-table/src/component/table/render.tsx
|
|
438
459
|
```
|
|
439
460
|
|
|
440
|
-
It does
|
|
461
|
+
It does more than delegate vnode creation:
|
|
441
462
|
|
|
442
463
|
- render the outer table markup with `<table class="table">`
|
|
464
|
+
- apply effective column alignment, pixel width/minimum width, and fixed-column sticky positioning
|
|
465
|
+
- calculate and apply left/right pinned-column offsets
|
|
466
|
+
- render accessible sortable header buttons, `aria-sort`, and sort-state indicators when the controller exposes a sortable column
|
|
443
467
|
- delegate header and cell vnode creation to TanStack `FlexRender`
|
|
444
468
|
|
|
445
469
|
If `slotDefault` is supplied, the render bean yields to that slot instead of the built-in table DOM.
|
|
@@ -470,7 +494,8 @@ zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockTable/controll
|
|
|
470
494
|
- loads `ModelResource`
|
|
471
495
|
- creates page-level JSX/CEL environment
|
|
472
496
|
- computes resource query state
|
|
473
|
-
- exposes `data`, `schemaRow`, and `permissions`
|
|
497
|
+
- exposes `data`, `schemaRow`, `schemaOrder`, and `permissions`
|
|
498
|
+
- owns controlled table sorting and converts its single current sorting entry into backend `orders`
|
|
474
499
|
- refreshes table metadata when permissions change
|
|
475
500
|
|
|
476
501
|
### Table block path
|
|
@@ -480,6 +505,8 @@ zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockTable/controll
|
|
|
480
505
|
- renders `ZTable`
|
|
481
506
|
- passes `data={$$page.data}`
|
|
482
507
|
- passes `schema={$$page.schemaRow}`
|
|
508
|
+
- passes `schemaOrder={$$page.schemaOrder}`
|
|
509
|
+
- passes controlled `sorting` and `onSortingChange`
|
|
483
510
|
- passes `tableScope={$$page.jsxCelScope}`
|
|
484
511
|
- captures `controllerRef` and stores `tableRef` back onto the page controller
|
|
485
512
|
|
|
@@ -487,18 +514,22 @@ This is one of the most important integration facts about the module.
|
|
|
487
514
|
|
|
488
515
|
The resource page does not manually rebuild the table runtime. It feeds page-owned resource state into the same reusable table controller.
|
|
489
516
|
|
|
517
|
+
This section uses Cabloy Basic `basic-page` paths as the concrete resource-page specimen. The core `ZovaRender.column(...)` metadata contract and the base Zova Table controller behavior are shared by Cabloy Basic and Cabloy Start; resolve Start-specific page modules and renderer keys from the active Start repository.
|
|
518
|
+
|
|
490
519
|
## 14. Compact call-flow sketch
|
|
491
520
|
|
|
492
521
|
When in doubt, use this short call flow:
|
|
493
522
|
|
|
494
|
-
1. `
|
|
495
|
-
2. `
|
|
496
|
-
3. `
|
|
497
|
-
4. `
|
|
498
|
-
5. `
|
|
499
|
-
6.
|
|
500
|
-
7.
|
|
501
|
-
8.
|
|
523
|
+
1. `ZovaRender.column(...)` contributes `rest.table` metadata to the backend/OpenAPI field contract
|
|
524
|
+
2. `ZTable` wrapper enters the normal Zova controller path
|
|
525
|
+
3. `ControllerTable.__init__()` creates CEL/JSX support and schema-driven properties
|
|
526
|
+
4. `refreshMeta()` computes visible table properties, column pinning, and per-column render functions
|
|
527
|
+
5. `_createTable()` creates the TanStack bridge through `$useTable(...)`
|
|
528
|
+
6. `RenderTable` applies column metadata and renders headers and rows through `FlexRender`
|
|
529
|
+
7. each cell render resolves to text fallback, a general render target, or a `tableCell` bean
|
|
530
|
+
8. `tableCell` beans receive controller-prepared options, scope, and `next()`
|
|
531
|
+
9. resource pages prepare `data`, `schemaRow`, `schemaOrder`, sorting state, permissions, and `tableScope` before entering the same runtime
|
|
532
|
+
10. a resource-page sort becomes backend `orders`; TanStack does not locally sort the fetched page
|
|
502
533
|
|
|
503
534
|
That is the shortest end-to-end explanation of how the module cooperates.
|
|
504
535
|
|
|
@@ -146,6 +146,8 @@ Then use the **reverse chain**:
|
|
|
146
146
|
|
|
147
147
|
See [Frontend Metadata Back to Backend](/fullstack/frontend-metadata-to-backend) for the end-to-end reverse-chain bridge from frontend-owned truth to backend-visible shared handoff.
|
|
148
148
|
|
|
149
|
+
When the frontend change creates a new independently mounted SSR site rather than refreshing an existing consumer, use [Independent SSR Site and Flavor Setup](/fullstack/ssr-site-and-flavor-setup). The new Vona consumer needs a matched flavor, paired SSR/REST build wrapper, site registration, and dispatch/hydration proof.
|
|
150
|
+
|
|
149
151
|
### 3. Do generated artifacts look stale?
|
|
150
152
|
|
|
151
153
|
Examples:
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# Independent SSR Site and Flavor Setup
|
|
2
|
+
|
|
3
|
+
Use this guide when adding a new deployable Zova SSR surface that Vona must dispatch and serve. It covers a new **site + flavor** pair, not merely a page or route inside an existing site.
|
|
4
|
+
|
|
5
|
+
The durable outcome is one aligned identity tuple:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
Zova flavor
|
|
9
|
+
↕
|
|
10
|
+
flavor env/config + Zova SSR/REST scripts
|
|
11
|
+
↕
|
|
12
|
+
root paired build wrapper
|
|
13
|
+
↕
|
|
14
|
+
SSR release directory + generated REST package
|
|
15
|
+
↕
|
|
16
|
+
Vona @SsrSite registration
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Do not substitute identifiers from an existing Admin, Web, or business site. Select and verify every member of the tuple together.
|
|
20
|
+
|
|
21
|
+
## Before you start
|
|
22
|
+
|
|
23
|
+
### Detect the active edition
|
|
24
|
+
|
|
25
|
+
Read the repository marker first:
|
|
26
|
+
|
|
27
|
+
- `__CABLOY_BASIC__` → use the current Basic scripts, flavors, UI baselines, site modules, and paths.
|
|
28
|
+
- `__CABLOY_START__` → inspect the active Start repository before naming a flavor, wrapper, generated package, or site module.
|
|
29
|
+
- both markers → stop: the checkout is ambiguous.
|
|
30
|
+
|
|
31
|
+
The framework model is shared, but the exact flavor names, site baselines, UI layer, assets, scripts, and generated output paths can differ by edition. See [Edition Detection](/editions/detection) and [Edition Collaboration Differences](/fullstack/edition-collaboration-differences).
|
|
32
|
+
|
|
33
|
+
### Confirm that a new site is needed
|
|
34
|
+
|
|
35
|
+
| Change | Normal boundary |
|
|
36
|
+
| ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
|
|
37
|
+
| New page or route inside an existing SSR site | Page, route, model, and existing site verification; normally no new flavor or `@SsrSite`. |
|
|
38
|
+
| Frontend metadata/resource handoff to an existing backend consumer | Reverse contract loop; rebuild every affected existing flavor pair, then run `npm run deps:vona`. |
|
|
39
|
+
| New deployable URL mount, SSR admission policy, assets, or independent frontend composition | New independent flavor and Vona SSR site; continue with this guide. |
|
|
40
|
+
|
|
41
|
+
A new site is an ownership decision, not a convenient alias for an existing Admin or Web surface.
|
|
42
|
+
|
|
43
|
+
## Define the identity tuple
|
|
44
|
+
|
|
45
|
+
Choose these values before editing source. Keep their spelling and ownership consistent:
|
|
46
|
+
|
|
47
|
+
| Identity | Role |
|
|
48
|
+
| ---------------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
49
|
+
| `<site-flavor>` | Exact Zova flavor identifier used in env/config, lower-level scripts, build output, and generated REST package. |
|
|
50
|
+
| `<site-id>` | Vona SSR site identity and Zova `SITE_ID`; unique among enabled sites. |
|
|
51
|
+
| `<public-path>` | External mount path; aligned between Zova `APP_PUBLIC_PATH` and Vona `publicPath`; unique among enabled sites. |
|
|
52
|
+
| `<site-module>` | Vona module that owns the site bean and copied SSR assets. |
|
|
53
|
+
| `<bundle-path>` | Copied SSR release directory under `<site-module>/assets/site`; exactly matches Vona `bundlePath`. |
|
|
54
|
+
| `<zova-rest-package>` | Flavor-specific generated REST/type package imported by the Vona site bean. |
|
|
55
|
+
| `<root-build-wrapper>` | Root command that produces the selected SSR bundle **and** REST/type package. |
|
|
56
|
+
|
|
57
|
+
Only one enabled SSR site may own the empty/root public path. A non-root site’s route aliases are relative to the router mount base; do not repeat `<public-path>` inside those aliases.
|
|
58
|
+
|
|
59
|
+
## Configure Zova
|
|
60
|
+
|
|
61
|
+
### 1. Add a tracked flavor environment
|
|
62
|
+
|
|
63
|
+
Keep shared defaults in `zova/env/.env`; add the site’s tracked flavor override at:
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
zova/env/.env.<site-flavor>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The override must select a non-empty `SITE_ID`, the matching `APP_PUBLIC_PATH`, an SSR rendering profile, and the build-copy targets. For a Vona-integrated SSR site, set `SSR_WITH_VONA=true`.
|
|
70
|
+
|
|
71
|
+
Choose `SSR_PROFILE` from the rendering contract:
|
|
72
|
+
|
|
73
|
+
- use a public profile for cookie-free, cache-safe first paint;
|
|
74
|
+
- choose a session/private profile deliberately for cookie-backed admission, personalized first paint, or private SSR data.
|
|
75
|
+
|
|
76
|
+
Point the build destinations to the selected Vona site module and generated package workspace:
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
BUILD_COPY_RELEASE = ../vona/src/.../modules/<site-module>/assets/site
|
|
80
|
+
BUILD_REST_COPY_DIST = ../vona/.zova-rest
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Do not change ports or worktree-local environment overrides to make a new site work. Follow the normal worktree-environment workflow only when that separate setup is explicitly required.
|
|
84
|
+
|
|
85
|
+
### 2. Add flavor-specific frontend configuration only where necessary
|
|
86
|
+
|
|
87
|
+
Use `zova/src/front/config/config/config.<site-flavor>.ts` for site-specific route aliases, route exclusions, layouts, or app configuration. Keep shared API, locale, theme, SSR, and app assembly in base `config.ts`.
|
|
88
|
+
|
|
89
|
+
For mounted routing:
|
|
90
|
+
|
|
91
|
+
- configure an unnamed canonical route through `config.routes.path`;
|
|
92
|
+
- configure a named dynamic route through `config.routes.name`;
|
|
93
|
+
- generate a named alias with `$router.getAliasPath(...)` rather than assuming `$router.getPagePath(...)` selects an alias;
|
|
94
|
+
- do not duplicate the external mount prefix in an alias because `APP_PUBLIC_PATH` supplies that boundary.
|
|
95
|
+
|
|
96
|
+
Choose layouts, title, locales, assets, route exclusions, `requiresAuth`, and redirects from the new site’s contract. Existing Admin and Web sites are specimens, not defaults to copy blindly.
|
|
97
|
+
|
|
98
|
+
### 3. Add durable scripts and root wrappers
|
|
99
|
+
|
|
100
|
+
Inspect the active root `package.json` and `zova/package.json` first. Add lower-level Zova scripts in the durable source manifest, normally including:
|
|
101
|
+
|
|
102
|
+
```text
|
|
103
|
+
dev:ssr:<site-flavor>
|
|
104
|
+
build:ssr:<site-flavor>
|
|
105
|
+
build:rest:<site-flavor>
|
|
106
|
+
preview:ssr:<site-flavor>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The SSR and REST scripts must pass the same flavor. Add a paired batch and an explicit root build wrapper that runs both outputs in order:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
npm run build:ssr:<site-flavor>
|
|
113
|
+
npm run build:rest:<site-flavor>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Use the root wrapper for normal site-level builds and in Vona diagnostics. Add the flavor to an aggregate build only when it belongs to that edition’s default shipped artifact set.
|
|
117
|
+
|
|
118
|
+
Edit durable manifests such as root `package.json` and `zova/package.original.json`; do not hand-edit generated `zova/package.json`, generated REST output, copied bundles, or generated dependency state. Do not run `npm run init` merely to synchronize a manifest.
|
|
119
|
+
|
|
120
|
+
## Register the Vona SSR site
|
|
121
|
+
|
|
122
|
+
Create or refine an independently packaged Vona site module. It must expose its assets, source entry point, locale metadata, and an SSR-site bean.
|
|
123
|
+
|
|
124
|
+
The site bean should:
|
|
125
|
+
|
|
126
|
+
1. import `IPagePathRecord` and `IIconRecord` from `<zova-rest-package>`;
|
|
127
|
+
2. augment the SSR site-ID and public-path type records;
|
|
128
|
+
3. define typed page/page-data options around the generated route record;
|
|
129
|
+
4. extend `BeanSsrSiteBase` and register `@SsrSite(...)`;
|
|
130
|
+
5. provide a localized title, `<site-id>`, `<public-path>`, `<bundle-path>`, and `<root-build-wrapper>` diagnostics command.
|
|
131
|
+
|
|
132
|
+
Conceptually:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
@SsrSite({
|
|
136
|
+
siteId: '<site-id>',
|
|
137
|
+
publicPath: '<public-path>',
|
|
138
|
+
bundlePath: '<bundle-path>',
|
|
139
|
+
diagnostics: { buildCommand: 'npm run <root-build-wrapper>' },
|
|
140
|
+
})
|
|
141
|
+
export class SsrSiteExample extends BeanSsrSiteBase<...> {}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The default release identity is normally `ssr-<site-flavor>-<app-version>`. Unless you intentionally override it, use that exact copied directory as `<bundle-path>`. Vona resolves the bundle from the owning site module’s `assets/site/<bundle-path>` directory.
|
|
145
|
+
|
|
146
|
+
Ensure the site module is discoverable through its durable suite/module manifest. Let normal tooling generate metadata and dependency closures; do not hand-edit generated metadata.
|
|
147
|
+
|
|
148
|
+
## Build the artifact pair and sync Vona
|
|
149
|
+
|
|
150
|
+
For every Vona-consumed independent SSR site, this sequence is required:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
npm run <root-build-wrapper>
|
|
154
|
+
npm run deps:vona
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The wrapper must create both:
|
|
158
|
+
|
|
159
|
+
```text
|
|
160
|
+
SSR bundle and client assets
|
|
161
|
+
+ generated flavor REST/type package
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`build:rest:<site-flavor>` alone is not a valid Vona SSR handoff: Vona needs the bundle, assets, and typed REST package to move together. Likewise, a default Admin or Web wrapper does not prove an independently named site was rebuilt.
|
|
165
|
+
|
|
166
|
+
After generation, verify that:
|
|
167
|
+
|
|
168
|
+
- the copied release directory matches `<bundle-path>`;
|
|
169
|
+
- the generated package name and Vona import match `<zova-rest-package>`;
|
|
170
|
+
- the Vona local workspace can discover the package after `npm run deps:vona`;
|
|
171
|
+
- the site bean’s diagnostics command names the same root wrapper that produced the artifacts.
|
|
172
|
+
|
|
173
|
+
If the generated artifacts are correct and `npm run deps:vona` completed but Vona still sees stale local types, diagnose [local dependency drift](/fullstack/contract-loop-playbook#recovery-path-for-local-dependency-drift) only then.
|
|
174
|
+
|
|
175
|
+
## Verify through the Vona boundary
|
|
176
|
+
|
|
177
|
+
A standalone Zova development server can help with page iteration, but it does not prove copied artifacts, Vona site matching, generated type handoff, or production-like hydration.
|
|
178
|
+
|
|
179
|
+
Run the narrowest meaningful checks first:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
npm run tsc:zova
|
|
183
|
+
npm run <root-build-wrapper>
|
|
184
|
+
npm run deps:vona
|
|
185
|
+
pnpm --dir vona run tsc
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Then prove the Vona-served site:
|
|
189
|
+
|
|
190
|
+
1. request the exact public mount path and assert the intended Vona SSR site handled it;
|
|
191
|
+
2. inspect raw HTML for server-rendered content and the correct cache/admission behavior;
|
|
192
|
+
3. verify client assets load from the mounted site;
|
|
193
|
+
4. open the same route in a browser and assert hydration completes without errors or SSR/client mismatch;
|
|
194
|
+
5. verify canonical and alias route behavior, including no duplicated mount path;
|
|
195
|
+
6. exercise the site’s public/private admission or redirect contract;
|
|
196
|
+
7. retain focused browser/E2E evidence for the new site rather than relying only on compilation.
|
|
197
|
+
|
|
198
|
+
For more detail, read [SSR Architecture Overview](/frontend/ssr-architecture-overview), [SSR Build and Deploy Guide](/frontend/ssr-build-deploy-guide), [Vona + Zova Integration](/fullstack/vona-zova-integration), and [Contract Loop Playbook](/fullstack/contract-loop-playbook).
|
|
@@ -64,6 +64,8 @@ Because Start can differ in UI layer, module composition, SSR site baselines, an
|
|
|
64
64
|
2. the Start repo’s `package.json`
|
|
65
65
|
3. the exact Zova flavor names and generated output paths used there
|
|
66
66
|
|
|
67
|
+
For a new independently mounted SSR surface, use [Independent SSR Site and Flavor Setup](/fullstack/ssr-site-and-flavor-setup). It defines the linked Zova flavor, paired SSR/REST artifacts, Vona `@SsrSite` registration, and browser proof without assuming that either edition’s default Admin or Web wrapper applies.
|
|
68
|
+
|
|
67
69
|
## Recommended integration workflow
|
|
68
70
|
|
|
69
71
|
### 1. Detect the edition
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { defineConfig } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
const rootDir = new URL('../..', import.meta.url).pathname;
|
|
4
|
+
const baseURL = 'http://127.0.0.1:4173';
|
|
5
|
+
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
testDir: `${rootDir}/repo-e2e/docs/specs`,
|
|
8
|
+
outputDir: `${rootDir}/repo-e2e/docs/test-results`,
|
|
9
|
+
fullyParallel: false,
|
|
10
|
+
workers: 1,
|
|
11
|
+
forbidOnly: !!process.env.CI,
|
|
12
|
+
retries: process.env.CI ? 2 : 0,
|
|
13
|
+
reporter: process.env.CI
|
|
14
|
+
? [
|
|
15
|
+
['html', { open: 'never', outputFolder: `${rootDir}/repo-e2e/docs/playwright-report` }],
|
|
16
|
+
['list'],
|
|
17
|
+
]
|
|
18
|
+
: 'list',
|
|
19
|
+
use: {
|
|
20
|
+
baseURL,
|
|
21
|
+
viewport: { width: 1440, height: 900 },
|
|
22
|
+
trace: 'on-first-retry',
|
|
23
|
+
},
|
|
24
|
+
webServer: {
|
|
25
|
+
command: 'pnpm --dir repo-docs docs:preview -- --host 127.0.0.1 --port 4173 --strictPort',
|
|
26
|
+
cwd: rootDir,
|
|
27
|
+
url: `${baseURL}/`,
|
|
28
|
+
timeout: 120_000,
|
|
29
|
+
reuseExistingServer: false,
|
|
30
|
+
stdout: 'pipe',
|
|
31
|
+
stderr: 'pipe',
|
|
32
|
+
gracefulShutdown: {
|
|
33
|
+
signal: 'SIGINT',
|
|
34
|
+
timeout: 10_000,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
});
|
|
@@ -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
|
+
);
|