@microsoft/fast-test-harness 0.1.0 → 0.3.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 +25 -19
- 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 +14 -1
- 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 +11 -16
- package/dist/dts/ssr/render.d.ts.map +1 -1
- package/dist/esm/build/generate-templates.js +62 -2
- package/dist/esm/build/generate-webui-templates.js +9 -26
- package/dist/esm/fixtures/ssr-fixture.js +19 -1
- package/dist/esm/ssr/render.js +48 -104
- 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/dist/esm/ssr/render.js
CHANGED
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
* flat exports.
|
|
22
22
|
* ```ts
|
|
23
23
|
* const { render } = createSSRRenderer({
|
|
24
|
-
* tagPrefix: "
|
|
24
|
+
* tagPrefix: "contoso",
|
|
25
25
|
* components: [
|
|
26
|
-
* { name: "button", packageName: "@
|
|
27
|
-
* { name: "checkbox", packageName: "@
|
|
26
|
+
* { name: "button", packageName: "@contoso/button" },
|
|
27
|
+
* { name: "checkbox", packageName: "@contoso/checkbox" },
|
|
28
28
|
* ],
|
|
29
29
|
* });
|
|
30
30
|
* ```
|
|
@@ -71,75 +71,6 @@ function resolvePackageRoot(packageName) {
|
|
|
71
71
|
function toServerUrl(absolutePath, packageRoot) {
|
|
72
72
|
return `/${relative(packageRoot, absolutePath).replace(/\\/g, "/")}`;
|
|
73
73
|
}
|
|
74
|
-
/**
|
|
75
|
-
* Parse a JavaScript default value string from CEM into a JSON-safe value.
|
|
76
|
-
* @internal
|
|
77
|
-
*/
|
|
78
|
-
export function parseDefaultValue(raw) {
|
|
79
|
-
const trimmed = raw.trim();
|
|
80
|
-
if (trimmed === "" || trimmed === "undefined" || trimmed === "null") {
|
|
81
|
-
return "";
|
|
82
|
-
}
|
|
83
|
-
if (trimmed === "true") {
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
if (trimmed === "false") {
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
if (trimmed.startsWith("'") || trimmed.startsWith('"')) {
|
|
90
|
-
return trimmed.slice(1, -1);
|
|
91
|
-
}
|
|
92
|
-
const num = Number(trimmed);
|
|
93
|
-
if (!Number.isNaN(num)) {
|
|
94
|
-
return num;
|
|
95
|
-
}
|
|
96
|
-
try {
|
|
97
|
-
return JSON.parse(trimmed);
|
|
98
|
-
}
|
|
99
|
-
catch {
|
|
100
|
-
return "";
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Load a Custom Elements Manifest and extract default state for
|
|
105
|
-
* each custom element declaration. Returns a map of tag names to
|
|
106
|
-
* their default property values.
|
|
107
|
-
*/
|
|
108
|
-
function loadDefaultStateFromCEM(packageName, tagPrefix) {
|
|
109
|
-
const cemPath = resolveSpecifier(`${packageName}/custom-elements.json`);
|
|
110
|
-
if (!cemPath) {
|
|
111
|
-
return new Map();
|
|
112
|
-
}
|
|
113
|
-
try {
|
|
114
|
-
const cem = JSON.parse(readFileSync(cemPath, "utf8"));
|
|
115
|
-
const result = new Map();
|
|
116
|
-
for (const mod of cem.modules ?? []) {
|
|
117
|
-
for (const decl of mod.declarations ?? []) {
|
|
118
|
-
if (!decl.customElement) {
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
const tagName = decl.tagName ?? `${tagPrefix}-${decl.name?.toLowerCase()}`;
|
|
122
|
-
const state = {};
|
|
123
|
-
for (const member of decl.members ?? []) {
|
|
124
|
-
if (member.kind !== "field" || member.privacy !== "public") {
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
state[member.name] =
|
|
128
|
-
member.default != null
|
|
129
|
-
? parseDefaultValue(String(member.default))
|
|
130
|
-
: "";
|
|
131
|
-
}
|
|
132
|
-
if (Object.keys(state).length > 0) {
|
|
133
|
-
result.set(tagName, state);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
return result;
|
|
138
|
-
}
|
|
139
|
-
catch {
|
|
140
|
-
return new Map();
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
74
|
/**
|
|
144
75
|
* Load artifacts for a component from a monolithic package
|
|
145
76
|
* (e.g., `@fluentui/web-components/button/template.html`).
|
|
@@ -155,7 +86,7 @@ function loadMonolithicComponent(packageName, componentDir, packageRoot) {
|
|
|
155
86
|
}
|
|
156
87
|
/**
|
|
157
88
|
* Load artifacts for a component from a per-component package
|
|
158
|
-
* (e.g., `@
|
|
89
|
+
* (e.g., `@contoso/button/template.html`).
|
|
159
90
|
*/
|
|
160
91
|
function loadPerPackageComponent(reg) {
|
|
161
92
|
const fTemplatePath = resolveSpecifier(`${reg.packageName}/template.html`);
|
|
@@ -263,9 +194,9 @@ export function buildState(queryObj) {
|
|
|
263
194
|
*
|
|
264
195
|
* Supports two modes:
|
|
265
196
|
* - **`packageName`**: Scans a monolithic package's dist directory for
|
|
266
|
-
* components in subdirectories (
|
|
197
|
+
* components in subdirectories (monolithic layout).
|
|
267
198
|
* - **`components`**: Uses an explicit list of per-component packages
|
|
268
|
-
* with flat exports
|
|
199
|
+
* with flat exports.
|
|
269
200
|
*/
|
|
270
201
|
export function createSSRRenderer(options) {
|
|
271
202
|
const { tagPrefix } = options;
|
|
@@ -284,13 +215,13 @@ export function createSSRRenderer(options) {
|
|
|
284
215
|
// Collect component artifacts from either mode.
|
|
285
216
|
const artifacts = [];
|
|
286
217
|
if (options.components) {
|
|
287
|
-
// Per-component packages
|
|
218
|
+
// Per-component packages
|
|
288
219
|
for (const reg of options.components) {
|
|
289
220
|
artifacts.push(loadPerPackageComponent(reg));
|
|
290
221
|
}
|
|
291
222
|
}
|
|
292
223
|
else if (options.packageName) {
|
|
293
|
-
// Monolithic package
|
|
224
|
+
// Monolithic package
|
|
294
225
|
const packageRoot = resolvePackageRoot(options.packageName);
|
|
295
226
|
const distDir = join(packageRoot, options.distDir ?? "dist/esm");
|
|
296
227
|
const pattern = "**/*.template.html";
|
|
@@ -306,30 +237,13 @@ export function createSSRRenderer(options) {
|
|
|
306
237
|
artifacts.push(loadMonolithicComponent(options.packageName, componentDir, packageRoot));
|
|
307
238
|
}
|
|
308
239
|
}
|
|
309
|
-
// Populate
|
|
310
|
-
const defaultStateByTag = new Map();
|
|
240
|
+
// Populate template and style maps from collected artifacts.
|
|
311
241
|
for (const art of artifacts) {
|
|
312
|
-
const tagName = `${tagPrefix}-${art.componentName}`;
|
|
313
242
|
if (art.fTemplate) {
|
|
314
243
|
fTemplatesByName.set(art.componentName, art.fTemplate);
|
|
315
244
|
}
|
|
316
245
|
styleUrlsByName.set(art.componentName, art.stylesUrl);
|
|
317
246
|
}
|
|
318
|
-
// Load CEM defaults per-package (each package may contain multiple elements).
|
|
319
|
-
if (options.components) {
|
|
320
|
-
for (const reg of options.components) {
|
|
321
|
-
const cemDefaults = loadDefaultStateFromCEM(reg.packageName, tagPrefix);
|
|
322
|
-
for (const [tag, state] of cemDefaults) {
|
|
323
|
-
defaultStateByTag.set(tag, state);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
else if (options.packageName) {
|
|
328
|
-
const cemDefaults = loadDefaultStateFromCEM(options.packageName, tagPrefix);
|
|
329
|
-
for (const [tag, state] of cemDefaults) {
|
|
330
|
-
defaultStateByTag.set(tag, state);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
247
|
// Inject styles into f-templates, then parse into the WASM
|
|
334
248
|
// templates map (tag-name → inner template content).
|
|
335
249
|
const templatesMap = {};
|
|
@@ -339,7 +253,16 @@ export function createSSRRenderer(options) {
|
|
|
339
253
|
const parsed = JSON.parse(wasm.parse_f_templates(styled));
|
|
340
254
|
const entry = parsed.find((t) => t.name !== null);
|
|
341
255
|
if (entry) {
|
|
342
|
-
|
|
256
|
+
const tagName = `${tagPrefix}-${name}`;
|
|
257
|
+
const attrs = entry.shadowrootAttributes ?? [];
|
|
258
|
+
// Use the object format when there are extra shadowroot attributes
|
|
259
|
+
// beyond the default shadowrootmode, so the WASM renderer can
|
|
260
|
+
// propagate them (e.g. shadowrootdelegatesfocus) onto the DSD
|
|
261
|
+
// <template> element.
|
|
262
|
+
const hasExtraAttrs = attrs.some(a => a.name !== "shadowrootmode" && a.name !== "shadowroot");
|
|
263
|
+
templatesMap[tagName] = hasExtraAttrs
|
|
264
|
+
? { content: entry.content, shadowrootAttributes: attrs }
|
|
265
|
+
: entry.content;
|
|
343
266
|
}
|
|
344
267
|
else {
|
|
345
268
|
console.warn(`No named template found for ${tagPrefix}-${name}`);
|
|
@@ -348,19 +271,21 @@ export function createSSRRenderer(options) {
|
|
|
348
271
|
const templatesJson = JSON.stringify(templatesMap);
|
|
349
272
|
// Concatenate all f-templates (with styles) for client hydration.
|
|
350
273
|
const allFTemplates = [...fTemplatesByName.values()].join("\n");
|
|
351
|
-
//
|
|
352
|
-
|
|
274
|
+
// Build preload links: theme stylesheet + component stylesheets.
|
|
275
|
+
const preloadParts = [];
|
|
353
276
|
if (options.themeStylesheet) {
|
|
354
|
-
|
|
277
|
+
preloadParts.push(`<link rel="stylesheet" href="${options.themeStylesheet}">`);
|
|
355
278
|
}
|
|
279
|
+
for (const stylesUrl of styleUrlsByName.values()) {
|
|
280
|
+
if (stylesUrl) {
|
|
281
|
+
preloadParts.push(`<link rel="preload" href="${stylesUrl}" as="style">`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const preloadLinks = preloadParts.join("\n");
|
|
356
285
|
return {
|
|
357
286
|
render(queryObj = {}) {
|
|
358
287
|
const entryHtml = buildEntryHtml(queryObj);
|
|
359
|
-
const
|
|
360
|
-
// Merge CEM default state (base) with request state (overrides).
|
|
361
|
-
const tagName = String(queryObj.tagName ?? "");
|
|
362
|
-
const defaults = defaultStateByTag.get(tagName) ?? {};
|
|
363
|
-
const state = { ...defaults, ...requestState };
|
|
288
|
+
const state = buildState(queryObj);
|
|
364
289
|
let fixture = "";
|
|
365
290
|
if (entryHtml) {
|
|
366
291
|
try {
|
|
@@ -368,6 +293,25 @@ export function createSSRRenderer(options) {
|
|
|
368
293
|
// Extract body content from the rendered document.
|
|
369
294
|
const bodyMatch = rendered.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
|
|
370
295
|
fixture = bodyMatch?.[1] ?? entryHtml;
|
|
296
|
+
// The WASM renderer only injects DSD for top-level
|
|
297
|
+
// custom elements. Inject DSD for any nested custom
|
|
298
|
+
// elements that have templates but weren't rendered.
|
|
299
|
+
for (const nestedTag of Object.keys(templatesMap)) {
|
|
300
|
+
// Match opening tags that don't already have a
|
|
301
|
+
// DSD <template> as their first child.
|
|
302
|
+
const openTagRe = new RegExp(`(<${nestedTag}(?=[\\s>/])[^>]*>)(?!\\s*<template[\\s>])`, "g");
|
|
303
|
+
fixture = fixture.replace(openTagRe, (_, open) => {
|
|
304
|
+
// Render this element in isolation.
|
|
305
|
+
const solo = wasm.render_with_templates(`${open}</${nestedTag}>`, templatesJson, "{}", "camelCase");
|
|
306
|
+
// Extract the DSD that was injected.
|
|
307
|
+
const dsdStart = solo.indexOf("<template shadowrootmode");
|
|
308
|
+
const dsdEnd = solo.lastIndexOf("</template>");
|
|
309
|
+
if (dsdStart !== -1 && dsdEnd > dsdStart) {
|
|
310
|
+
return `${open}${solo.slice(dsdStart, dsdEnd + "</template>".length)}`;
|
|
311
|
+
}
|
|
312
|
+
return open;
|
|
313
|
+
});
|
|
314
|
+
}
|
|
371
315
|
}
|
|
372
316
|
catch (e) {
|
|
373
317
|
// 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.3.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":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ssr-fixture.pw.spec.d.ts","sourceRoot":"","sources":["../../../src/fixtures/ssr-fixture.pw.spec.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"render.test.d.ts","sourceRoot":"","sources":["../../../src/ssr/render.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { test } from "node:test";
|
|
3
|
-
// Each test needs a clean globalThis, so we tear down the shim between tests.
|
|
4
|
-
function teardownShim() {
|
|
5
|
-
for (const key of [
|
|
6
|
-
"Node",
|
|
7
|
-
"Element",
|
|
8
|
-
"HTMLElement",
|
|
9
|
-
"Document",
|
|
10
|
-
"CustomEvent",
|
|
11
|
-
"CSSStyleSheet",
|
|
12
|
-
"ShadowRoot",
|
|
13
|
-
"CustomElementRegistry",
|
|
14
|
-
"MutationObserver",
|
|
15
|
-
"MediaQueryList",
|
|
16
|
-
"matchMedia",
|
|
17
|
-
"document",
|
|
18
|
-
"customElements",
|
|
19
|
-
"window",
|
|
20
|
-
"CSS",
|
|
21
|
-
]) {
|
|
22
|
-
delete globalThis[key];
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
test.describe("installDomShim", () => {
|
|
26
|
-
test.beforeEach(() => {
|
|
27
|
-
teardownShim();
|
|
28
|
-
});
|
|
29
|
-
async function loadShim() {
|
|
30
|
-
// Dynamic import so each test gets a fresh evaluation context
|
|
31
|
-
// after globalThis is cleaned.
|
|
32
|
-
const { installDomShim } = await import("@microsoft/fast-test-harness/build/dom-shim.js");
|
|
33
|
-
installDomShim();
|
|
34
|
-
}
|
|
35
|
-
test("should assign globals on first call", async () => {
|
|
36
|
-
await loadShim();
|
|
37
|
-
assert.ok(globalThis.window !== undefined);
|
|
38
|
-
assert.ok(globalThis.document !== undefined);
|
|
39
|
-
assert.ok(globalThis.customElements !== undefined);
|
|
40
|
-
assert.ok(globalThis.Node !== undefined);
|
|
41
|
-
assert.ok(globalThis.Element !== undefined);
|
|
42
|
-
assert.ok(globalThis.HTMLElement !== undefined);
|
|
43
|
-
assert.ok(globalThis.CSSStyleSheet !== undefined);
|
|
44
|
-
assert.ok(globalThis.MutationObserver !== undefined);
|
|
45
|
-
assert.ok(globalThis.matchMedia !== undefined);
|
|
46
|
-
});
|
|
47
|
-
test("should be idempotent when window is already defined", async () => {
|
|
48
|
-
const sentinel = { __sentinel: true };
|
|
49
|
-
globalThis.window = sentinel;
|
|
50
|
-
await loadShim();
|
|
51
|
-
assert.strictEqual(globalThis.window, sentinel);
|
|
52
|
-
assert.strictEqual(globalThis.document, undefined);
|
|
53
|
-
});
|
|
54
|
-
test("should set window to globalThis", async () => {
|
|
55
|
-
await loadShim();
|
|
56
|
-
assert.strictEqual(globalThis.window, globalThis);
|
|
57
|
-
});
|
|
58
|
-
test("should provide CSS.supports that returns true", async () => {
|
|
59
|
-
await loadShim();
|
|
60
|
-
assert.strictEqual(globalThis.CSS.supports("display", "flex"), true);
|
|
61
|
-
});
|
|
62
|
-
test("should not overwrite an existing CSS global", async () => {
|
|
63
|
-
const existing = { supports: () => false };
|
|
64
|
-
globalThis.CSS = existing;
|
|
65
|
-
await loadShim();
|
|
66
|
-
assert.strictEqual(globalThis.CSS, existing);
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
test.describe("ShimHTMLElement", () => {
|
|
70
|
-
test.beforeEach(async () => {
|
|
71
|
-
teardownShim();
|
|
72
|
-
const { installDomShim } = await import("@microsoft/fast-test-harness/build/dom-shim.js");
|
|
73
|
-
installDomShim();
|
|
74
|
-
});
|
|
75
|
-
test("should support setAttribute / getAttribute / hasAttribute", () => {
|
|
76
|
-
const el = new globalThis.HTMLElement();
|
|
77
|
-
assert.strictEqual(el.hasAttribute("id"), false);
|
|
78
|
-
assert.strictEqual(el.getAttribute("id"), null);
|
|
79
|
-
el.setAttribute("id", "test");
|
|
80
|
-
assert.strictEqual(el.hasAttribute("id"), true);
|
|
81
|
-
assert.strictEqual(el.getAttribute("id"), "test");
|
|
82
|
-
});
|
|
83
|
-
test("should support removeAttribute", () => {
|
|
84
|
-
const el = new globalThis.HTMLElement();
|
|
85
|
-
el.setAttribute("class", "foo");
|
|
86
|
-
assert.strictEqual(el.hasAttribute("class"), true);
|
|
87
|
-
el.removeAttribute("class");
|
|
88
|
-
assert.strictEqual(el.hasAttribute("class"), false);
|
|
89
|
-
assert.strictEqual(el.getAttribute("class"), null);
|
|
90
|
-
});
|
|
91
|
-
test("should return attributes as an array of {name, value}", () => {
|
|
92
|
-
const el = new globalThis.HTMLElement();
|
|
93
|
-
el.setAttribute("role", "button");
|
|
94
|
-
el.setAttribute("aria-label", "Close");
|
|
95
|
-
const attrs = el.attributes;
|
|
96
|
-
assert.strictEqual(attrs.length, 2);
|
|
97
|
-
assert.deepStrictEqual(attrs.map((a) => a.name).sort(), [
|
|
98
|
-
"aria-label",
|
|
99
|
-
"role",
|
|
100
|
-
]);
|
|
101
|
-
});
|
|
102
|
-
test("should support attachShadow with open mode", () => {
|
|
103
|
-
const el = new globalThis.HTMLElement();
|
|
104
|
-
const sr = el.attachShadow({ mode: "open" });
|
|
105
|
-
assert.ok(sr);
|
|
106
|
-
assert.strictEqual(sr.host, el);
|
|
107
|
-
assert.strictEqual(el.shadowRoot, sr);
|
|
108
|
-
});
|
|
109
|
-
test("should not expose shadowRoot for closed mode", () => {
|
|
110
|
-
const el = new globalThis.HTMLElement();
|
|
111
|
-
el.attachShadow({ mode: "closed" });
|
|
112
|
-
assert.strictEqual(el.shadowRoot, null);
|
|
113
|
-
});
|
|
114
|
-
test("should provide a classList stub", () => {
|
|
115
|
-
const el = new globalThis.HTMLElement();
|
|
116
|
-
const cl = el.classList;
|
|
117
|
-
assert.strictEqual(cl.contains("foo"), false);
|
|
118
|
-
// Should not throw
|
|
119
|
-
cl.add("foo");
|
|
120
|
-
cl.remove("foo");
|
|
121
|
-
cl.toggle("foo");
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
test.describe("ShimCSSStyleSheet", () => {
|
|
125
|
-
test.beforeEach(async () => {
|
|
126
|
-
teardownShim();
|
|
127
|
-
const { installDomShim } = await import("@microsoft/fast-test-harness/build/dom-shim.js");
|
|
128
|
-
installDomShim();
|
|
129
|
-
});
|
|
130
|
-
test("should support insertRule", () => {
|
|
131
|
-
const sheet = new globalThis.CSSStyleSheet();
|
|
132
|
-
const idx = sheet.insertRule(".foo { color: red }", 0);
|
|
133
|
-
assert.strictEqual(idx, 0);
|
|
134
|
-
assert.strictEqual(sheet.cssRules.length, 1);
|
|
135
|
-
assert.strictEqual(sheet.cssRules[0].selectorText, ".foo ");
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
test.describe("ShimCustomElementRegistry", () => {
|
|
139
|
-
test.beforeEach(async () => {
|
|
140
|
-
teardownShim();
|
|
141
|
-
const { installDomShim } = await import("@microsoft/fast-test-harness/build/dom-shim.js");
|
|
142
|
-
installDomShim();
|
|
143
|
-
});
|
|
144
|
-
test("should support define and get", () => {
|
|
145
|
-
class MyEl {
|
|
146
|
-
}
|
|
147
|
-
globalThis.customElements.define("my-el", MyEl);
|
|
148
|
-
assert.strictEqual(globalThis.customElements.get("my-el"), MyEl);
|
|
149
|
-
});
|
|
150
|
-
test("should return undefined for unknown elements", () => {
|
|
151
|
-
assert.strictEqual(globalThis.customElements.get("unknown-el"), undefined);
|
|
152
|
-
});
|
|
153
|
-
test("should resolve whenDefined immediately", async () => {
|
|
154
|
-
const result = await globalThis.customElements.whenDefined("any-el");
|
|
155
|
-
assert.strictEqual(result, undefined);
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
test.describe("ShimDocument", () => {
|
|
159
|
-
test.beforeEach(async () => {
|
|
160
|
-
teardownShim();
|
|
161
|
-
const { installDomShim } = await import("@microsoft/fast-test-harness/build/dom-shim.js");
|
|
162
|
-
installDomShim();
|
|
163
|
-
});
|
|
164
|
-
test("should create elements via createElement", () => {
|
|
165
|
-
const el = globalThis.document.createElement("div");
|
|
166
|
-
assert.ok(el);
|
|
167
|
-
assert.strictEqual(typeof el.setAttribute, "function");
|
|
168
|
-
});
|
|
169
|
-
test("should support adoptedStyleSheets", () => {
|
|
170
|
-
assert.ok(Array.isArray(globalThis.document.adoptedStyleSheets));
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
test.describe("ShimCustomEvent", () => {
|
|
174
|
-
test.beforeEach(async () => {
|
|
175
|
-
teardownShim();
|
|
176
|
-
const { installDomShim } = await import("@microsoft/fast-test-harness/build/dom-shim.js");
|
|
177
|
-
installDomShim();
|
|
178
|
-
});
|
|
179
|
-
test("should carry detail", () => {
|
|
180
|
-
const evt = new globalThis.CustomEvent("test", { detail: 42 });
|
|
181
|
-
assert.strictEqual(evt.detail, 42);
|
|
182
|
-
assert.strictEqual(evt.type, "test");
|
|
183
|
-
});
|
|
184
|
-
test("should default detail to null", () => {
|
|
185
|
-
const evt = new globalThis.CustomEvent("test");
|
|
186
|
-
assert.strictEqual(evt.detail, null);
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
test.describe("matchMedia", () => {
|
|
190
|
-
test.beforeEach(async () => {
|
|
191
|
-
teardownShim();
|
|
192
|
-
const { installDomShim } = await import("@microsoft/fast-test-harness/build/dom-shim.js");
|
|
193
|
-
installDomShim();
|
|
194
|
-
});
|
|
195
|
-
test("should return a MediaQueryList with matches = false", () => {
|
|
196
|
-
const mql = globalThis.matchMedia("(prefers-color-scheme: dark)");
|
|
197
|
-
assert.strictEqual(mql.matches, false);
|
|
198
|
-
// Should not throw
|
|
199
|
-
mql.addEventListener("change", () => { });
|
|
200
|
-
mql.removeEventListener("change", () => { });
|
|
201
|
-
});
|
|
202
|
-
});
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { test } from "node:test";
|
|
6
|
-
import { generateStylesheets } from "@microsoft/fast-test-harness/build/generate-stylesheets.js";
|
|
7
|
-
test.describe("generateStylesheets", () => {
|
|
8
|
-
let tempDir;
|
|
9
|
-
test.beforeEach(async () => {
|
|
10
|
-
tempDir = await mkdtemp(join(tmpdir(), "fast-styles-"));
|
|
11
|
-
});
|
|
12
|
-
test.afterEach(async () => {
|
|
13
|
-
await rm(tempDir, { recursive: true, force: true });
|
|
14
|
-
});
|
|
15
|
-
test("should extract CSS from a styles module", async () => {
|
|
16
|
-
const distDir = join(tempDir, "dist");
|
|
17
|
-
await mkdir(distDir, { recursive: true });
|
|
18
|
-
// Write a fake styles module that exports an ElementStyles-like object.
|
|
19
|
-
await writeFile(join(distDir, "button.styles.js"), `export const styles = { styles: [":host { display: block; }", "span { color: red; }"] };`);
|
|
20
|
-
await generateStylesheets({ cwd: tempDir });
|
|
21
|
-
const css = await readFile(join(distDir, "button.styles.css"), "utf8");
|
|
22
|
-
assert.ok(css.includes(":host { display: block; }"));
|
|
23
|
-
assert.ok(css.includes("span { color: red; }"));
|
|
24
|
-
});
|
|
25
|
-
test("should write to outDir when specified", async () => {
|
|
26
|
-
const distDir = join(tempDir, "dist");
|
|
27
|
-
const outDir = join(tempDir, "out");
|
|
28
|
-
await mkdir(distDir, { recursive: true });
|
|
29
|
-
await writeFile(join(distDir, "card.styles.js"), `export const styles = { styles: [".card { padding: 8px; }"] };`);
|
|
30
|
-
await generateStylesheets({ cwd: tempDir, outDir: "out" });
|
|
31
|
-
const css = await readFile(join(outDir, "card.styles.css"), "utf8");
|
|
32
|
-
assert.ok(css.includes(".card { padding: 8px; }"));
|
|
33
|
-
});
|
|
34
|
-
test("should apply a format function", async () => {
|
|
35
|
-
const distDir = join(tempDir, "dist");
|
|
36
|
-
await mkdir(distDir, { recursive: true });
|
|
37
|
-
await writeFile(join(distDir, "link.styles.js"), `export const styles = { styles: ["a { color: blue; }"] };`);
|
|
38
|
-
await generateStylesheets({
|
|
39
|
-
cwd: tempDir,
|
|
40
|
-
format: css => `/* formatted */\n${css}`,
|
|
41
|
-
});
|
|
42
|
-
const css = await readFile(join(distDir, "link.styles.css"), "utf8");
|
|
43
|
-
assert.ok(css.startsWith("/* formatted */"));
|
|
44
|
-
});
|
|
45
|
-
test("should flatten nested styles arrays", async () => {
|
|
46
|
-
const distDir = join(tempDir, "dist");
|
|
47
|
-
await mkdir(distDir, { recursive: true });
|
|
48
|
-
await writeFile(join(distDir, "nested.styles.js"), `export const styles = {
|
|
49
|
-
styles: [
|
|
50
|
-
{ styles: [":host { display: flex; }", "div { margin: 0; }"] },
|
|
51
|
-
"span { font-size: 14px; }"
|
|
52
|
-
]
|
|
53
|
-
};`);
|
|
54
|
-
await generateStylesheets({ cwd: tempDir });
|
|
55
|
-
const css = await readFile(join(distDir, "nested.styles.css"), "utf8");
|
|
56
|
-
assert.ok(css.includes(":host { display: flex; }"));
|
|
57
|
-
assert.ok(css.includes("div { margin: 0; }"));
|
|
58
|
-
assert.ok(css.includes("span { font-size: 14px; }"));
|
|
59
|
-
});
|
|
60
|
-
test("should skip modules without a styles export", async () => {
|
|
61
|
-
const distDir = join(tempDir, "dist");
|
|
62
|
-
await mkdir(distDir, { recursive: true });
|
|
63
|
-
await writeFile(join(distDir, "empty.styles.js"), `export const template = "<div></div>";`);
|
|
64
|
-
await generateStylesheets({ cwd: tempDir });
|
|
65
|
-
// Should not create a CSS file
|
|
66
|
-
try {
|
|
67
|
-
await readFile(join(distDir, "empty.styles.css"), "utf8");
|
|
68
|
-
assert.fail("Should not have created a CSS file");
|
|
69
|
-
}
|
|
70
|
-
catch (err) {
|
|
71
|
-
assert.strictEqual(err.code, "ENOENT");
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
});
|