cabloy 5.1.160 → 5.1.162
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/CHANGELOG.md +23 -0
- package/CLAUDE.md +1 -1
- package/CONTRIBUTING.md +1 -1
- package/README.md +34 -58
- package/package.json +1 -1
- package/repo-docs/.vitepress/config.mjs +58 -32
- package/repo-docs/ai/ai-spec-driven-development.md +99 -0
- package/repo-docs/ai/docs-skills-rules-mapping.md +25 -8
- package/repo-docs/ai/edition-consistency-checklist.md +2 -3
- package/repo-docs/ai/introduction.md +7 -4
- package/repo-docs/ai/playbook-spec-execution.md +19 -5
- package/repo-docs/ai/playbook-spec-generation.md +69 -101
- package/repo-docs/ai/repo-guidance.md +1 -1
- package/repo-docs/ai/skills.md +31 -10
- package/repo-docs/backend/dto-guide.md +1 -0
- package/repo-docs/backend/openapi-guide.md +1 -1
- package/repo-docs/editions/overview.md +51 -120
- package/repo-docs/frontend/environment-config-guide.md +1 -1
- package/repo-docs/frontend/form-layout-guide.md +4 -2
- package/repo-docs/frontend/introduction.md +2 -1
- package/repo-docs/frontend/quickstart.md +2 -8
- package/repo-docs/frontend/scripts.md +7 -3
- package/repo-docs/frontend/ssr-architecture-overview.md +1 -1
- package/repo-docs/frontend/ssr-build-deploy-guide.md +4 -2
- package/repo-docs/fullstack/comparison-with-other-frameworks.md +6 -2
- package/repo-docs/fullstack/contract-loop-playbook.md +4 -0
- package/repo-docs/fullstack/introduction.md +13 -4
- package/repo-docs/fullstack/parallel-worktree-environment.md +2 -0
- package/repo-docs/fullstack/quickstart.md +65 -74
- package/repo-docs/fullstack/semantic-presentation-contract.md +121 -0
- package/repo-docs/fullstack/ssr-site-and-flavor-setup.md +2 -2
- package/repo-docs/index.md +16 -11
- package/repo-docs/reference/glossary.md +30 -0
- package/repo-docs/reference/package-map.md +1 -1
- package/repo-docs/reference/repo-scripts.md +6 -4
- package/repo-e2e/specs/cabloy-basic.spec.ts +16 -16
- package/test-results/.last-run.json +2 -20
- package/vona/pnpm-lock.yaml +38 -214
- package/zova/pnpm-lock.yaml +8 -0
- package/zova/src/suite/a-home/modules/home-base/src/.metadata/component/siteEntryTables.ts +12 -0
- package/zova/src/suite/a-home/modules/home-base/src/.metadata/index.ts +13 -0
- package/zova/src/suite/a-home/modules/home-base/src/component/siteEntryTables/controller.tsx +71 -0
- package/zova/src/suite/a-home/modules/home-base/src/config/locale/en-us.ts +8 -0
- package/zova/src/suite/a-home/modules/home-base/src/config/locale/zh-cn.ts +8 -0
- package/zova/src/suite/a-home/modules/home-indexadmin/package.json +3 -0
- package/zova/src/suite/a-home/modules/home-indexadmin/src/page/dashboard/controller.tsx +6 -13
- package/zova/src/suite/a-home/modules/home-indexweb/package.json +3 -1
- package/zova/src/suite/a-home/modules/home-indexweb/src/page/home/controller.tsx +6 -14
- package/repo-docs/ai/edition-detection.md +0 -30
- package/repo-docs/editions/cabloy-basic.md +0 -26
- package/repo-docs/editions/cabloy-start.md +0 -53
- package/repo-docs/editions/choosing-between-basic-and-start.md +0 -83
- package/repo-docs/editions/detection.md +0 -31
- package/test-results/a-commerce-ATP-ADDR-01-aut-11dea-ss-through-Web-self-service/error-context.md +0 -238
- package/test-results/a-commerce-ATP-SPC-01-Coup-ef380-mantic-Admin-field-controls/error-context.md +0 -240
- package/test-results/a-commerce-ATP-SPC-02-Cate-9f120-on-and-publication-controls/error-context.md +0 -238
- package/test-results/a-commerce-ATP-SPC-02-Prod-5f92a-on-and-publication-controls/error-context.md +0 -238
- package/test-results/a-commerce-ATP-SPC-02-SKU--2e61e-ency-and-lifecycle-controls/error-context.md +0 -238
- package/test-results/a-commerce-ATP-SPC-04-Stoc-fa895--readonly-and-mutation-free/error-context.md +0 -235
- package/test-results/a-commerce-ATP-SPC-05-syst-fb406-e-without-mutation-controls/error-context.md +0 -238
- package/test-results/a-commerce-Commerce-sessio-4e963-ie-selects-raw-server-theme/error-context.md +0 -226
- package/test-results/a-commerce-Commerce-theme--062fe--without-hydration-mismatch/error-context.md +0 -229
- package/test-results/a-commerce-PayPal-browser--8672c-t-or-open-an-awaiting-order/error-context.md +0 -238
- package/test-results/a-commerce-Payment-callbac-f56c2--reconciles-after-hydration/error-context.md +0 -238
- package/test-results/a-commerce-Payment-cancell-3fc74-ified-provider-confirmation/error-context.md +0 -238
- package/test-results/a-commerce-Phase-50-60-aut-71d61--observes-operator-shipment/error-context.md +0 -238
- package/test-results/a-commerce-Phase-60-custom-407cd-ecutes-a-whole-order-refund/error-context.md +0 -238
- package/test-results/cabloy-basic-ATP-BASIC-TAB-e7253-and-pins-configured-columns/error-context.md +0 -240
- package/test-results/markdown-ATP-SPC-02-Produc-9d5c2-link-toolbar-edits-Markdown/error-context.md +0 -211
- package/test-results/markdown-ATP-SPC-02-Produc-9d6ea-t-editor-and-saves-Markdown/error-context.md +0 -238
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { BeanControllerPageBase } from 'zova';
|
|
3
3
|
import { Controller } from 'zova-module-a-bean';
|
|
4
|
+
import { ZPage, ZSiteEntryTables } from 'zova-module-home-base';
|
|
4
5
|
|
|
5
6
|
export const ControllerPageHomeSchemaParams = z.object({
|
|
6
7
|
locale: z.string().optional(),
|
|
@@ -8,22 +9,13 @@ export const ControllerPageHomeSchemaParams = z.object({
|
|
|
8
9
|
|
|
9
10
|
@Controller()
|
|
10
11
|
export class ControllerPageHome extends BeanControllerPageBase {
|
|
11
|
-
public message: string;
|
|
12
|
-
|
|
13
|
-
protected async __init__() {
|
|
14
|
-
this.message = 'Hello Zova';
|
|
15
|
-
}
|
|
16
|
-
|
|
17
12
|
protected render() {
|
|
18
|
-
const localeCurrent = this.app.meta.locale.current;
|
|
19
13
|
return (
|
|
20
|
-
<
|
|
21
|
-
<
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
</div>
|
|
26
|
-
</div>
|
|
14
|
+
<ZPage>
|
|
15
|
+
<section class="mx-auto max-w-6xl p-6">
|
|
16
|
+
<ZSiteEntryTables />
|
|
17
|
+
</section>
|
|
18
|
+
</ZPage>
|
|
27
19
|
);
|
|
28
20
|
}
|
|
29
21
|
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# Edition Detection for AI Workflows
|
|
2
|
-
|
|
3
|
-
This page applies the general detection rule from [Editions / Detection](/editions/detection) to AI-assisted development specifically.
|
|
4
|
-
|
|
5
|
-
Edition detection should happen before any AI workflow that assumes a frontend stack, module set, or example path.
|
|
6
|
-
|
|
7
|
-
## AI-specific consequence
|
|
8
|
-
|
|
9
|
-
If an agent skips edition detection, it can:
|
|
10
|
-
|
|
11
|
-
- scaffold against the wrong UI-layer assumptions
|
|
12
|
-
- suggest the wrong frontend flavors
|
|
13
|
-
- cite suites, modules, SSR site baselines, or project assets that do not exist in the active repo
|
|
14
|
-
- generate docs or skills that accidentally hardcode Basic-only behavior or `npm create cabloy` assumptions into Start workflows
|
|
15
|
-
|
|
16
|
-
## Operational rule
|
|
17
|
-
|
|
18
|
-
Before an AI workflow recommends implementation steps:
|
|
19
|
-
|
|
20
|
-
1. check the edition marker
|
|
21
|
-
2. verify the relevant package scripts or CLI entrypoints
|
|
22
|
-
3. branch the guidance only where the editions truly diverge
|
|
23
|
-
|
|
24
|
-
## Where this rule should live
|
|
25
|
-
|
|
26
|
-
Keep the same rule in three places on purpose:
|
|
27
|
-
|
|
28
|
-
- **public docs** so people can review it
|
|
29
|
-
- **skills** so agents execute it consistently
|
|
30
|
-
- **`CLAUDE.md`** so repo-wide Claude behavior stays aligned
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# Cabloy Basic
|
|
2
|
-
|
|
3
|
-
Cabloy Basic is the public framework/reference edition you are reading right now.
|
|
4
|
-
|
|
5
|
-
## Repository marker
|
|
6
|
-
|
|
7
|
-
The repository root contains:
|
|
8
|
-
|
|
9
|
-
- `__CABLOY_BASIC__`
|
|
10
|
-
|
|
11
|
-
This marker is the quickest safe signal for docs, skills, and rules that need to distinguish Cabloy Basic from Cabloy Start.
|
|
12
|
-
|
|
13
|
-
## Typical role
|
|
14
|
-
|
|
15
|
-
Use Cabloy Basic as the public reference edition for:
|
|
16
|
-
|
|
17
|
-
- shared architecture explanations
|
|
18
|
-
- root scripts and monorepo entrypoints
|
|
19
|
-
- Vona and Zova source browsing
|
|
20
|
-
- public CLI-backed workflows
|
|
21
|
-
- public user-facing documentation
|
|
22
|
-
- projects created with `npm create cabloy`
|
|
23
|
-
|
|
24
|
-
## Frontend bias
|
|
25
|
-
|
|
26
|
-
In the current source and docs, Cabloy Basic examples commonly align with a shared frontend engineering layer built on Zova, Vue, Vite, and Quasar tooling, plus a Basic-specific UI layer built with DaisyUI + Tailwind CSS. That does not mean Zova is limited to that UI stack, but it does mean docs and AI outputs should not accidentally project Vuetify-specific assumptions into Cabloy Basic unless the current source explicitly requires it.
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
# Cabloy Start
|
|
2
|
-
|
|
3
|
-
Cabloy Start is the public MIT-licensed edition. It is a sibling repository that shares the Cabloy fullstack direction while intentionally differing from Cabloy Basic.
|
|
4
|
-
|
|
5
|
-
## Repository marker
|
|
6
|
-
|
|
7
|
-
The Cabloy Start root contains:
|
|
8
|
-
|
|
9
|
-
- `__CABLOY_START__`
|
|
10
|
-
|
|
11
|
-
Use that marker before choosing examples, UI assumptions, or automation behavior.
|
|
12
|
-
|
|
13
|
-
## Typical role
|
|
14
|
-
|
|
15
|
-
Use Cabloy Start as the edition-aware target when work depends on:
|
|
16
|
-
|
|
17
|
-
- direct use of the public Cabloy Start repository source
|
|
18
|
-
- Vuetify-specific frontend workflows
|
|
19
|
-
- Cabloy Start flavor names in frontend scripts
|
|
20
|
-
- edition-specific module composition in the active Start repository
|
|
21
|
-
- separate-repository structure and edition-specific project composition
|
|
22
|
-
- edition-specific SSR site baselines and project assets
|
|
23
|
-
|
|
24
|
-
## Clone and initialize
|
|
25
|
-
|
|
26
|
-
Cabloy Start is public and MIT-licensed. It does not use the default `npm create cabloy` project route.
|
|
27
|
-
|
|
28
|
-
To use Cabloy Start:
|
|
29
|
-
|
|
30
|
-
1. clone the public repository directly
|
|
31
|
-
2. run the edition initialization flow in the cloned project
|
|
32
|
-
|
|
33
|
-
Repository:
|
|
34
|
-
|
|
35
|
-
- `https://github.com/cabloy/cabloy-start`
|
|
36
|
-
|
|
37
|
-
Clone the repository:
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
git clone git@github.com:cabloy/cabloy-start.git
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
After cloning, run:
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
npm run init
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
This initializes the project and installs dependencies.
|
|
50
|
-
|
|
51
|
-
## Relationship to this docs site
|
|
52
|
-
|
|
53
|
-
This unified docs site treats Cabloy Start as a supported edition, not as a clone of Cabloy Basic. Shared architecture should remain shared, but any Start-specific script, module path, or UI workflow must be labeled explicitly.
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
# Choosing Between Cabloy Basic and Cabloy Start
|
|
2
|
-
|
|
3
|
-
This guide helps you choose the right Cabloy edition before you start a new project, adopt a frontend UI strategy, or prepare AI workflow guidance.
|
|
4
|
-
|
|
5
|
-
## Short answer
|
|
6
|
-
|
|
7
|
-
Choose **Cabloy Basic** when you want the public framework/reference edition, the default `npm create cabloy` project route, and a faster path for open, community-oriented, or small-to-medium system development.
|
|
8
|
-
|
|
9
|
-
Choose **Cabloy Start** when you want the public MIT-licensed edition in its own repository and a stronger baseline for more complex business systems built around Start-oriented SSR sites, project assets, and a Vuetify-aligned UI layer.
|
|
10
|
-
|
|
11
|
-
## What stays the same in both editions
|
|
12
|
-
|
|
13
|
-
Both editions share the same Cabloy fullstack core:
|
|
14
|
-
|
|
15
|
-
- **Vona** as the backend framework
|
|
16
|
-
- **Zova** as the frontend framework
|
|
17
|
-
- suite-based modular architecture
|
|
18
|
-
- CLI-first workflows
|
|
19
|
-
- shared frontend engineering direction built around Vue, Vite, Quasar tooling, and related libraries
|
|
20
|
-
|
|
21
|
-
So the decision is not about choosing two unrelated products. It is about choosing the edition baseline that fits your delivery goals best.
|
|
22
|
-
|
|
23
|
-
## Choose Cabloy Basic when
|
|
24
|
-
|
|
25
|
-
Cabloy Basic is usually the better fit when you want:
|
|
26
|
-
|
|
27
|
-
- the public framework/reference edition
|
|
28
|
-
- the default project route created by `npm create cabloy`
|
|
29
|
-
- open-source visibility and community-friendly workflows
|
|
30
|
-
- public examples and docs that match your repo directly
|
|
31
|
-
- a UI layer aligned with DaisyUI + Tailwind CSS
|
|
32
|
-
- a faster path for small-to-medium system development
|
|
33
|
-
|
|
34
|
-
## Choose Cabloy Start when
|
|
35
|
-
|
|
36
|
-
Cabloy Start is usually the better fit when you want:
|
|
37
|
-
|
|
38
|
-
- the public MIT-licensed edition maintained in its own repository
|
|
39
|
-
- direct cloning of the public Start repository
|
|
40
|
-
- Start-specific flavors, SSR site baselines, and project assets
|
|
41
|
-
- a UI layer aligned with Vuetify
|
|
42
|
-
- a stronger starting point for more complex business systems
|
|
43
|
-
- edition-specific rules, skills, and docs optimized for the Start repo assumptions
|
|
44
|
-
|
|
45
|
-
## The most practical decision factors
|
|
46
|
-
|
|
47
|
-
### Project creation path
|
|
48
|
-
|
|
49
|
-
- **Cabloy Basic**: create the project with `npm create cabloy`
|
|
50
|
-
- **Cabloy Start**: clone the public repository directly, then run `npm run init`
|
|
51
|
-
|
|
52
|
-
### UI strategy
|
|
53
|
-
|
|
54
|
-
- **Cabloy Basic**: DaisyUI + Tailwind CSS
|
|
55
|
-
- **Cabloy Start**: Vuetify
|
|
56
|
-
|
|
57
|
-
### Repo and asset model
|
|
58
|
-
|
|
59
|
-
- **Cabloy Basic**: public repository and public reference materials
|
|
60
|
-
- **Cabloy Start**: separate public repository with edition-specific SSR sites and project assets
|
|
61
|
-
|
|
62
|
-
### AI workflow assumptions
|
|
63
|
-
|
|
64
|
-
- **Cabloy Basic**: use Basic-specific examples, flavors, modules, and UI assumptions
|
|
65
|
-
- **Cabloy Start**: use Start-specific examples, flavors, SSR site baselines, and UI assumptions
|
|
66
|
-
|
|
67
|
-
This is why edition detection matters so much for AI vibe coding.
|
|
68
|
-
|
|
69
|
-
## A simple recommendation rule
|
|
70
|
-
|
|
71
|
-
Use this rule when you need a fast decision:
|
|
72
|
-
|
|
73
|
-
1. If you want the public, default, `npm create cabloy` path, choose **Cabloy Basic**.
|
|
74
|
-
2. If you want the public MIT-licensed Start repository with Start-oriented assets, a Vuetify-based business-system baseline, and a clone-plus-`npm run init` onboarding path, choose **Cabloy Start**.
|
|
75
|
-
3. If AI workflow accuracy matters for UI generation, SSR site assumptions, or flavor-specific commands, confirm the edition before writing prompts, rules, skills, or docs.
|
|
76
|
-
|
|
77
|
-
## Read together with
|
|
78
|
-
|
|
79
|
-
- [Editions Overview](/editions/overview)
|
|
80
|
-
- [Cabloy Basic](/editions/cabloy-basic)
|
|
81
|
-
- [Cabloy Start](/editions/cabloy-start)
|
|
82
|
-
- [Edition Detection](/editions/detection)
|
|
83
|
-
- [Fullstack Quickstart](/fullstack/quickstart)
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# Edition Detection
|
|
2
|
-
|
|
3
|
-
Edition detection is a first-class requirement for documentation, rules, and skills.
|
|
4
|
-
|
|
5
|
-
## Repository markers
|
|
6
|
-
|
|
7
|
-
Use these root markers:
|
|
8
|
-
|
|
9
|
-
- `__CABLOY_BASIC__` → Cabloy Basic
|
|
10
|
-
- `__CABLOY_START__` → Cabloy Start
|
|
11
|
-
|
|
12
|
-
## Why this matters
|
|
13
|
-
|
|
14
|
-
The two editions share many concepts but differ in important operational details, especially around:
|
|
15
|
-
|
|
16
|
-
- frontend UI library assumptions
|
|
17
|
-
- flavor-specific frontend scripts
|
|
18
|
-
- module availability
|
|
19
|
-
- Start-specific value-add project content
|
|
20
|
-
|
|
21
|
-
If an agent skips edition detection, it may generate the wrong instructions, recommend the wrong module, or use the wrong UI stack.
|
|
22
|
-
|
|
23
|
-
## Recommended rule for skills and repo guidance
|
|
24
|
-
|
|
25
|
-
Before suggesting a cross-stack implementation path:
|
|
26
|
-
|
|
27
|
-
1. check the repository marker
|
|
28
|
-
2. verify the relevant root scripts or package scripts
|
|
29
|
-
3. only then choose the edition-specific example or workflow branch
|
|
30
|
-
|
|
31
|
-
When neither marker is present, fall back to code inspection and ask the user before making a strong edition-specific assumption.
|
package/test-results/a-commerce-ATP-ADDR-01-aut-11dea-ss-through-Web-self-service/error-context.md
DELETED
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
# Instructions
|
|
2
|
-
|
|
3
|
-
- Following Playwright test failed.
|
|
4
|
-
- Explain why, be concise, respect Playwright best practices.
|
|
5
|
-
- Provide a snippet of code with the fix, if possible.
|
|
6
|
-
|
|
7
|
-
# Test info
|
|
8
|
-
|
|
9
|
-
- Name: a-commerce.spec.ts >> ATP-ADDR-01: authenticated customer manages Address through Web self-service
|
|
10
|
-
- Location: repo-e2e/specs/a-commerce.spec.ts:556:1
|
|
11
|
-
|
|
12
|
-
# Error details
|
|
13
|
-
|
|
14
|
-
```
|
|
15
|
-
Error: expect(page).toHaveURL(expected) failed
|
|
16
|
-
|
|
17
|
-
Expected pattern: /\/commerce\/commerce\/member\/address(?:\?|$)/
|
|
18
|
-
Received string: "http://127.0.0.1:7102/commerce/home/base/errorAccessDenied"
|
|
19
|
-
Timeout: 5000ms
|
|
20
|
-
|
|
21
|
-
Call log:
|
|
22
|
-
- Expect "toHaveURL" with timeout 5000ms
|
|
23
|
-
14 × locator resolved to <html lang="en-us" data-zova-hydrated="commerce">…</html>
|
|
24
|
-
- unexpected value "http://127.0.0.1:7102/commerce/home/base/errorAccessDenied"
|
|
25
|
-
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
```yaml
|
|
29
|
-
- text: 403 Access denied.
|
|
30
|
-
- button "Go Home"
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
# Test source
|
|
34
|
-
|
|
35
|
-
```ts
|
|
36
|
-
89 | await expect(passwordInput).toHaveValue(password);
|
|
37
|
-
90 | await expect(page.getByPlaceholder('Please input captcha')).not.toHaveValue('');
|
|
38
|
-
91 | const loginResponse = waitForApiResponse(page, 'POST', '/api/home/user/passport/login');
|
|
39
|
-
92 | await page.getByRole('button', { name: 'Login', exact: true }).click();
|
|
40
|
-
93 | const loginResponseValue = await loginResponse;
|
|
41
|
-
94 | if (!loginResponseValue.ok()) {
|
|
42
|
-
95 | throw new Error(
|
|
43
|
-
96 | `login failed: ${loginResponseValue.status()} ${await loginResponseValue.text()}`,
|
|
44
|
-
97 | );
|
|
45
|
-
98 | }
|
|
46
|
-
99 | await expect(page).not.toHaveURL(/\/login(?:\?|$)/);
|
|
47
|
-
100 | }
|
|
48
|
-
101 | }
|
|
49
|
-
102 |
|
|
50
|
-
103 | async function registerCustomer(
|
|
51
|
-
104 | request: APIRequestContext,
|
|
52
|
-
105 | testInfo: TestInfo,
|
|
53
|
-
106 | ): Promise<{ password: string; username: string }> {
|
|
54
|
-
107 | const id = `${testInfo.workerIndex}-${testInfo.parallelIndex ?? testInfo.retry}-${Date.now()}`;
|
|
55
|
-
108 | const username = `e2e-address-${id}`;
|
|
56
|
-
109 | const password = 'address-e2e-password';
|
|
57
|
-
110 | const captchaResponse = await request.post('/api/captcha/create', {
|
|
58
|
-
111 | data: { scene: 'captcha-simple:simple' },
|
|
59
|
-
112 | });
|
|
60
|
-
113 | expect(captchaResponse.ok()).toBeTruthy();
|
|
61
|
-
114 | const captcha = (await captchaResponse.json()).data;
|
|
62
|
-
115 | expect(captcha?.id).toEqual(expect.any(String));
|
|
63
|
-
116 | expect(captcha?.token).toEqual(expect.any(String));
|
|
64
|
-
117 |
|
|
65
|
-
118 | const baseURL = testInfo.project.use.baseURL;
|
|
66
|
-
119 | if (!baseURL) throw new Error('commerce E2E base URL is unavailable');
|
|
67
|
-
120 | const consumerUrl = new URL('/home/user/activation', baseURL).toString();
|
|
68
|
-
121 | const registerResponse = await request.post('/api/home/user/passport/register', {
|
|
69
|
-
122 | data: {
|
|
70
|
-
123 | username,
|
|
71
|
-
124 | email: `${username}@example.test`,
|
|
72
|
-
125 | password,
|
|
73
|
-
126 | passwordConfirm: password,
|
|
74
|
-
127 | consumerUrl,
|
|
75
|
-
128 | captcha: { id: captcha.id, token: captcha.token },
|
|
76
|
-
129 | },
|
|
77
|
-
130 | });
|
|
78
|
-
131 | expect(registerResponse.ok()).toBeTruthy();
|
|
79
|
-
132 | const registration = (await registerResponse.json()).data;
|
|
80
|
-
133 | expect(registration?.jwt?.accessToken).toEqual(expect.any(String));
|
|
81
|
-
134 | const activateResponse = await request.post(passportTestActivateCurrentPath, {
|
|
82
|
-
135 | headers: { Authorization: `Bearer ${registration.jwt.accessToken}` },
|
|
83
|
-
136 | });
|
|
84
|
-
137 | expect(activateResponse.ok()).toBeTruthy();
|
|
85
|
-
138 | return { username, password };
|
|
86
|
-
139 | }
|
|
87
|
-
140 |
|
|
88
|
-
141 | function createAddressFixture(testInfo: TestInfo): IAddressFixture {
|
|
89
|
-
142 | const id = `${testInfo.workerIndex}-${testInfo.parallelIndex ?? testInfo.retry}-${Date.now()}`;
|
|
90
|
-
143 | return {
|
|
91
|
-
144 | recipientName: `E2E Address ${id}`,
|
|
92
|
-
145 | phone: '15555550123',
|
|
93
|
-
146 | countryCode: 'US',
|
|
94
|
-
147 | region: 'California',
|
|
95
|
-
148 | city: `Original City ${id}`,
|
|
96
|
-
149 | postalCode: '94105',
|
|
97
|
-
150 | addressLine1: `1 Test Street ${id}`,
|
|
98
|
-
151 | addressLine2: 'Suite E2E',
|
|
99
|
-
152 | updatedCity: `Updated City ${id}`,
|
|
100
|
-
153 | };
|
|
101
|
-
154 | }
|
|
102
|
-
155 |
|
|
103
|
-
156 | function getAddressTextbox(page: Page, name: string): Locator {
|
|
104
|
-
157 | return page.getByRole('group', { name, exact: true }).getByRole('textbox');
|
|
105
|
-
158 | }
|
|
106
|
-
159 |
|
|
107
|
-
160 | async function fillAddressForm(page: Page, fixture: IAddressFixture) {
|
|
108
|
-
161 | await getAddressTextbox(page, 'Recipient Name *').fill(fixture.recipientName);
|
|
109
|
-
162 | await getAddressTextbox(page, 'Phone *').fill(fixture.phone);
|
|
110
|
-
163 | await getAddressTextbox(page, 'Country Code *').fill(fixture.countryCode);
|
|
111
|
-
164 | await getAddressTextbox(page, 'Region *').fill(fixture.region);
|
|
112
|
-
165 | await getAddressTextbox(page, 'City *').fill(fixture.city);
|
|
113
|
-
166 | await getAddressTextbox(page, 'Postal Code *').fill(fixture.postalCode);
|
|
114
|
-
167 | await getAddressTextbox(page, 'Address Line 1 *').fill(fixture.addressLine1);
|
|
115
|
-
168 | await getAddressTextbox(page, 'Address Line 2').fill(fixture.addressLine2);
|
|
116
|
-
169 | }
|
|
117
|
-
170 |
|
|
118
|
-
171 | async function createAddressThroughCustomerPage(
|
|
119
|
-
172 | browser: Browser,
|
|
120
|
-
173 | request: APIRequestContext,
|
|
121
|
-
174 | testInfo: TestInfo,
|
|
122
|
-
175 | ) {
|
|
123
|
-
176 | const customer = await registerCustomer(request, testInfo);
|
|
124
|
-
177 | const fixture = createAddressFixture(testInfo);
|
|
125
|
-
178 | const context = await browser.newContext();
|
|
126
|
-
179 | const page = await context.newPage();
|
|
127
|
-
180 | const pageErrors = collectPageErrors(page);
|
|
128
|
-
181 |
|
|
129
|
-
182 | await login(
|
|
130
|
-
183 | page,
|
|
131
|
-
184 | '/commerce/commerce/member/address',
|
|
132
|
-
185 | customer.username,
|
|
133
|
-
186 | customer.password,
|
|
134
|
-
187 | 'commerce',
|
|
135
|
-
188 | );
|
|
136
|
-
> 189 | await expect(page).toHaveURL(/\/commerce\/commerce\/member\/address(?:\?|$)/);
|
|
137
|
-
| ^ Error: expect(page).toHaveURL(expected) failed
|
|
138
|
-
190 | await expect(page.getByRole('heading', { name: 'Addresses' })).toBeVisible();
|
|
139
|
-
191 | await expect(page.getByText('No addresses yet.')).toBeVisible();
|
|
140
|
-
192 |
|
|
141
|
-
193 | await fillAddressForm(page, fixture);
|
|
142
|
-
194 | const createResponse = waitForAddressResponse(page, 'POST', `${addressActionPath}/createMine`);
|
|
143
|
-
195 | const mineResponse = waitForAddressMine(page);
|
|
144
|
-
196 | await page.getByRole('button', { name: 'Save address', exact: true }).click();
|
|
145
|
-
197 | await createResponse;
|
|
146
|
-
198 | await mineResponse;
|
|
147
|
-
199 | await expect(page.getByRole('heading', { name: fixture.recipientName })).toBeVisible();
|
|
148
|
-
200 |
|
|
149
|
-
201 | return { context, fixture, page, pageErrors };
|
|
150
|
-
202 | }
|
|
151
|
-
203 |
|
|
152
|
-
204 | async function deleteAddressThroughCustomerPage(page: Page, fixture: IAddressFixture) {
|
|
153
|
-
205 | const card = page
|
|
154
|
-
206 | .locator('article')
|
|
155
|
-
207 | .filter({ has: page.getByRole('heading', { name: fixture.recipientName }) });
|
|
156
|
-
208 | const deleteResponse = waitForAddressResponse(page, 'DELETE', /\/deleteMine\/[^/]+$/);
|
|
157
|
-
209 | const mineResponse = waitForAddressMine(page);
|
|
158
|
-
210 | await card.getByRole('button', { name: 'Delete', exact: true }).click();
|
|
159
|
-
211 | await deleteResponse;
|
|
160
|
-
212 | await mineResponse;
|
|
161
|
-
213 | await expect(page.getByRole('heading', { name: fixture.recipientName })).toHaveCount(0);
|
|
162
|
-
214 | await expect(page.getByText('No addresses yet.')).toBeVisible();
|
|
163
|
-
215 | }
|
|
164
|
-
216 |
|
|
165
|
-
217 | test(
|
|
166
|
-
218 | 'ATP-SSR-01: anonymous Commerce HTML is public and hydration completes',
|
|
167
|
-
219 | { tag: ['@web', '@smoke'] },
|
|
168
|
-
220 | async ({ page, request }) => {
|
|
169
|
-
221 | const response = await request.get('/commerce');
|
|
170
|
-
222 | expect(response.ok()).toBeTruthy();
|
|
171
|
-
223 | expect(response.headers()['cache-control']).toBe('no-cache, no-store, must-revalidate');
|
|
172
|
-
224 | const html = await response.text();
|
|
173
|
-
225 | const normalizedHtml = html.toLowerCase();
|
|
174
|
-
226 | expect(normalizedHtml).not.toContain('data-zova-hydrated');
|
|
175
|
-
227 |
|
|
176
|
-
228 | for (const marker of privateMarkers) {
|
|
177
|
-
229 | expect(normalizedHtml).not.toContain(`"${marker}"`);
|
|
178
|
-
230 | }
|
|
179
|
-
231 |
|
|
180
|
-
232 | const pageErrors = collectPageErrors(page);
|
|
181
|
-
233 | const documentResponse = await page.goto('/commerce', { waitUntil: 'load' });
|
|
182
|
-
234 | expect(documentResponse?.ok()).toBeTruthy();
|
|
183
|
-
235 | await expect(page).toHaveURL(/\/commerce(?:\/|$)/);
|
|
184
|
-
236 | await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'commerce');
|
|
185
|
-
237 | await expect(page.getByRole('heading', { name: 'Commerce catalogue' })).toBeVisible();
|
|
186
|
-
238 | await expect(page.getByText('Dashboard')).toHaveCount(0);
|
|
187
|
-
239 | await expect(page.locator('body')).toBeVisible();
|
|
188
|
-
240 | await expect(page).not.toHaveTitle(/error/i);
|
|
189
|
-
241 | expect(pageErrors).toEqual([]);
|
|
190
|
-
242 | },
|
|
191
|
-
243 | );
|
|
192
|
-
244 |
|
|
193
|
-
245 | test(
|
|
194
|
-
246 | 'Commerce theme: public local preference mirrors session cookie without hydration mismatch',
|
|
195
|
-
247 | { tag: ['@web', '@ssr', '@theme'] },
|
|
196
|
-
248 | async ({ browser, request }, testInfo) => {
|
|
197
|
-
249 | const customer = await registerCustomer(request, testInfo);
|
|
198
|
-
250 | const context = await browser.newContext();
|
|
199
|
-
251 | await context.addInitScript(() => localStorage.setItem('themedark', 'true'));
|
|
200
|
-
252 | const page = await context.newPage();
|
|
201
|
-
253 | const pageErrors = collectPageErrors(page);
|
|
202
|
-
254 | const consoleErrors: string[] = [];
|
|
203
|
-
255 | page.on('console', message => {
|
|
204
|
-
256 | if (message.type() === 'error' || /hydration mismatch/i.test(message.text())) {
|
|
205
|
-
257 | consoleErrors.push(message.text());
|
|
206
|
-
258 | }
|
|
207
|
-
259 | });
|
|
208
|
-
260 | try {
|
|
209
|
-
261 | await page.goto('/commerce', { waitUntil: 'load' });
|
|
210
|
-
262 | await page.evaluate(() => localStorage.setItem('themedark', 'true'));
|
|
211
|
-
263 | await page.reload({ waitUntil: 'load' });
|
|
212
|
-
264 | await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'commerce');
|
|
213
|
-
265 | await expect(page.locator('body')).toHaveAttribute('data-theme', 'dark');
|
|
214
|
-
266 | expect((await context.cookies()).find(cookie => cookie.name === 'themedark')?.value).toBe(
|
|
215
|
-
267 | 'true',
|
|
216
|
-
268 | );
|
|
217
|
-
269 |
|
|
218
|
-
270 | await login(
|
|
219
|
-
271 | page,
|
|
220
|
-
272 | '/commerce/commerce/member/address',
|
|
221
|
-
273 | customer.username,
|
|
222
|
-
274 | customer.password,
|
|
223
|
-
275 | 'commerce',
|
|
224
|
-
276 | );
|
|
225
|
-
277 | await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'commerce');
|
|
226
|
-
278 | await expect(page.locator('body')).toHaveAttribute('data-theme', 'dark');
|
|
227
|
-
279 | expect(pageErrors).toEqual([]);
|
|
228
|
-
280 | expect(consoleErrors).toEqual([]);
|
|
229
|
-
281 | } finally {
|
|
230
|
-
282 | await context.close();
|
|
231
|
-
283 | }
|
|
232
|
-
284 | },
|
|
233
|
-
285 | );
|
|
234
|
-
286 |
|
|
235
|
-
287 | test(
|
|
236
|
-
288 | 'Commerce theme: session cookie mirrors public local preference without hydration mismatch',
|
|
237
|
-
289 | { tag: ['@web', '@ssr', '@theme'] },
|
|
238
|
-
```
|