Skip to content

Commit 2ba0e33

Browse files
authored
chore(wtr): make readme helpful @W-19098307 (#5547)
* chore(wtr): make readme helpful * test(wtr): oops can't mock modules * test(wtr): it doesn't log in production * docs(wtr): use JSDoc in code instead of forgettable README
1 parent 34d4b76 commit 2ba0e33

File tree

156 files changed

+296
-8
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

156 files changed

+296
-8
lines changed

package.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,8 @@
2323
"test:bespoke": "nx run-many --target=test",
2424
"test:debug": "vitest --inspect-brk --no-file-parallelism",
2525
"test:ci": "vitest run --coverage",
26-
"test:karma": "nx test @lwc/integration-karma",
27-
"test:karma:start": "nx start @lwc/integration-karma",
28-
"test:hydration": "nx hydration:test @lwc/integration-karma",
29-
"test:hydration:start": "nx hydration:start @lwc/integration-karma",
26+
"test:wtr": "nx test @lwc/integration-not-karma",
27+
"test:hydration": "nx test:hydration @lwc/integration-not-karma",
3028
"test:integration": "nx sauce @lwc/integration-tests",
3129
"test:performance": "nx test @lwc/perf-benchmarks",
3230
"test:performance:best": "nx test:best @lwc/perf-benchmarks",
Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,36 @@
1-
# @lwc/integration-not-karma
1+
# LWC [Web Test Runner](https://modern-web.dev/docs/test-runner/overview/) integration tests
22

3-
It's not karma, it's something else!
3+
## Quick Start
4+
5+
To run integration tests, run `yarn test:wtr` from the monorepo root, or `yarn test` from the package directory.
6+
7+
To run hydration tests, run `yarn test:hydration` from either the monorepo root or the package directory.
8+
9+
To manually debug tests in your browser, add the `--manual` flag to the test command.
10+
11+
To run individual test files, provide them as CLI arguments. If using relative paths, they must be relative to the _package directory_, e.g. `yarn test:wtr test/act/index.spec.js`.
12+
13+
Environment variables are used as controls to run tests in different modes (e.g native vs synthetic shadow, different API versions). The full list of controls is defined in [`helpers/options.js`](./helpers/options.js).
14+
15+
## Architecture
16+
17+
- `configs`: WTR configuration files. The main entrypoints are `integration.js` and `hydration.js`.
18+
- `helpers`: Helper functions used by tests and the test runner.
19+
- `mocks`: Module mocks to replace imports in tests.
20+
- `test`: The test directory for integration tests.
21+
- `test-hydration`: The test directory for hydration tests.
22+
23+
### Integration Tests
24+
25+
Integration tests are simply `.spec.js` files that run in the browser. LWC components are transformed by a plugin defined in `serve-integration.js`.
26+
27+
### Hydration Tests
28+
29+
Hydration tests test the SSR packages, and are therefore more complex than the integration tests. While the files are named `index.spec.js`, they are actually _config_ files. The actual test executed is defined in `test-hydration.js`, which also contains the interface definition for the config. Each hydration test is also expected to define an entrypoint component named `x/main`. The hydration tests are transformed by a plugin defined in `serve-hydration.js`.
30+
31+
## Design Goals
32+
33+
1. Web Test Runner is an ESM-first test runner, which means that as much code as possible should be served directly to the browser. LWC components must be transformed, so some bundling is unavoidable, but should be minimized.
34+
2. "Magic" should be avoided as much as possible -- global variables, code defined in strings, etc. When unavoidable, the source of the magic should be explained in comments.
35+
3. Simplify code wherever possible. These tests were originally written many years ago, for a different testing framework (Karma). There are many workarounds or sub-optimal patterns used to accommodate Karma or older browsers, because new developers were unfamiliar with established patterns, and so on. When updating tests, we should try to update the code to remove legacy logic.
36+
4. Over-use code comments. There are a lot of systems in play, and it's not always apparent why code was written in a particular way.

packages/@lwc/integration-not-karma/configs/integration.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export default {
1616
files: ['test/**/*.spec.js', '!test/custom-elements/index.spec.js'],
1717
plugins: [
1818
...baseConfig.plugins,
19+
// Only used for the `sanitizeAttribute` test
1920
importMapsPlugin({ inject: { importMap: { imports: { lwc: './mocks/lwc.js' } } } }),
2021
testPlugin,
2122
],

packages/@lwc/integration-not-karma/configs/plugins/test-hydration.js

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,75 @@ import { spyOn } from '@vitest/spy';
22
import * as LWC from 'lwc';
33
import { setHooks } from '../../helpers/hooks';
44

5+
/*
6+
* Because these tests are written in JS, the type defs below are not enforced. They are provided
7+
* solely as documentation. They can be used as IDE autocomplete if the test configs are annotated
8+
* with the JSDoc below (the number of ../ may need to be adjusted):
9+
/** @type {import('../../../configs/plugins/test-hydration.js').HydrationTestConfig} */
10+
11+
/**
12+
* @typedef {object} TestConfig
13+
* Exactly one of `test` or `advancedTest` must be defined. All other properties are optional.
14+
* `snapshot` is ignored if `advancedTest` is defined.
15+
* @property {Record<string, string>} [props]
16+
* Props to provide for the root test component.
17+
* @property {Record<string, string} [clientProps]
18+
* Client-side props to hydrate the root test component.
19+
* @property {string[]} [requiredFeatureFlags]
20+
* List of feature flags that should be enabled for the test.
21+
* @property {SnapshotFunc} [snapshot]
22+
* A function that can be used to capture the pre-hydration state of the page.
23+
* Only used if `test` is defined.
24+
* @property {TestFunc} [test]
25+
* A function that contains assertions, run after hydration.
26+
* Should be used if asserting the pre-hydration state is not required.
27+
* @property {AdvancedTestFunc} [advancedTest]
28+
* A function that contains assertions and is also responsible for hydrating the page.
29+
* Should only be used if assertions are required before hydration.
30+
*/
31+
32+
/**
33+
* @callback SnapshotFunc
34+
* Captures a snapshot of the page before hydration.
35+
* @param {HTMLElement} component
36+
* The root test component, corresponding to the `x-main` component.
37+
* @returns {unknown}
38+
* Any data required for test assertions.
39+
*/
40+
41+
/**
42+
* @callback TestFunc
43+
* Asserts the state of the page after hydration has occurred.
44+
* @param {HTMLElement} target
45+
* The root test element, corresponding to the `x-main` component.
46+
* @param {unknown} snapshot
47+
* The result of the `snapshot` function, if defined.
48+
* @param {Record<'log' | 'warn' | 'error', unknown[][]>} calls
49+
* Console calls that occurred during hydration.
50+
* @returns {void}
51+
*/
52+
53+
/**
54+
* @callback AdvancedTestFunc
55+
* Asserts the state of the page before and after hydration has occurred.
56+
* Is responsible for calling `hydrateComponent`.
57+
* @param {HTMLElement} target
58+
* The root test element, corresponding to the `x-main` component.
59+
* @param {object} utils
60+
* Various things helpful for making assertions.
61+
* @param {import('lwc').LightningElement} utils.Component
62+
* The constructor for the root test component (`x-main`).
63+
* @param {import('lwc').hydrateComponent} utils.hydrateComponent
64+
* A bound instance of `hydrateComponent`. Must be called for tests to pass.
65+
* @param {Record<'log' | 'warn' | 'error', unknown[][]> & {reset: () => void}} utils.consoleSpy
66+
* A spy on `console` to track calls. Calling `reset` empties the tracked calls.
67+
* @param {HTMLDivElement} utils.container
68+
* The parent of the test root element.
69+
* @param {'x-main'} utils.selector
70+
* The selector of the root test element.
71+
* @returns {void}
72+
*/
73+
574
setHooks({ sanitizeHtmlContent: (content) => content });
675

776
function parseStringToDom(html) {
@@ -86,13 +155,21 @@ export function runTest(configPath, componentPath, ssrRendered) {
86155
target = container.querySelector(selector);
87156
await testConfig.test(target, snapshot, consoleSpy.calls);
88157
} else if (testConfig.advancedTest) {
158+
let hydrated = false;
159+
// We can't spy on LWC because it's an ESM module, so we just wrap it
160+
const hydrateComponent = (...args) => {
161+
hydrated = true;
162+
return LWC.hydrateComponent(...args);
163+
};
89164
await testConfig.advancedTest(target, {
90165
Component,
91-
hydrateComponent: LWC.hydrateComponent.bind(LWC),
166+
hydrateComponent,
92167
consoleSpy,
93168
container,
94169
selector,
95170
});
171+
// Sanity check: if we've never hydrated then we haven't set up the test correctly
172+
expect(hydrated).toBe(true);
96173
} else {
97174
throw new Error(`Missing test or advancedTest function in ${configPath}.`);
98175
}

packages/@lwc/integration-not-karma/helpers/options.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,38 @@ import { HIGHEST_API_VERSION } from '@lwc/shared';
66

77
// --- Boolean test flags --- //
88

9+
/** Run SauceLabs tests using only "legacy" browsers. */
910
export const LEGACY_BROWSERS = Boolean(process.env.LEGACY_BROWSERS);
1011

12+
/** Force tests to run in native shadow mode with synthetic shadow polyfill patches. */
1113
export const FORCE_NATIVE_SHADOW_MODE_FOR_TEST = Boolean(
1214
process.env.FORCE_NATIVE_SHADOW_MODE_FOR_TEST
1315
);
1416

17+
/** Enable ARIA string reflection as a global polyfill. */
1518
export const ENABLE_ARIA_REFLECTION_GLOBAL_POLYFILL = Boolean(
1619
process.env.ENABLE_ARIA_REFLECTION_GLOBAL_POLYFILL
1720
);
1821

22+
/** Disable synthetic shadow support at the compiler level. */
1923
export const DISABLE_SYNTHETIC_SHADOW_SUPPORT_IN_COMPILER = Boolean(
2024
process.env.DISABLE_SYNTHETIC_SHADOW_SUPPORT_IN_COMPILER
2125
);
2226

27+
/** Set the compiler flag to disable static content optimization. */
2328
export const DISABLE_STATIC_CONTENT_OPTIMIZATION = Boolean(
2429
process.env.DISABLE_STATIC_CONTENT_OPTIMIZATION
2530
);
2631

32+
/** Set the runtime flag to use the synthetic custom element lifecycle instead of native. */
2733
export const DISABLE_NATIVE_CUSTOM_ELEMENT_LIFECYCLE = Boolean(
2834
process.env.DISABLE_NATIVE_CUSTOM_ELEMENT_LIFECYCLE
2935
);
3036

37+
/** Disable detached rehydation. I don't know what that means. */
3138
export const DISABLE_DETACHED_REHYDRATION = Boolean(process.env.DISABLE_DETACHED_REHYDRATION);
3239

40+
/** Run hydration tests using `@lwc/engine-server` instead of SSR v2. */
3341
export const ENGINE_SERVER = Boolean(process.env.ENGINE_SERVER);
3442

3543
// --- Test config --- //
@@ -42,10 +50,12 @@ export const ENGINE_SERVER = Boolean(process.env.ENGINE_SERVER);
4250
// NOTE: NATIVE_SHADOW is not defined here because integration/hydration have different defaults
4351
export const SHADOW_MODE_OVERRIDE = process.env.SHADOW_MODE_OVERRIDE;
4452

53+
/** The API version to use for compiling and rendering components. */
4554
export const API_VERSION = process.env.API_VERSION
4655
? parseInt(process.env.API_VERSION, 10)
4756
: HIGHEST_API_VERSION;
4857

58+
/** The `NODE_ENV` to set for tests (at runtime, in the browser). */
4959
export const NODE_ENV_FOR_TEST = process.env.NODE_ENV_FOR_TEST || 'development';
5060

5161
/** Unique directory name that encodes the flags that the tests were executed with. */
@@ -69,9 +79,15 @@ export const COVERAGE_DIR_FOR_OPTIONS =
6979

7080
// --- CI config --- //
7181

82+
/** Whether or not to report coverage. Currently unused. */
7283
export const COVERAGE = Boolean(process.env.COVERAGE);
84+
/** SauceLabs username. */
7385
export const SAUCE_USERNAME = process.env.SAUCE_USERNAME;
86+
/** SauceLabs access key. */
7487
export const SAUCE_ACCESS_KEY = process.env.SAUCE_ACCESS_KEY || process.env.SAUCE_KEY;
88+
/** SauceLabs tunnel ID. */
7589
export const SAUCE_TUNNEL_ID = process.env.SAUCE_TUNNEL_ID;
90+
/** Whether or not we're running in CI. */
7691
export const CI = Boolean(process.env.CI);
92+
/** The GitHub Actions run ID, set by GitHub. */
7793
export const GITHUB_RUN_ID = process.env.GITHUB_RUN_ID;

packages/@lwc/integration-not-karma/test-hydration/adjacent-text-and-comment-nodes/index.spec.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/** @type {import('../../configs/plugins/test-hydration.js').TestConfig} */
12
export default {
23
snapshot(target) {
34
const span = target.shadowRoot.querySelector('span');

packages/@lwc/integration-not-karma/test-hydration/adjacent-text-nodes/index.spec.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/** @type {import('../../configs/plugins/test-hydration.js').TestConfig} */
12
export default {
23
props: {},
34
snapshot(target) {

packages/@lwc/integration-not-karma/test-hydration/attributes/class/index.spec.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/** @type {import('../../../configs/plugins/test-hydration.js').TestConfig} */
12
export default {
23
snapshot(target) {
34
const div = target.shadowRoot.querySelector('div');

packages/@lwc/integration-not-karma/test-hydration/attributes/expression/index.spec.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/** @type {import('../../../configs/plugins/test-hydration.js').TestConfig} */
12
export default {
23
clientProps: {
34
foo: 'foo',

packages/@lwc/integration-not-karma/test-hydration/attributes/falsy-mismatch/index.spec.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { expectConsoleCallsDev } from '../../../helpers/utils.js';
22

3+
/** @type {import('../../../configs/plugins/test-hydration.js').TestConfig} */
34
export default {
45
props: {
56
isFalse: false,

0 commit comments

Comments
 (0)