Skip to content

Commit 7383168

Browse files
authored
chore: prepare fast-test-harness package for publishing (#7506)
# Pull Request ## 📖 Description Prepares `@microsoft/fast-test-harness` for npm publishing by cleaning up the package layout and exports. - Excluded test files (`.test.ts`, `.spec.ts`) from the build output via `tsconfig.build.json` - Simplified the `files` array to use wildcards (`*.mjs`, `*.d.ts`) instead of enumerating each root-level file - Added `./fixtures/*.js` to the package exports map so fixture classes can be imported directly - Exported `setTemplate` and `updateTemplate` option types from `CSRFixture` and `SSRFixture` - Updated `README` exports table to match the current public API surface - Updated package scripts (`test:node`, `test:playwright`) ## 📑 Test Plan All existing tests pass: ``` npm run test # runs test:node && test:playwright ``` 96 Node.js unit tests and 129 Playwright E2E tests across Chromium, Firefox, and WebKit. Verified `npm pack --dry-run` produces a clean tarball with 42 files (no test artifacts). ## ✅ Checklist ### General - [x] I have included a change request file using `$ npm run change` - [ ] I have added tests for my changes. - [x] I have tested my changes. - [x] I have updated the project documentation to reflect my changes. - [x] I have read the [CONTRIBUTING](https://github.com/microsoft/fast/blob/main/CONTRIBUTING.md) documentation and followed the [standards](https://github.com/microsoft/fast/blob/main/CODE_OF_CONDUCT.md#our-standards) for this project.
1 parent aad6f54 commit 7383168

9 files changed

Lines changed: 147 additions & 41 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "none",
3+
"comment": "chore: prepare package for publish",
4+
"packageName": "@microsoft/fast-test-harness",
5+
"email": "863023+radium-v@users.noreply.github.com",
6+
"dependentChangeType": "none"
7+
}

package-lock.json

Lines changed: 9 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/fast-test-harness/README.md

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44

55
The `fast-test-harness` package is a Playwright testing harness for FAST Element web components with CSR and SSR support.
66

7+
## Requirements
8+
9+
- Node.js 22.18 or later
10+
- Playwright 1.56 or later
11+
712
## Installation
813

