diff --git a/change/@microsoft-fast-test-harness-87ac85bb-3986-4dbc-b9c8-16fecdf194b3.json b/change/@microsoft-fast-test-harness-87ac85bb-3986-4dbc-b9c8-16fecdf194b3.json new file mode 100644 index 00000000000..63bbd6eda0a --- /dev/null +++ b/change/@microsoft-fast-test-harness-87ac85bb-3986-4dbc-b9c8-16fecdf194b3.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: prepare package for publish", + "packageName": "@microsoft/fast-test-harness", + "email": "863023+radium-v@users.noreply.github.com", + "dependentChangeType": "none" +} diff --git a/package-lock.json b/package-lock.json index 2783cbc05cc..d071062b6fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7365,18 +7365,24 @@ "fast-test-harness": "start.mjs" }, "devDependencies": { - "@microsoft/fast-html": "*" + "@microsoft/fast-build": "^0.6.0", + "@microsoft/fast-element": "^2.10.4", + "@microsoft/fast-html": "^1.0.0-alpha.52" }, "engines": { "node": ">=22.18.0" }, "peerDependencies": { - "@microsoft/fast-build": ">=0.6.0 <1.0.0", - "@microsoft/fast-html": ">=1.0.0-alpha.52 <1.0.0", + "@microsoft/fast-build": "^0.6.0", + "@microsoft/fast-element": "^2.10.4 || ^3.0.0", + "@microsoft/fast-html": ">=1.0.0-alpha.52", "@playwright/test": ">=1.40.0", "vite": ">=7.0.0" }, "peerDependenciesMeta": { + "@microsoft/fast-element": { + "optional": true + }, "@microsoft/fast-html": { "optional": true } diff --git a/packages/fast-test-harness/README.md b/packages/fast-test-harness/README.md index d3b0127bb05..60ed9db1bc8 100644 --- a/packages/fast-test-harness/README.md +++ b/packages/fast-test-harness/README.md @@ -4,6 +4,11 @@ The `fast-test-harness` package is a Playwright testing harness for FAST Element web components with CSR and SSR support. +## Requirements + +- Node.js 22.18 or later +- Playwright 1.56 or later + ## Installation To install `fast-test-harness` using `npm`: @@ -12,6 +17,21 @@ To install `fast-test-harness` using `npm`: npm install --save-dev @microsoft/fast-test-harness ``` +## Test directory setup + +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. + +``` +test/ +├── index.html # CSR: loads main.ts +├── ssr.html # SSR: template with comment placeholders +├── vite.config.ts # Vite config (shared by both modes) +└── src/ + ├── main.ts # CSR: registers components, applies theme + ├── entry-client.ts # SSR: registers components for hydration + └── entry-server.ts # SSR: exports render() for fixture generation +``` + ## Writing tests 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"); | `waitFor` | `string[]` | `[]` | Additional elements to wait for before testing | | `ssr` | `boolean` | `false` | Use SSR mode (or set `PLAYWRIGHT_TEST_SSR=true`) | -## Test directory setup - -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. - -``` -test/ -├── index.html # CSR: loads main.ts -├── ssr.html # SSR: template with comment placeholders -├── vite.config.ts # Vite config (shared by both modes) -└── src/ - ├── main.ts # CSR: registers components, applies theme - ├── entry-client.ts # SSR: registers components for hydration - └── entry-server.ts # SSR: exports render() for fixture generation -``` - ### CSR files **`index.html`** loads a script that registers your components: @@ -258,10 +263,11 @@ CLI flags take precedence over environment variables. | Specifier | Contents | |-----------|----------| -| `@microsoft/fast-test-harness` | `test`, `expect`, `CSRFixture`, `SSRFixture`, `createSSRRenderer`, build utilities | -| `@microsoft/fast-test-harness/server.mjs` | `startServer` | -| `@microsoft/fast-test-harness/ssr/render.js` | `createSSRRenderer`, `ComponentRegistration`, `RenderResult`, `SSRRendererOptions` | +| `@microsoft/fast-test-harness` | `test`, `expect`, `CSRFixture`, `SSRFixture`, `toHaveCustomState`, `installDomShim`, `createSSRRenderer` | | `@microsoft/fast-test-harness/build/*.js` | `installDomShim`, `generateStylesheets`, `generateFTemplates`, `generateWebuiTemplates` | +| `@microsoft/fast-test-harness/fixtures/*.js` | `CSRFixture`, `SSRFixture`, `toHaveCustomState`, extended `test` and `expect` | +| `@microsoft/fast-test-harness/ssr/render.js` | `createSSRRenderer`, `renderTemplate`, `buildEntryHtml`, `buildState`, `parseDefaultValue` | +| `@microsoft/fast-test-harness/server.mjs` | `startServer` | | `@microsoft/fast-test-harness/playwright.config.mjs` | Shared Playwright configuration | | `@microsoft/fast-test-harness/vite.config.mjs` | Shared Vite configuration | | `@microsoft/fast-test-harness/public/*` | Static assets (base CSS) | diff --git a/packages/fast-test-harness/package.json b/packages/fast-test-harness/package.json index fb53468f3b4..a02a0c1c77a 100644 --- a/packages/fast-test-harness/package.json +++ b/packages/fast-test-harness/package.json @@ -28,6 +28,10 @@ "types": "./dist/dts/build/*.d.ts", "default": "./dist/esm/build/*.js" }, + "./fixtures/*.js": { + "types": "./dist/dts/fixtures/*.d.ts", + "default": "./dist/esm/fixtures/*.js" + }, "./ssr/*.js": { "types": "./dist/dts/ssr/*.d.ts", "default": "./dist/esm/ssr/*.js" @@ -48,36 +52,42 @@ "./package.json": "./package.json" }, "scripts": { - "clean": "clean dist temp test-results", + "clean": "clean dist temp test-results test/temp", "build": "npm run build:tsc", "build:tsc": "tsgo -p tsconfig.build.json", - "test": "npm run test:node && npm run test:playwright", + "lint": "biome-changed", + "lint:fix": "biome-changed -- --fix", + "prepublishOnly": "npm run clean && npm run build", + "test": "npm run lint && npm run test:node && npm run test:playwright", "test:node": "node --test --experimental-test-isolation=none \"**/*.test.ts\"", - "test:playwright": "playwright test" + "test:playwright": "playwright test", + "test:chromium": "playwright test --project=chromium" }, "files": [ "dist", - "playwright.config.mjs", - "playwright.config.d.ts", "public", - "server.mjs", - "start.mjs", - "vite.config.mjs", - "vite.config.d.ts" + "*.mjs", + "*.d.ts" ], + "devDependencies": { + "@microsoft/fast-element": "^2.10.4", + "@microsoft/fast-build": "^0.6.0", + "@microsoft/fast-html": "^1.0.0-alpha.52" + }, "dependencies": { "cheerio": "1.2.0" }, - "devDependencies": { - "@microsoft/fast-html": "*" - }, "peerDependencies": { - "@microsoft/fast-build": ">=0.6.0 <1.0.0", - "@microsoft/fast-html": ">=1.0.0-alpha.52 <1.0.0", + "@microsoft/fast-element": "^2.10.4 || ^3.0.0", + "@microsoft/fast-build": "^0.6.0", + "@microsoft/fast-html": ">=1.0.0-alpha.52", "@playwright/test": ">=1.40.0", "vite": ">=7.0.0" }, "peerDependenciesMeta": { + "@microsoft/fast-element": { + "optional": true + }, "@microsoft/fast-html": { "optional": true } diff --git a/packages/fast-test-harness/src/fixtures/csr-fixture.ts b/packages/fast-test-harness/src/fixtures/csr-fixture.ts index 0ad0b62ae1d..4dea90b9057 100644 --- a/packages/fast-test-harness/src/fixtures/csr-fixture.ts +++ b/packages/fast-test-harness/src/fixtures/csr-fixture.ts @@ -2,8 +2,19 @@ import type { Locator, Page } from "@playwright/test"; export type ThemeTokens = Record; +/** + * The initial attributes for the fixture's template, where boolean attributes are + * represented as `true` and omitted when `false`. This allows for a more intuitive + * configuration of boolean attributes in the template options. + */ export type InitialTemplateAttributes = Record; +/** + * The attributes for the fixture's template, where boolean attributes can be represented as `true` + * or `false`. When `true`, the attribute will be included without a value (e.g., `disabled`), and + * when `false`, the attribute will be omitted entirely. This type is used for updating the + * template, allowing for both adding and removing boolean attributes. + */ export type TemplateAttributes = Record; /** @@ -14,6 +25,10 @@ export type InitialTemplateOptions = { innerHTML?: string; }; +/** + * The options for updating the fixture's template, where `attributes` can include boolean values to + * add or remove attributes from the element. + */ export type FixtureOptions = Omit & { attributes?: TemplateAttributes; }; @@ -45,6 +60,19 @@ export class CSRFixture { /** * Additional custom elements to wait for before running the test. + * + * @remarks + * This is useful for fixtures that depend on multiple custom elements being defined + * and stable before the test can run. By specifying additional tag names here, the + * fixture will wait for these elements to be defined before proceeding. Ensure that + * any elements specified here are included on the page and properly defined to + * prevent test timeouts. + * + * @example + * test.use({ + * tagName: "fast-dropdown", + * waitFor: ["fast-listbox", "fast-option"], + * }); */ protected readonly waitFor: string[]; diff --git a/packages/fast-test-harness/src/fixtures/ssr-fixture.ts b/packages/fast-test-harness/src/fixtures/ssr-fixture.ts index 251b269e915..5f3bf550b90 100644 --- a/packages/fast-test-harness/src/fixtures/ssr-fixture.ts +++ b/packages/fast-test-harness/src/fixtures/ssr-fixture.ts @@ -12,6 +12,16 @@ export class SSRFixture extends CSRFixture { */ private templateRendered = false; + /** + * Creates an instance of the SSRFixture. + * + * @param page - The Playwright page object. + * @param tagName - The tag name of the custom element. + * @param innerHTML - The inner HTML of the custom element. + * @param waitFor - Additional custom elements to wait for. + * @param testId - The test ID for the SSR fixture. + * @param testTitle - The test title for the SSR fixture. + */ constructor( page: Page, tagName: string, diff --git a/packages/fast-test-harness/src/ssr/render.test.ts b/packages/fast-test-harness/src/ssr/render.test.ts index 6e81755294a..4a42b1e68ff 100644 --- a/packages/fast-test-harness/src/ssr/render.test.ts +++ b/packages/fast-test-harness/src/ssr/render.test.ts @@ -276,7 +276,7 @@ test.describe("createSSRRenderer", () => { ); }); - test("should return empty preloadLinks without a theme", () => { + test("should not include theme link in preloadLinks when themeStylesheet is omitted", () => { const { render } = createSSRRenderer({ tagPrefix: "test", components: [{ name: "widget", packageName: "@microsoft/fast-test-harness" }], @@ -284,7 +284,10 @@ test.describe("createSSRRenderer", () => { const result = render({ tagName: "test-widget" }); - assert.strictEqual(result.preloadLinks, ""); + assert.ok( + !result.preloadLinks.includes('rel="stylesheet"'), + `should not have a stylesheet link, got: ${result.preloadLinks}`, + ); }); test("should handle raw HTML via the html key", () => { diff --git a/packages/fast-test-harness/src/ssr/render.ts b/packages/fast-test-harness/src/ssr/render.ts index fe7cfffa892..9939f994e1b 100644 --- a/packages/fast-test-harness/src/ssr/render.ts +++ b/packages/fast-test-harness/src/ssr/render.ts @@ -476,11 +476,17 @@ export function createSSRRenderer(options: SSRRendererOptions): { // Concatenate all f-templates (with styles) for client hydration. const allFTemplates = [...fTemplatesByName.values()].join("\n"); - // Resolve theme stylesheet if provided. - let preloadLinks = ""; + // Build preload links: theme stylesheet + component stylesheets. + const preloadParts: string[] = []; if (options.themeStylesheet) { - preloadLinks = ``; + preloadParts.push(``); } + for (const stylesUrl of styleUrlsByName.values()) { + if (stylesUrl) { + preloadParts.push(``); + } + } + const preloadLinks = preloadParts.join("\n"); return { render(queryObj: Record = {}): RenderResult { @@ -505,6 +511,35 @@ export function createSSRRenderer(options: SSRRendererOptions): { // Extract body content from the rendered document. const bodyMatch = rendered.match(/]*>([\s\S]*?)<\/body>/i); fixture = bodyMatch?.[1] ?? entryHtml; + + // The WASM renderer only injects DSD for top-level + // custom elements. Inject DSD for any nested custom + // elements that have templates but weren't rendered. + for (const nestedTag of Object.keys(templatesMap)) { + // Match opening tags that don't already have a + // DSD