@microsoft/fast-test-harness 0.1.0 → 0.2.0
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/README.md +24 -18
- package/dist/dts/build/generate-templates.d.ts +35 -0
- package/dist/dts/build/generate-templates.d.ts.map +1 -1
- package/dist/dts/build/generate-webui-templates.d.ts +13 -0
- package/dist/dts/build/generate-webui-templates.d.ts.map +1 -1
- package/dist/dts/fixtures/csr-fixture.d.ts +28 -0
- package/dist/dts/fixtures/csr-fixture.d.ts.map +1 -1
- package/dist/dts/fixtures/ssr-fixture.d.ts +19 -0
- package/dist/dts/fixtures/ssr-fixture.d.ts.map +1 -1
- package/dist/dts/ssr/render.d.ts.map +1 -1
- package/dist/esm/build/generate-templates.js +61 -1
- package/dist/esm/build/generate-webui-templates.js +8 -25
- package/dist/esm/fixtures/ssr-fixture.js +19 -1
- package/dist/esm/ssr/render.js +38 -5
- package/package.json +26 -15
- package/dist/dts/build/dom-shim.test.d.ts +0 -2
- package/dist/dts/build/dom-shim.test.d.ts.map +0 -1
- package/dist/dts/build/generate-stylesheets.test.d.ts +0 -2
- package/dist/dts/build/generate-stylesheets.test.d.ts.map +0 -1
- package/dist/dts/build/generate-templates.test.d.ts +0 -2
- package/dist/dts/build/generate-templates.test.d.ts.map +0 -1
- package/dist/dts/build/generate-webui-templates.test.d.ts +0 -2
- package/dist/dts/build/generate-webui-templates.test.d.ts.map +0 -1
- package/dist/dts/fixtures/csr-fixture.pw.spec.d.ts +0 -2
- package/dist/dts/fixtures/csr-fixture.pw.spec.d.ts.map +0 -1
- package/dist/dts/fixtures/ssr-fixture.pw.spec.d.ts +0 -2
- package/dist/dts/fixtures/ssr-fixture.pw.spec.d.ts.map +0 -1
- package/dist/dts/ssr/render.test.d.ts +0 -2
- package/dist/dts/ssr/render.test.d.ts.map +0 -1
- package/dist/esm/build/dom-shim.test.js +0 -202
- package/dist/esm/build/generate-stylesheets.test.js +0 -74
- package/dist/esm/build/generate-templates.test.js +0 -231
- package/dist/esm/build/generate-webui-templates.test.js +0 -179
- package/dist/esm/fixtures/csr-fixture.pw.spec.js +0 -137
- package/dist/esm/fixtures/ssr-fixture.pw.spec.js +0 -189
- package/dist/esm/ssr/render.test.js +0 -236
package/README.md
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
The `fast-test-harness` package is a Playwright testing harness for FAST Element web components with CSR and SSR support.
|
|
6
6
|
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
- Node.js 22.18 or later
|
|
10
|
+
- Playwright 1.56 or later
|
|
11
|
+
|
|
7
12
|
## Installation
|
|
8
13
|
|
|
9
14
|
To install `fast-test-harness` using `npm`:
|
|
@@ -12,6 +17,21 @@ To install `fast-test-harness` using `npm`:
|
|
|
12
17
|
npm install --save-dev @microsoft/fast-test-harness
|
|
13
18
|
```
|
|
14
19
|
|
|
20
|
+
## Test directory setup
|
|
21
|
+
|
|
22
|
+
The harness serves a Vite dev server from a `test/` directory in your project. CSR and SSR modes use different entry points from the same directory.
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
test/
|
|
26
|
+
├── index.html # CSR: loads main.ts
|
|
27
|
+
├── ssr.html # SSR: template with comment placeholders
|
|
28
|
+
├── vite.config.ts # Vite config (shared by both modes)
|
|
29
|
+
└── src/
|
|
30
|
+
├── main.ts # CSR: registers components, applies theme
|
|
31
|
+
├── entry-client.ts # SSR: registers components for hydration
|
|
32
|
+
└── entry-server.ts # SSR: exports render() for fixture generation
|
|
33
|
+
```
|
|
34
|
+
|
|
15
35
|
## Writing tests
|
|
16
36
|
|
|
17
37
|
Import `test` and `expect` from the harness. Configure the component tag name with `test.use()`, then call `fastPage.setTemplate()` in each test to render it.
|
|
@@ -67,21 +87,6 @@ await expect(element).toHaveCustomState("checked");
|
|
|
67
87
|
| `waitFor` | `string[]` | `[]` | Additional elements to wait for before testing |
|
|
68
88
|
| `ssr` | `boolean` | `false` | Use SSR mode (or set `PLAYWRIGHT_TEST_SSR=true`) |
|
|
69
89
|
|
|
70
|
-
## Test directory setup
|
|
71
|
-
|
|
72
|
-
The harness serves a Vite dev server from a `test/` directory in your project. CSR and SSR modes use different entry points from the same directory.
|
|
73
|
-
|
|
74
|
-
```
|
|
75
|
-
test/
|
|
76
|
-
├── index.html # CSR: loads main.ts
|
|
77
|
-
├── ssr.html # SSR: template with comment placeholders
|
|
78
|
-
├── vite.config.ts # Vite config (shared by both modes)
|
|
79
|
-
└── src/
|
|
80
|
-
├── main.ts # CSR: registers components, applies theme
|
|
81
|
-
├── entry-client.ts # SSR: registers components for hydration
|
|
82
|
-
└── entry-server.ts # SSR: exports render() for fixture generation
|
|
83
|
-
```
|
|
84
|
-
|
|
85
90
|
### CSR files
|
|
86
91
|
|
|
87
92
|
**`index.html`** loads a script that registers your components:
|
|
@@ -258,10 +263,11 @@ CLI flags take precedence over environment variables.
|
|
|
258
263
|
|
|
259
264
|
| Specifier | Contents |
|
|
260
265
|
|-----------|----------|
|
|
261
|
-
| `@microsoft/fast-test-harness` | `test`, `expect`, `CSRFixture`, `SSRFixture`, `
|
|
266
|
+
| `@microsoft/fast-test-harness` | `test`, `expect`, `CSRFixture`, `SSRFixture`, `toHaveCustomState`, `installDomShim`, `createSSRRenderer` |
|
|
267
|
+
| `@microsoft/fast-test-harness/build/*.js` | `installDomShim`, `generateStylesheets`, `generateFTemplates`, `generateWebuiTemplates`, `definitionAsyncResolver`, `shadowOptionsToAttributes`, `ShadowOptionsResolver` |
|
|
268
|
+
| `@microsoft/fast-test-harness/fixtures/*.js` | `CSRFixture`, `SSRFixture`, `toHaveCustomState`, extended `test` and `expect` |
|
|
269
|
+
| `@microsoft/fast-test-harness/ssr/render.js` | `createSSRRenderer`, `renderTemplate`, `buildEntryHtml`, `buildState`, `parseDefaultValue` |
|
|
262
270
|
| `@microsoft/fast-test-harness/server.mjs` | `startServer` |
|
|
263
|
-
| `@microsoft/fast-test-harness/ssr/render.js` | `createSSRRenderer`, `ComponentRegistration`, `RenderResult`, `SSRRendererOptions` |
|
|
264
|
-
| `@microsoft/fast-test-harness/build/*.js` | `installDomShim`, `generateStylesheets`, `generateFTemplates`, `generateWebuiTemplates` |
|
|
265
271
|
| `@microsoft/fast-test-harness/playwright.config.mjs` | Shared Playwright configuration |
|
|
266
272
|
| `@microsoft/fast-test-harness/vite.config.mjs` | Shared Vite configuration |
|
|
267
273
|
| `@microsoft/fast-test-harness/public/*` | Static assets (base CSS) |
|
|
@@ -45,7 +45,37 @@ export interface GenerateFTemplatesOptions {
|
|
|
45
45
|
* Optional formatter function applied to generated HTML before writing.
|
|
46
46
|
*/
|
|
47
47
|
format?: (html: string, filePath: string) => string | Promise<string>;
|
|
48
|
+
/**
|
|
49
|
+
* Resolves shadow DOM options for a given template module path.
|
|
50
|
+
* Returns a `shadowOptions` object (e.g. `{ delegatesFocus: true }`) or
|
|
51
|
+
* `undefined` if the component has no special shadow options.
|
|
52
|
+
*
|
|
53
|
+
* Defaults to {@link definitionAsyncResolver}, which loads a companion
|
|
54
|
+
* `*.definition-async.js` module next to each template. Set to `null`
|
|
55
|
+
* to disable shadow options resolution.
|
|
56
|
+
*
|
|
57
|
+
* @default definitionAsyncResolver
|
|
58
|
+
*/
|
|
59
|
+
resolveShadowOptions?: ShadowOptionsResolver | null;
|
|
48
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* A function that resolves shadow DOM options for a template module.
|
|
63
|
+
* Receives the absolute path to the compiled `*.template.js` file and
|
|
64
|
+
* returns shadow options or `undefined`.
|
|
65
|
+
*/
|
|
66
|
+
export type ShadowOptionsResolver = (templateJsPath: string) => Record<string, unknown> | undefined | Promise<Record<string, unknown> | undefined>;
|
|
67
|
+
/**
|
|
68
|
+
* Convention-based resolver that loads a companion `*.definition-async.js`
|
|
69
|
+
* module next to the template module and returns its `shadowOptions`.
|
|
70
|
+
*
|
|
71
|
+
* For a template at `dist/textarea/textarea.template.js`, this looks for
|
|
72
|
+
* `dist/textarea/textarea.definition-async.js`.
|
|
73
|
+
*
|
|
74
|
+
* This is the default resolver used by {@link generateFTemplates} and
|
|
75
|
+
* {@link generateWebuiTemplates} when `resolveShadowOptions` is not
|
|
76
|
+
* specified.
|
|
77
|
+
*/
|
|
78
|
+
export declare function definitionAsyncResolver(templateJsPath: string): Promise<Record<string, unknown> | undefined>;
|
|
49
79
|
export interface ViewTemplate {
|
|
50
80
|
html: string | HTMLTemplateElement;
|
|
51
81
|
factories: Record<string, Factory>;
|
|
@@ -64,6 +94,11 @@ interface Factory {
|
|
|
64
94
|
* f-template HTML string.
|
|
65
95
|
*/
|
|
66
96
|
export declare function convertTemplate(viewTemplate: ViewTemplate, componentName: string): string | null;
|
|
97
|
+
/**
|
|
98
|
+
* Convert a `shadowOptions` object (e.g. `{ delegatesFocus: true }`) into
|
|
99
|
+
* DSD template attribute entries (e.g. `{ shadowrootdelegatesfocus: "" }`).
|
|
100
|
+
*/
|
|
101
|
+
export declare function shadowOptionsToAttributes(shadowOptions: Record<string, unknown> | undefined): Record<string, string>;
|
|
67
102
|
export declare function generateFTemplates(options?: GenerateFTemplatesOptions): Promise<void>;
|
|
68
103
|
export {};
|
|
69
104
|
//# sourceMappingURL=generate-templates.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-templates.d.ts","sourceRoot":"","sources":["../../../src/build/generate-templates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AA8BH,MAAM,WAAW,yBAAyB;IACtC;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"generate-templates.d.ts","sourceRoot":"","sources":["../../../src/build/generate-templates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AA8BH,MAAM,WAAW,yBAAyB;IACtC;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAEtE;;;;;;;;;;OAUG;IACH,oBAAoB,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;CACvD;AAED;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAChC,cAAc,EAAE,MAAM,KACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;AAExF;;;;;;;;;;GAUG;AACH,wBAAsB,uBAAuB,CACzC,cAAc,EAAE,MAAM,GACvB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,CAY9C;AAED,MAAM,WAAW,YAAY;IACzB,IAAI,EAAE,MAAM,GAAG,mBAAmB,CAAC;IACnC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,UAAU,OAAO;IACb,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9B,OAAO,CAAC,EAAE,GAAG,CAAC;IACd,WAAW,CAAC,EAAE;QACV,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC;KACrC,CAAC;CACL;AAmFD;;;GAGG;AACH,wBAAgB,eAAe,CAC3B,YAAY,EAAE,YAAY,EAC1B,aAAa,EAAE,MAAM,GACtB,MAAM,GAAG,IAAI,CA4Hf;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CACrC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GACnD,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAmBxB;AAED,wBAAsB,kBAAkB,CACpC,OAAO,GAAE,yBAA8B,GACxC,OAAO,CAAC,IAAI,CAAC,CAgFf"}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* await generateWebuiTemplates({ cwd: process.cwd(), tagPrefix: "mai" });
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
|
+
import { type ShadowOptionsResolver } from "./generate-templates.js";
|
|
20
21
|
export interface GenerateWebuiTemplatesOptions {
|
|
21
22
|
/**
|
|
22
23
|
* Root directory of the package. Defaults to `process.cwd()`.
|
|
@@ -49,6 +50,18 @@ export interface GenerateWebuiTemplatesOptions {
|
|
|
49
50
|
* Optional formatter function applied to generated HTML before writing.
|
|
50
51
|
*/
|
|
51
52
|
format?: (html: string, filePath: string) => string | Promise<string>;
|
|
53
|
+
/**
|
|
54
|
+
* Resolves shadow DOM options for a given template module path.
|
|
55
|
+
* Returns a `shadowOptions` object (e.g. `{ delegatesFocus: true }`) or
|
|
56
|
+
* `undefined` if the component has no special shadow options.
|
|
57
|
+
*
|
|
58
|
+
* Defaults to {@link definitionAsyncResolver}, which loads a companion
|
|
59
|
+
* `*.definition-async.js` module next to each template. Set to `null`
|
|
60
|
+
* to disable shadow options resolution.
|
|
61
|
+
*
|
|
62
|
+
* @default definitionAsyncResolver
|
|
63
|
+
*/
|
|
64
|
+
resolveShadowOptions?: ShadowOptionsResolver | null;
|
|
52
65
|
}
|
|
53
66
|
export declare function generateWebuiTemplates(options?: GenerateWebuiTemplatesOptions): Promise<void>;
|
|
54
67
|
//# sourceMappingURL=generate-webui-templates.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-webui-templates.d.ts","sourceRoot":"","sources":["../../../src/build/generate-webui-templates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;
|
|
1
|
+
{"version":3,"file":"generate-webui-templates.d.ts","sourceRoot":"","sources":["../../../src/build/generate-webui-templates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAQH,OAAO,EAGH,KAAK,qBAAqB,EAG7B,MAAM,yBAAyB,CAAC;AAQjC,MAAM,WAAW,6BAA6B;IAC1C;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAEtE;;;;;;;;;;OAUG;IACH,oBAAoB,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;CACvD;AA4CD,wBAAsB,sBAAsB,CACxC,OAAO,GAAE,6BAAkC,GAC5C,OAAO,CAAC,IAAI,CAAC,CA6Ef"}
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import type { Locator, Page } from "@playwright/test";
|
|
2
2
|
export type ThemeTokens = Record<string, string | number | boolean>;
|
|
3
|
+
/**
|
|
4
|
+
* The initial attributes for the fixture's template, where boolean attributes are
|
|
5
|
+
* represented as `true` and omitted when `false`. This allows for a more intuitive
|
|
6
|
+
* configuration of boolean attributes in the template options.
|
|
7
|
+
*/
|
|
3
8
|
export type InitialTemplateAttributes = Record<string, string | true>;
|
|
9
|
+
/**
|
|
10
|
+
* The attributes for the fixture's template, where boolean attributes can be represented as `true`
|
|
11
|
+
* or `false`. When `true`, the attribute will be included without a value (e.g., `disabled`), and
|
|
12
|
+
* when `false`, the attribute will be omitted entirely. This type is used for updating the
|
|
13
|
+
* template, allowing for both adding and removing boolean attributes.
|
|
14
|
+
*/
|
|
4
15
|
export type TemplateAttributes = Record<string, string | boolean>;
|
|
5
16
|
/**
|
|
6
17
|
* The options for configuring the fixture's template.
|
|
@@ -9,6 +20,10 @@ export type InitialTemplateOptions = {
|
|
|
9
20
|
attributes?: InitialTemplateAttributes;
|
|
10
21
|
innerHTML?: string;
|
|
11
22
|
};
|
|
23
|
+
/**
|
|
24
|
+
* The options for updating the fixture's template, where `attributes` can include boolean values to
|
|
25
|
+
* add or remove attributes from the element.
|
|
26
|
+
*/
|
|
12
27
|
export type FixtureOptions = Omit<InitialTemplateOptions, "attributes"> & {
|
|
13
28
|
attributes?: TemplateAttributes;
|
|
14
29
|
};
|
|
@@ -36,6 +51,19 @@ export declare class CSRFixture {
|
|
|
36
51
|
protected readonly innerHTML: string;
|
|
37
52
|
/**
|
|
38
53
|
* Additional custom elements to wait for before running the test.
|
|
54
|
+
*
|
|
55
|
+
* @remarks
|
|
56
|
+
* This is useful for fixtures that depend on multiple custom elements being defined
|
|
57
|
+
* and stable before the test can run. By specifying additional tag names here, the
|
|
58
|
+
* fixture will wait for these elements to be defined before proceeding. Ensure that
|
|
59
|
+
* any elements specified here are included on the page and properly defined to
|
|
60
|
+
* prevent test timeouts.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* test.use({
|
|
64
|
+
* tagName: "fast-dropdown",
|
|
65
|
+
* waitFor: ["fast-listbox", "fast-option"],
|
|
66
|
+
* });
|
|
39
67
|
*/
|
|
40
68
|
protected readonly waitFor: string[];
|
|
41
69
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"csr-fixture.d.ts","sourceRoot":"","sources":["../../../src/fixtures/csr-fixture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAEtD,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;AAEpE,MAAM,MAAM,yBAAyB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;AAEtE,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;AAElE;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACjC,UAAU,CAAC,EAAE,yBAAyB,CAAC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,sBAAsB,EAAE,YAAY,CAAC,GAAG;IACtE,UAAU,CAAC,EAAE,kBAAkB,CAAC;CACnC,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,sBAAsB,GAAG,MAAM,CAAC;AAEhE;;GAEG;AACH,qBAAa,UAAU;
|
|
1
|
+
{"version":3,"file":"csr-fixture.d.ts","sourceRoot":"","sources":["../../../src/fixtures/csr-fixture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAEtD,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;AAEpE;;;;GAIG;AACH,MAAM,MAAM,yBAAyB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;AAEtE;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;AAElE;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACjC,UAAU,CAAC,EAAE,yBAAyB,CAAC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,sBAAsB,EAAE,YAAY,CAAC,GAAG;IACtE,UAAU,CAAC,EAAE,kBAAkB,CAAC;CACnC,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,sBAAsB,GAAG,MAAM,CAAC;AAEhE;;GAEG;AACH,qBAAa,UAAU;aA2CC,IAAI,EAAE,IAAI;IA1C9B;;OAEG;IACH,SAAgB,OAAO,EAAE,OAAO,CAAC;IAEjC;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEnC;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAErC;;;;;;;;;;;;;;;OAeG;IACH,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAErC;;;;;;;OAOG;IACH,YACoB,IAAI,EAAE,IAAI,EAC1B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,MAAM,EAAO,EAMzB;IAED;;;;;OAKG;IACG,WAAW,CAAC,OAAO,EAAE,UAAU,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5E;IAED;;;;OAIG;IACG,IAAI,CAAC,GAAG,GAAE,MAAY,iBAE3B;IAED;;;;;OAKG;IACG,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CASpD;IAED;;OAEG;IACH,OAAO,CAAC,eAAe;IAkBvB;;;;;;;;;;;;;;OAcG;IACG,WAAW,CAAC,iBAAiB,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBtE;IAED;;;;;OAKG;IACH,UAAgB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAqBhD;IAED;;;;;;OAMG;IACG,cAAc,CAChB,OAAO,EAAE,MAAM,GAAG,OAAO,EACzB,OAAO,EAAE,cAAc,GACxB,OAAO,CAAC,IAAI,CAAC,CAuBf;IAED;;;;;;OAMG;IACG,oBAAoB,CACtB,OAAO,GAAE,MAAqB,EAC9B,GAAG,QAAQ,EAAE,MAAM,EAAE,GACtB,OAAO,CAAC,IAAI,CAAC,CAUf;CACJ"}
|
|
@@ -11,6 +11,25 @@ export declare class SSRFixture extends CSRFixture {
|
|
|
11
11
|
* Whether the template has been rendered.
|
|
12
12
|
*/
|
|
13
13
|
private templateRendered;
|
|
14
|
+
/**
|
|
15
|
+
* The internal reference to the generated fixture URL.
|
|
16
|
+
*/
|
|
17
|
+
private _url?;
|
|
18
|
+
/**
|
|
19
|
+
* The URL of the generated SSR fixture page. This is set after {@link setTemplate}
|
|
20
|
+
* is called and the page navigates to the generated fixture.
|
|
21
|
+
*/
|
|
22
|
+
get url(): string | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Creates an instance of the SSRFixture.
|
|
25
|
+
*
|
|
26
|
+
* @param page - The Playwright page object.
|
|
27
|
+
* @param tagName - The tag name of the custom element.
|
|
28
|
+
* @param innerHTML - The inner HTML of the custom element.
|
|
29
|
+
* @param waitFor - Additional custom elements to wait for.
|
|
30
|
+
* @param testId - The test ID for the SSR fixture.
|
|
31
|
+
* @param testTitle - The test title for the SSR fixture.
|
|
32
|
+
*/
|
|
14
33
|
constructor(page: Page, tagName: string, innerHTML?: string, waitFor?: string[], testId?: string, testTitle?: string);
|
|
15
34
|
/**
|
|
16
35
|
* Buffers style tags added before {@link setTemplate} so they can be
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr-fixture.d.ts","sourceRoot":"","sources":["../../../src/fixtures/ssr-fixture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAEtE,qBAAa,UAAW,SAAQ,UAAU;
|
|
1
|
+
{"version":3,"file":"ssr-fixture.d.ts","sourceRoot":"","sources":["../../../src/fixtures/ssr-fixture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAEtE,qBAAa,UAAW,SAAQ,UAAU;IAuClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;IAvC/B;;OAEG;IACH,OAAO,CAAC,aAAa,CAA4C;IAEjE;;OAEG;IACH,OAAO,CAAC,gBAAgB,CAAS;IAEjC;;OAEG;IACH,OAAO,CAAC,IAAI,CAAC,CAAS;IAEtB;;;OAGG;IACH,IAAW,GAAG,IAAI,MAAM,GAAG,SAAS,CAEnC;IAED;;;;;;;;;OASG;IACH,YACI,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,MAAM,EACf,SAAS,GAAE,MAAW,EACtB,OAAO,GAAE,MAAM,EAAO,EACL,MAAM,CAAC,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM,EAGtC;IAED;;;;;;;OAOG;IACY,WAAW,CACtB,OAAO,EAAE,UAAU,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,GAC5C,OAAO,CAAC,IAAI,CAAC,CAMf;IAED;;;;;;;;;;;OAWG;IACY,WAAW,CAAC,iBAAiB,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqE/E;IAED;;OAEG;IACH,OAAO,CAAC,eAAe;CAqB1B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/ssr/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAOH,MAAM,WAAW,qBAAqB;IAClC;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,kBAAkB;IAC/B;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;OAIG;IACH,UAAU,CAAC,EAAE,qBAAqB,EAAE,CAAC;IAErC;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IACzB,qEAAqE;IACrE,QAAQ,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,YAAY,EAAE,MAAM,CAAC;CACxB;AA4CD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CA6BtD;AA0FD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAa1E;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAwCvE;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAsBpF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG;IAC5D,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,YAAY,CAAC;CAC9D,
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/ssr/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAOH,MAAM,WAAW,qBAAqB;IAClC;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,kBAAkB;IAC/B;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;OAIG;IACH,UAAU,CAAC,EAAE,qBAAqB,EAAE,CAAC;IAErC;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IACzB,qEAAqE;IACrE,QAAQ,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,YAAY,EAAE,MAAM,CAAC;CACxB;AA4CD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CA6BtD;AA0FD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAa1E;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAwCvE;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAsBpF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG;IAC5D,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,YAAY,CAAC;CAC9D,CAkMA"}
|
|
@@ -29,6 +29,30 @@ function wrapDefaultExpression(expression) {
|
|
|
29
29
|
function attributeDirective(name, value) {
|
|
30
30
|
return `${attributeDirectivePrefix}${name}="${wrapClientExpression(value)}"`;
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Convention-based resolver that loads a companion `*.definition-async.js`
|
|
34
|
+
* module next to the template module and returns its `shadowOptions`.
|
|
35
|
+
*
|
|
36
|
+
* For a template at `dist/textarea/textarea.template.js`, this looks for
|
|
37
|
+
* `dist/textarea/textarea.definition-async.js`.
|
|
38
|
+
*
|
|
39
|
+
* This is the default resolver used by {@link generateFTemplates} and
|
|
40
|
+
* {@link generateWebuiTemplates} when `resolveShadowOptions` is not
|
|
41
|
+
* specified.
|
|
42
|
+
*/
|
|
43
|
+
export async function definitionAsyncResolver(templateJsPath) {
|
|
44
|
+
const dir = path.dirname(templateJsPath);
|
|
45
|
+
const base = path.basename(templateJsPath, ".template.js");
|
|
46
|
+
const defAsyncPath = path.resolve(dir, `${base}.definition-async.js`);
|
|
47
|
+
try {
|
|
48
|
+
const mod = await import(pathToFileURL(defAsyncPath).href);
|
|
49
|
+
const definition = mod.definition ?? mod.default;
|
|
50
|
+
return definition?.shadowOptions;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
32
56
|
/**
|
|
33
57
|
* Extract a readable binding expression from a factory's evaluate function.
|
|
34
58
|
*/
|
|
@@ -199,6 +223,29 @@ export function convertTemplate(viewTemplate, componentName) {
|
|
|
199
223
|
fInner = fInner.replace(/(<template[^>]*>)/, `$1${stylesMarker}`);
|
|
200
224
|
return `<f-template name="${componentName}" shadowrootmode="open">\n${fInner}\n</f-template>\n`;
|
|
201
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* Convert a `shadowOptions` object (e.g. `{ delegatesFocus: true }`) into
|
|
228
|
+
* DSD template attribute entries (e.g. `{ shadowrootdelegatesfocus: "" }`).
|
|
229
|
+
*/
|
|
230
|
+
export function shadowOptionsToAttributes(shadowOptions) {
|
|
231
|
+
const attrs = {};
|
|
232
|
+
if (!shadowOptions) {
|
|
233
|
+
return attrs;
|
|
234
|
+
}
|
|
235
|
+
for (const [key, value] of Object.entries(shadowOptions)) {
|
|
236
|
+
if (key === "mode") {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const attrName = `shadowroot${key.toLowerCase()}`;
|
|
240
|
+
if (value === true) {
|
|
241
|
+
attrs[attrName] = "";
|
|
242
|
+
}
|
|
243
|
+
else if (typeof value === "string" && value !== "") {
|
|
244
|
+
attrs[attrName] = value;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return attrs;
|
|
248
|
+
}
|
|
202
249
|
export async function generateFTemplates(options = {}) {
|
|
203
250
|
installDomShim();
|
|
204
251
|
const cwd = options.cwd ?? process.cwd();
|
|
@@ -220,7 +267,19 @@ export async function generateFTemplates(options = {}) {
|
|
|
220
267
|
if (!fTemplateHtml) {
|
|
221
268
|
continue;
|
|
222
269
|
}
|
|
270
|
+
// Resolve shadow options and inject them into the <f-template> tag.
|
|
271
|
+
const resolver = options.resolveShadowOptions === null
|
|
272
|
+
? undefined
|
|
273
|
+
: (options.resolveShadowOptions ?? definitionAsyncResolver);
|
|
274
|
+
const shadowOpts = resolver ? await resolver(jsFilePath) : undefined;
|
|
275
|
+
const shadowAttrs = shadowOptionsToAttributes(shadowOpts);
|
|
223
276
|
let html = fTemplateHtml;
|
|
277
|
+
if (Object.keys(shadowAttrs).length > 0) {
|
|
278
|
+
const extraAttrs = Object.entries(shadowAttrs)
|
|
279
|
+
.map(([k, v]) => (v ? ` ${k}="${v}"` : ` ${k}`))
|
|
280
|
+
.join("");
|
|
281
|
+
html = html.replace(/(<f-template[^>]*)(>)/, `$1${extraAttrs}$2`);
|
|
282
|
+
}
|
|
224
283
|
if (options.format) {
|
|
225
284
|
try {
|
|
226
285
|
html = await options.format(html, jsFilePath);
|
|
@@ -229,8 +288,9 @@ export async function generateFTemplates(options = {}) {
|
|
|
229
288
|
console.warn(styleText(["yellow", "bold"], "⚠"), `Format failed for ${componentName}:`, formatError.message);
|
|
230
289
|
}
|
|
231
290
|
}
|
|
291
|
+
const relativeDir = path.relative(distDir, path.dirname(jsFilePath));
|
|
232
292
|
const fTemplatePath = outDir
|
|
233
|
-
? path.resolve(outDir, `${componentBaseName}.template.html`)
|
|
293
|
+
? path.resolve(outDir, relativeDir, `${componentBaseName}.template.html`)
|
|
234
294
|
: path.resolve(path.dirname(jsFilePath), `${componentBaseName}.template.html`);
|
|
235
295
|
await mkdir(path.dirname(fTemplatePath), { recursive: true });
|
|
236
296
|
await writeFile(fTemplatePath, html, "utf8");
|
|
@@ -23,31 +23,9 @@ import { pathToFileURL } from "node:url";
|
|
|
23
23
|
import { styleText } from "node:util";
|
|
24
24
|
import { closeExpression, openExpression } from "@microsoft/fast-html/syntax.js";
|
|
25
25
|
import { installDomShim } from "./dom-shim.js";
|
|
26
|
-
import { convertTemplate } from "./generate-templates.js";
|
|
26
|
+
import { convertTemplate, definitionAsyncResolver, shadowOptionsToAttributes, } from "./generate-templates.js";
|
|
27
27
|
const stylesMarker = `${openExpression}styles${closeExpression}`;
|
|
28
28
|
const escapedStylesMarker = new RegExp(stylesMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g");
|
|
29
|
-
/**
|
|
30
|
-
* Try to load shadow options from a companion `*.definition-async.js`
|
|
31
|
-
* module next to the template module. Returns an object with template
|
|
32
|
-
* attribute strings to add (e.g. `shadowrootdelegatesfocus`).
|
|
33
|
-
*/
|
|
34
|
-
async function loadShadowAttributes(templateJsPath) {
|
|
35
|
-
const dir = path.dirname(templateJsPath);
|
|
36
|
-
const base = path.basename(templateJsPath, ".template.js");
|
|
37
|
-
const defAsyncPath = path.resolve(dir, `${base}.definition-async.js`);
|
|
38
|
-
const attrs = {};
|
|
39
|
-
try {
|
|
40
|
-
const mod = await import(pathToFileURL(defAsyncPath).href);
|
|
41
|
-
const definition = mod.definition ?? mod.default;
|
|
42
|
-
if (definition?.shadowOptions?.delegatesFocus) {
|
|
43
|
-
attrs.shadowrootdelegatesfocus = "";
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
// No definition-async module or it failed to load — skip.
|
|
48
|
-
}
|
|
49
|
-
return attrs;
|
|
50
|
-
}
|
|
51
29
|
/**
|
|
52
30
|
* Transform an f-template string into a webui template by replacing the
|
|
53
31
|
* `<f-template>` wrapper with `<template shadowrootmode="open">` and
|
|
@@ -97,7 +75,11 @@ export async function generateWebuiTemplates(options = {}) {
|
|
|
97
75
|
if (!fTemplateHtml) {
|
|
98
76
|
continue;
|
|
99
77
|
}
|
|
100
|
-
const
|
|
78
|
+
const resolver = options.resolveShadowOptions === null
|
|
79
|
+
? undefined
|
|
80
|
+
: (options.resolveShadowOptions ?? definitionAsyncResolver);
|
|
81
|
+
const shadowOpts = resolver ? await resolver(jsFilePath) : undefined;
|
|
82
|
+
const shadowAttrs = shadowOptionsToAttributes(shadowOpts);
|
|
101
83
|
let html = fTemplateToWebui(fTemplateHtml, shadowAttrs);
|
|
102
84
|
if (options.format) {
|
|
103
85
|
try {
|
|
@@ -107,8 +89,9 @@ export async function generateWebuiTemplates(options = {}) {
|
|
|
107
89
|
console.warn(styleText(["yellow", "bold"], "⚠"), `Format failed for ${componentName}:`, formatError.message);
|
|
108
90
|
}
|
|
109
91
|
}
|
|
92
|
+
const relativeDir = path.relative(distDir, path.dirname(jsFilePath));
|
|
110
93
|
const webuiPath = outDir
|
|
111
|
-
? path.resolve(outDir, `${componentBaseName}.template-webui.html`)
|
|
94
|
+
? path.resolve(outDir, relativeDir, `${componentBaseName}.template-webui.html`)
|
|
112
95
|
: path.resolve(path.dirname(jsFilePath), `${componentBaseName}.template-webui.html`);
|
|
113
96
|
await mkdir(path.dirname(webuiPath), { recursive: true });
|
|
114
97
|
await writeFile(webuiPath, html, "utf8");
|
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
import { CSRFixture } from "./csr-fixture.js";
|
|
2
2
|
export class SSRFixture extends CSRFixture {
|
|
3
|
+
/**
|
|
4
|
+
* The URL of the generated SSR fixture page. This is set after {@link setTemplate}
|
|
5
|
+
* is called and the page navigates to the generated fixture.
|
|
6
|
+
*/
|
|
7
|
+
get url() {
|
|
8
|
+
return this._url;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Creates an instance of the SSRFixture.
|
|
12
|
+
*
|
|
13
|
+
* @param page - The Playwright page object.
|
|
14
|
+
* @param tagName - The tag name of the custom element.
|
|
15
|
+
* @param innerHTML - The inner HTML of the custom element.
|
|
16
|
+
* @param waitFor - Additional custom elements to wait for.
|
|
17
|
+
* @param testId - The test ID for the SSR fixture.
|
|
18
|
+
* @param testTitle - The test title for the SSR fixture.
|
|
19
|
+
*/
|
|
3
20
|
constructor(page, tagName, innerHTML = "", waitFor = [], testId, testTitle) {
|
|
4
21
|
super(page, tagName, innerHTML, waitFor);
|
|
5
22
|
this.testId = testId;
|
|
@@ -86,7 +103,8 @@ export class SSRFixture extends CSRFixture {
|
|
|
86
103
|
if (!result.url) {
|
|
87
104
|
throw new Error(`Invalid response from server: ${JSON.stringify(result)}`);
|
|
88
105
|
}
|
|
89
|
-
|
|
106
|
+
this._url = result.url;
|
|
107
|
+
await this.page.goto(this._url);
|
|
90
108
|
await this.waitForStability();
|
|
91
109
|
this.templateRendered = true;
|
|
92
110
|
this.pendingStyles.length = 0;
|
package/dist/esm/ssr/render.js
CHANGED
|
@@ -309,7 +309,6 @@ export function createSSRRenderer(options) {
|
|
|
309
309
|
// Populate maps and collect default state from CEM.
|
|
310
310
|
const defaultStateByTag = new Map();
|
|
311
311
|
for (const art of artifacts) {
|
|
312
|
-
const tagName = `${tagPrefix}-${art.componentName}`;
|
|
313
312
|
if (art.fTemplate) {
|
|
314
313
|
fTemplatesByName.set(art.componentName, art.fTemplate);
|
|
315
314
|
}
|
|
@@ -339,7 +338,16 @@ export function createSSRRenderer(options) {
|
|
|
339
338
|
const parsed = JSON.parse(wasm.parse_f_templates(styled));
|
|
340
339
|
const entry = parsed.find((t) => t.name !== null);
|
|
341
340
|
if (entry) {
|
|
342
|
-
|
|
341
|
+
const tagName = `${tagPrefix}-${name}`;
|
|
342
|
+
const attrs = entry.shadowrootAttributes ?? [];
|
|
343
|
+
// Use the object format when there are extra shadowroot attributes
|
|
344
|
+
// beyond the default shadowrootmode, so the WASM renderer can
|
|
345
|
+
// propagate them (e.g. shadowrootdelegatesfocus) onto the DSD
|
|
346
|
+
// <template> element.
|
|
347
|
+
const hasExtraAttrs = attrs.some(a => a.name !== "shadowrootmode" && a.name !== "shadowroot");
|
|
348
|
+
templatesMap[tagName] = hasExtraAttrs
|
|
349
|
+
? { content: entry.content, shadowrootAttributes: attrs }
|
|
350
|
+
: entry.content;
|
|
343
351
|
}
|
|
344
352
|
else {
|
|
345
353
|
console.warn(`No named template found for ${tagPrefix}-${name}`);
|
|
@@ -348,11 +356,17 @@ export function createSSRRenderer(options) {
|
|
|
348
356
|
const templatesJson = JSON.stringify(templatesMap);
|
|
349
357
|
// Concatenate all f-templates (with styles) for client hydration.
|
|
350
358
|
const allFTemplates = [...fTemplatesByName.values()].join("\n");
|
|
351
|
-
//
|
|
352
|
-
|
|
359
|
+
// Build preload links: theme stylesheet + component stylesheets.
|
|
360
|
+
const preloadParts = [];
|
|
353
361
|
if (options.themeStylesheet) {
|
|
354
|
-
|
|
362
|
+
preloadParts.push(`<link rel="stylesheet" href="${options.themeStylesheet}">`);
|
|
355
363
|
}
|
|
364
|
+
for (const stylesUrl of styleUrlsByName.values()) {
|
|
365
|
+
if (stylesUrl) {
|
|
366
|
+
preloadParts.push(`<link rel="preload" href="${stylesUrl}" as="style">`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
const preloadLinks = preloadParts.join("\n");
|
|
356
370
|
return {
|
|
357
371
|
render(queryObj = {}) {
|
|
358
372
|
const entryHtml = buildEntryHtml(queryObj);
|
|
@@ -368,6 +382,25 @@ export function createSSRRenderer(options) {
|
|
|
368
382
|
// Extract body content from the rendered document.
|
|
369
383
|
const bodyMatch = rendered.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
|
|
370
384
|
fixture = bodyMatch?.[1] ?? entryHtml;
|
|
385
|
+
// The WASM renderer only injects DSD for top-level
|
|
386
|
+
// custom elements. Inject DSD for any nested custom
|
|
387
|
+
// elements that have templates but weren't rendered.
|
|
388
|
+
for (const nestedTag of Object.keys(templatesMap)) {
|
|
389
|
+
// Match opening tags that don't already have a
|
|
390
|
+
// DSD <template> as their first child.
|
|
391
|
+
const openTagRe = new RegExp(`(<${nestedTag}(?=[\\s>/])[^>]*>)(?!\\s*<template[\\s>])`, "g");
|
|
392
|
+
fixture = fixture.replace(openTagRe, (_, open) => {
|
|
393
|
+
// Render this element in isolation.
|
|
394
|
+
const solo = wasm.render_with_templates(`${open}</${nestedTag}>`, templatesJson, JSON.stringify(defaultStateByTag.get(nestedTag) ?? {}), "camelCase");
|
|
395
|
+
// Extract the DSD that was injected.
|
|
396
|
+
const dsdStart = solo.indexOf("<template shadowrootmode");
|
|
397
|
+
const dsdEnd = solo.lastIndexOf("</template>");
|
|
398
|
+
if (dsdStart !== -1 && dsdEnd > dsdStart) {
|
|
399
|
+
return `${open}${solo.slice(dsdStart, dsdEnd + "</template>".length)}`;
|
|
400
|
+
}
|
|
401
|
+
return open;
|
|
402
|
+
});
|
|
403
|
+
}
|
|
371
404
|
}
|
|
372
405
|
catch (e) {
|
|
373
406
|
// Fall back to the raw entry HTML if rendering fails.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@microsoft/fast-test-harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"author": {
|
|
5
5
|
"name": "Microsoft",
|
|
6
6
|
"url": "https://discord.gg/FcSNfg4"
|
|
@@ -28,6 +28,10 @@
|
|
|
28
28
|
"types": "./dist/dts/build/*.d.ts",
|
|
29
29
|
"default": "./dist/esm/build/*.js"
|
|
30
30
|
},
|
|
31
|
+
"./fixtures/*.js": {
|
|
32
|
+
"types": "./dist/dts/fixtures/*.d.ts",
|
|
33
|
+
"default": "./dist/esm/fixtures/*.js"
|
|
34
|
+
},
|
|
31
35
|
"./ssr/*.js": {
|
|
32
36
|
"types": "./dist/dts/ssr/*.d.ts",
|
|
33
37
|
"default": "./dist/esm/ssr/*.js"
|
|
@@ -38,6 +42,7 @@
|
|
|
38
42
|
},
|
|
39
43
|
"./public/*": "./public/*",
|
|
40
44
|
"./template.html": "./test/src/test-widget/test-widget.template.html",
|
|
45
|
+
"./input/template.html": "./test/src/test-widget/input/test-input.template.html",
|
|
41
46
|
"./styles.css": "./test/src/test-widget/test-widget.styles.css",
|
|
42
47
|
"./server.mjs": "./server.mjs",
|
|
43
48
|
"./start.mjs": "./start.mjs",
|
|
@@ -48,36 +53,42 @@
|
|
|
48
53
|
"./package.json": "./package.json"
|
|
49
54
|
},
|
|
50
55
|
"scripts": {
|
|
51
|
-
"clean": "clean dist temp test-results",
|
|
56
|
+
"clean": "clean dist temp test-results test/temp",
|
|
52
57
|
"build": "npm run build:tsc",
|
|
53
58
|
"build:tsc": "tsgo -p tsconfig.build.json",
|
|
54
|
-
"
|
|
59
|
+
"lint": "biome-changed",
|
|
60
|
+
"lint:fix": "biome-changed -- --fix",
|
|
61
|
+
"prepublishOnly": "npm run clean && npm run build",
|
|
62
|
+
"test": "npm run lint && npm run test:node && npm run test:playwright",
|
|
55
63
|
"test:node": "node --test --experimental-test-isolation=none \"**/*.test.ts\"",
|
|
56
|
-
"test:playwright": "playwright test"
|
|
64
|
+
"test:playwright": "playwright test",
|
|
65
|
+
"test:chromium": "playwright test --project=chromium"
|
|
57
66
|
},
|
|
58
67
|
"files": [
|
|
59
68
|
"dist",
|
|
60
|
-
"playwright.config.mjs",
|
|
61
|
-
"playwright.config.d.ts",
|
|
62
69
|
"public",
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"vite.config.mjs",
|
|
66
|
-
"vite.config.d.ts"
|
|
70
|
+
"*.mjs",
|
|
71
|
+
"*.d.ts"
|
|
67
72
|
],
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"@microsoft/fast-element": "^2.10.4",
|
|
75
|
+
"@microsoft/fast-build": "^0.7.0",
|
|
76
|
+
"@microsoft/fast-html": "^1.0.0-alpha.53"
|
|
77
|
+
},
|
|
68
78
|
"dependencies": {
|
|
69
79
|
"cheerio": "1.2.0"
|
|
70
80
|
},
|
|
71
|
-
"devDependencies": {
|
|
72
|
-
"@microsoft/fast-html": "*"
|
|
73
|
-
},
|
|
74
81
|
"peerDependencies": {
|
|
75
|
-
"@microsoft/fast-
|
|
76
|
-
"@microsoft/fast-
|
|
82
|
+
"@microsoft/fast-element": "^2.10.4 || ^3.0.0",
|
|
83
|
+
"@microsoft/fast-build": "^0.7.0",
|
|
84
|
+
"@microsoft/fast-html": ">=1.0.0-alpha.53 <1.0.0",
|
|
77
85
|
"@playwright/test": ">=1.40.0",
|
|
78
86
|
"vite": ">=7.0.0"
|
|
79
87
|
},
|
|
80
88
|
"peerDependenciesMeta": {
|
|
89
|
+
"@microsoft/fast-element": {
|
|
90
|
+
"optional": true
|
|
91
|
+
},
|
|
81
92
|
"@microsoft/fast-html": {
|
|
82
93
|
"optional": true
|
|
83
94
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dom-shim.test.d.ts","sourceRoot":"","sources":["../../../src/build/dom-shim.test.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"generate-stylesheets.test.d.ts","sourceRoot":"","sources":["../../../src/build/generate-stylesheets.test.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"generate-templates.test.d.ts","sourceRoot":"","sources":["../../../src/build/generate-templates.test.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"generate-webui-templates.test.d.ts","sourceRoot":"","sources":["../../../src/build/generate-webui-templates.test.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"csr-fixture.pw.spec.d.ts","sourceRoot":"","sources":["../../../src/fixtures/csr-fixture.pw.spec.ts"],"names":[],"mappings":""}
|