914
To install `fast-test-harness` using `npm`:
@@ -12,6 +17,21 @@ To install `fast-test-harness` using `npm`:
1217
npm install --save-dev @microsoft/fast-test-harness
1318
```
1419

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+
1535
## Writing tests
1636

1737
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");
6787
| `waitFor` | `string[]` | `[]` | Additional elements to wait for before testing |
6888
| `ssr` | `boolean` | `false` | Use SSR mode (or set `PLAYWRIGHT_TEST_SSR=true`) |
6989

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-
8590
### CSR files
8691

8792
**`index.html`** loads a script that registers your components:
@@ -258,10 +263,11 @@ CLI flags take precedence over environment variables.
258263

259264
| Specifier | Contents |
260265
|-----------|----------|
261-
| `@microsoft/fast-test-harness` | `test`, `expect`, `CSRFixture`, `SSRFixture`, `createSSRRenderer`, build utilities |
262-
| `@microsoft/fast-test-harness/server.mjs` | `startServer` |
263-
| `@microsoft/fast-test-harness/ssr/render.js` | `createSSRRenderer`, `ComponentRegistration`, `RenderResult`, `SSRRendererOptions` |
266+
| `@microsoft/fast-test-harness` | `test`, `expect`, `CSRFixture`, `SSRFixture`, `toHaveCustomState`, `installDomShim`, `createSSRRenderer` |
264267
| `@microsoft/fast-test-harness/build/*.js` | `installDomShim`, `generateStylesheets`, `generateFTemplates`, `generateWebuiTemplates` |
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` |
270+
| `@microsoft/fast-test-harness/server.mjs` | `startServer` |
265271
| `@microsoft/fast-test-harness/playwright.config.mjs` | Shared Playwright configuration |
266272
| `@microsoft/fast-test-harness/vite.config.mjs` | Shared Vite configuration |
267273
| `@microsoft/fast-test-harness/public/*` | Static assets (base CSS) |

packages/fast-test-harness/package.json

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
"types": "./dist/dts/build/*.d.ts",
2929
"default": "./dist/esm/build/*.js"
3030
},
31+
"./fixtures/*.js": {
32+
"types": "./dist/dts/fixtures/*.d.ts",
33+
"default": "./dist/esm/fixtures/*.js"
34+
},
3135
"./ssr/*.js": {
3236
"types": "./dist/dts/ssr/*.d.ts",
3337
"default": "./dist/esm/ssr/*.js"
@@ -48,36 +52,42 @@
4852
"./package.json": "./package.json"
4953
},
5054
"scripts": {
51-
"clean": "clean dist temp test-results",
55+
"clean": "clean dist temp test-results test/temp",
5256
"build": "npm run build:tsc",
5357
"build:tsc": "tsgo -p tsconfig.build.json",
54-
"test": "npm run test:node && npm run test:playwright",
58+
"lint": "biome-changed",
59+
"lint:fix": "biome-changed -- --fix",
60+
"prepublishOnly": "npm run clean && npm run build",
61+
"test": "npm run lint && npm run test:node && npm run test:playwright",
5562
"test:node": "node --test --experimental-test-isolation=none \"**/*.test.ts\"",
56-
"test:playwright": "playwright test"
63+
"test:playwright": "playwright test",
64+
"test:chromium": "playwright test --project=chromium"
5765
},
5866
"files": [
5967
"dist",
60-
"playwright.config.mjs",
61-
"playwright.config.d.ts",
6268
"public",
63-
"server.mjs",
64-
"start.mjs",
65-
"vite.config.mjs",
66-
"vite.config.d.ts"
69+
"*.mjs",
70+
"*.d.ts"
6771
],
72+
"devDependencies": {
73+
"@microsoft/fast-element": "^2.10.4",
74+
"@microsoft/fast-build": "^0.6.0",
75+
"@microsoft/fast-html": "^1.0.0-alpha.52"
76+
},
6877
"dependencies": {
6978
"cheerio": "1.2.0"
7079
},
71-
"devDependencies": {
72-
"@microsoft/fast-html": "*"
73-
},
7480
"peerDependencies": {
75-
"@microsoft/fast-build": ">=0.6.0 <1.0.0",
76-
"@microsoft/fast-html": ">=1.0.0-alpha.52 <1.0.0",
81+
"@microsoft/fast-element": "^2.10.4 || ^3.0.0",
82+
"@microsoft/fast-build": "^0.6.0",
83+
"@microsoft/fast-html": ">=1.0.0-alpha.52",
7784
"@playwright/test": ">=1.40.0",
7885
"vite": ">=7.0.0"
7986
},
8087
"peerDependenciesMeta": {
88+
"@microsoft/fast-element": {
89+
"optional": true
90+
},
8191
"@microsoft/fast-html": {
8292
"optional": true
8393
}

packages/fast-test-harness/src/fixtures/csr-fixture.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,19 @@ import type { Locator, Page } from "@playwright/test";
22

33
export type ThemeTokens = Record<string, string | number | boolean>;
44

5+
/**
6+
* The initial attributes for the fixture's template, where boolean attributes are
7+
* represented as `true` and omitted when `false`. This allows for a more intuitive
8+
* configuration of boolean attributes in the template options.
9+
*/
510
export type InitialTemplateAttributes = Record<string, string | true>;
611

12+
/**
13+
* The attributes for the fixture's template, where boolean attributes can be represented as `true`
14+
* or `false`. When `true`, the attribute will be included without a value (e.g., `disabled`), and
15+
* when `false`, the attribute will be omitted entirely. This type is used for updating the
16+
* template, allowing for both adding and removing boolean attributes.
17+
*/
718
export type TemplateAttributes = Record<string, string | boolean>;
819

920
/**
@@ -14,6 +25,10 @@ export type InitialTemplateOptions = {
1425
innerHTML?: string;
1526
};
1627

28+
/**
29+
* The options for updating the fixture's template, where `attributes` can include boolean values to
30+
* add or remove attributes from the element.
31+
*/
1732
export type FixtureOptions = Omit<InitialTemplateOptions, "attributes"> & {
1833
attributes?: TemplateAttributes;
1934
};
@@ -45,6 +60,19 @@ export class CSRFixture {
4560

4661
/**
4762
* Additional custom elements to wait for before running the test.
63+
*
64+
* @remarks
65+
* This is useful for fixtures that depend on multiple custom elements being defined
66+
* and stable before the test can run. By specifying additional tag names here, the
67+
* fixture will wait for these elements to be defined before proceeding. Ensure that
68+
* any elements specified here are included on the page and properly defined to
69+
* prevent test timeouts.
70+
*
71+
* @example
72+
* test.use({
73+
* tagName: "fast-dropdown",
74+
* waitFor: ["fast-listbox", "fast-option"],
75+
* });
4876
*/
4977
protected readonly waitFor: string[];
5078

packages/fast-test-harness/src/fixtures/ssr-fixture.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ export class SSRFixture extends CSRFixture {
1212
*/
1313
private templateRendered = false;
1414

15+
/**
16+
* Creates an instance of the SSRFixture.
17+
*
18+
* @param page - The Playwright page object.
19+
* @param tagName - The tag name of the custom element.
20+
* @param innerHTML - The inner HTML of the custom element.
21+
* @param waitFor - Additional custom elements to wait for.
22+
* @param testId - The test ID for the SSR fixture.
23+
* @param testTitle - The test title for the SSR fixture.
24+
*/
1525
constructor(
1626
page: Page,
1727
tagName: string,

packages/fast-test-harness/src/ssr/render.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,15 +276,18 @@ test.describe("createSSRRenderer", () => {
276276
);
277277
});
278278

279-
test("should return empty preloadLinks without a theme", () => {
279+
test("should not include theme link in preloadLinks when themeStylesheet is omitted", () => {
280280
const { render } = createSSRRenderer({
281281
tagPrefix: "test",
282282
components: [{ name: "widget", packageName: "@microsoft/fast-test-harness" }],
283283
});
284284

285285
const result = render({ tagName: "test-widget" });
286286

287-
assert.strictEqual(result.preloadLinks, "");
287+
assert.ok(
288+
!result.preloadLinks.includes('rel="stylesheet"'),
289+
`should not have a stylesheet link, got: ${result.preloadLinks}`,
290+
);
288291
});
289292

290293
test("should handle raw HTML via the html key", () => {

packages/fast-test-harness/src/ssr/render.ts

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -476,11 +476,17 @@ export function createSSRRenderer(options: SSRRendererOptions): {
476476
// Concatenate all f-templates (with styles) for client hydration.
477477
const allFTemplates = [...fTemplatesByName.values()].join("\n");
478478

479-
// Resolve theme stylesheet if provided.
480-
let preloadLinks = "";
479+
// Build preload links: theme stylesheet + component stylesheets.
480+
const preloadParts: string[] = [];
481481
if (options.themeStylesheet) {
482-
preloadLinks = `<link rel="stylesheet" href="${options.themeStylesheet}">`;
482+
preloadParts.push(`<link rel="stylesheet" href="${options.themeStylesheet}">`);
483483
}
484+
for (const stylesUrl of styleUrlsByName.values()) {
485+
if (stylesUrl) {
486+
preloadParts.push(`<link rel="preload" href="${stylesUrl}" as="style">`);
487+
}
488+
}
489+
const preloadLinks = preloadParts.join("\n");
484490

485491
return {
486492
render(queryObj: Record<string, string> = {}): RenderResult {
@@ -505,6 +511,35 @@ export function createSSRRenderer(options: SSRRendererOptions): {
505511
// Extract body content from the rendered document.
506512
const bodyMatch = rendered.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
507513
fixture = bodyMatch?.[1] ?? entryHtml;
514+
515+
// The WASM renderer only injects DSD for top-level
516+
// custom elements. Inject DSD for any nested custom
517+
// elements that have templates but weren't rendered.
518+
for (const nestedTag of Object.keys(templatesMap)) {
519+
// Match opening tags that don't already have a
520+
// DSD <template> as their first child.
521+
const openTagRe = new RegExp(
522+
`(<${nestedTag}(?=[\\s>/])[^>]*>)(?!\\s*<template[\\s>])`,
523+
"g",
524+
);
525+
526+
fixture = fixture.replace(openTagRe, (_, open: string) => {
527+
// Render this element in isolation.
528+
const solo: string = wasm.render_with_templates(
529+
`${open}</${nestedTag}>`,
530+
templatesJson,
531+
JSON.stringify(defaultStateByTag.get(nestedTag) ?? {}),
532+
"camelCase",
533+
);
534+
// Extract the DSD that was injected.
535+
const dsdStart = solo.indexOf("<template shadowrootmode");
536+
const dsdEnd = solo.lastIndexOf("</template>");
537+
if (dsdStart !== -1 && dsdEnd > dsdStart) {
538+
return `${open}${solo.slice(dsdStart, dsdEnd + "</template>".length)}`;
539+
}
540+
return open;
541+
});
542+
}
508543
} catch (e) {
509544
// Fall back to the raw entry HTML if rendering fails.
510545
console.error("WASM render failed:", e);

packages/fast-test-harness/tsconfig.build.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@
1111
"sourceMap": false,
1212
"outDir": "dist/esm"
1313
},
14-
"include": ["src/**/*.ts"]
14+
"include": ["src/**/*.ts"],
15+
"exclude": ["src/**/*.test.ts", "src/**/*.spec.ts"]
1516
}

0 commit comments

Comments
 (0)