diff --git a/packages/@lwc/engine-core/src/framework/check-version-mismatch.ts b/packages/@lwc/engine-core/src/framework/check-version-mismatch.ts index edeac5ddcc..8012ae37cc 100644 --- a/packages/@lwc/engine-core/src/framework/check-version-mismatch.ts +++ b/packages/@lwc/engine-core/src/framework/check-version-mismatch.ts @@ -16,7 +16,7 @@ import type { Stylesheet } from '@lwc/shared'; let warned = false; // Only used in LWC's Karma tests -if (process.env.NODE_ENV === 'test-karma-lwc') { +if (process.env.NODE_ENV === 'test-lwc-integration') { (window as any).__lwcResetWarnedOnVersionMismatch = () => { warned = false; }; diff --git a/packages/@lwc/engine-core/src/framework/fragment-cache.ts b/packages/@lwc/engine-core/src/framework/fragment-cache.ts index 7cdb79d8f6..8302cd5042 100644 --- a/packages/@lwc/engine-core/src/framework/fragment-cache.ts +++ b/packages/@lwc/engine-core/src/framework/fragment-cache.ts @@ -25,7 +25,7 @@ const fragmentCache: WeakMap[] = ArrayFrom( ); // Only used in LWC's Karma tests -if (process.env.NODE_ENV === 'test-karma-lwc') { +if (process.env.NODE_ENV === 'test-lwc-integration') { (window as any).__lwcResetFragmentCache = () => { for (let i = 0; i < fragmentCache.length; i++) { fragmentCache[i] = new WeakMap(); diff --git a/packages/@lwc/engine-core/src/framework/hot-swaps.ts b/packages/@lwc/engine-core/src/framework/hot-swaps.ts index 2bd5ea00e5..af8f98b8be 100644 --- a/packages/@lwc/engine-core/src/framework/hot-swaps.ts +++ b/packages/@lwc/engine-core/src/framework/hot-swaps.ts @@ -34,7 +34,7 @@ let activeComponents: WeakMultiMap = let activeStyles: WeakMultiMap = /*@__PURE__@*/ new WeakMultiMap(); // Only used in LWC's Karma tests -if (process.env.NODE_ENV === 'test-karma-lwc') { +if (process.env.NODE_ENV === 'test-lwc-integration') { // Used to reset the global state between test runs (window as any).__lwcResetHotSwaps = () => { swappedTemplateMap = new WeakMap(); diff --git a/packages/@lwc/engine-core/src/framework/mutation-logger.ts b/packages/@lwc/engine-core/src/framework/mutation-logger.ts index 15de4602b7..3ef240a016 100644 --- a/packages/@lwc/engine-core/src/framework/mutation-logger.ts +++ b/packages/@lwc/engine-core/src/framework/mutation-logger.ts @@ -99,7 +99,7 @@ export function logMutation(reactiveObserver: ReactiveObserver, target: object, // because the unit tests just create Reactive Observers on-the-fly. // Note we could explicitly target Vitest with `process.env.NODE_ENV === 'test'`, but then that would also // affect our downstream consumers' Jest/Vitest tests, and we don't want to throw an error just for a logger. - if (process.env.NODE_ENV === 'test-karma-lwc') { + if (process.env.NODE_ENV === 'test-lwc-integration') { throw new Error('The VM should always be defined except possibly in unit tests'); } } else { diff --git a/packages/@lwc/engine-core/src/framework/stylesheet.ts b/packages/@lwc/engine-core/src/framework/stylesheet.ts index 042d6108bc..21dabf29cb 100644 --- a/packages/@lwc/engine-core/src/framework/stylesheet.ts +++ b/packages/@lwc/engine-core/src/framework/stylesheet.ts @@ -37,7 +37,7 @@ let stylesheetsToCssContent: WeakMap> = /*@__PURE__@*/ n let cssContentToAbortControllers: Map = /*@__PURE__@*/ new Map(); // Only used in LWC's Karma tests -if (process.env.NODE_ENV === 'test-karma-lwc') { +if (process.env.NODE_ENV === 'test-lwc-integration') { // Used to reset the global state between test runs (window as any).__lwcResetStylesheetCache = () => { stylesheetsToCssContent = new WeakMap(); diff --git a/packages/@lwc/engine-core/src/framework/vm.ts b/packages/@lwc/engine-core/src/framework/vm.ts index e5c977d9b6..1889dc28f4 100644 --- a/packages/@lwc/engine-core/src/framework/vm.ts +++ b/packages/@lwc/engine-core/src/framework/vm.ts @@ -516,7 +516,7 @@ function computeShadowMode( if ( // Force the shadow mode to always be native. Used for running tests with synthetic shadow patches // on, but components running in actual native shadow mode - (process.env.NODE_ENV === 'test-karma-lwc' && + (process.env.NODE_ENV === 'test-lwc-integration' && process.env.FORCE_NATIVE_SHADOW_MODE_FOR_TEST) || // If synthetic shadow is explicitly disabled, use pure-native lwcRuntimeFlags.DISABLE_SYNTHETIC_SHADOW || diff --git a/packages/@lwc/engine-core/src/shared/logger.ts b/packages/@lwc/engine-core/src/shared/logger.ts index 738a052c6c..617edd4e7e 100644 --- a/packages/@lwc/engine-core/src/shared/logger.ts +++ b/packages/@lwc/engine-core/src/shared/logger.ts @@ -12,7 +12,7 @@ import type { VM } from '../framework/vm'; const alreadyLoggedMessages = new Set(); // Only used in LWC's Karma tests -if (process.env.NODE_ENV === 'test-karma-lwc') { +if (process.env.NODE_ENV === 'test-lwc-integration') { (window as any).__lwcResetAlreadyLoggedMessages = () => { alreadyLoggedMessages.clear(); }; diff --git a/packages/@lwc/engine-dom/src/styles.ts b/packages/@lwc/engine-dom/src/styles.ts index a050830d61..59ad7ec7f4 100644 --- a/packages/@lwc/engine-dom/src/styles.ts +++ b/packages/@lwc/engine-dom/src/styles.ts @@ -51,7 +51,7 @@ const stylesheetCache: Map = new Map(); // // Only used in LWC's Karma tests -if (process.env.NODE_ENV === 'test-karma-lwc') { +if (process.env.NODE_ENV === 'test-lwc-integration') { (window as any).__lwcResetGlobalStylesheets = () => { stylesheetCache.clear(); }; diff --git a/packages/@lwc/features/src/index.ts b/packages/@lwc/features/src/index.ts index a60f471c9d..c8370a80e3 100644 --- a/packages/@lwc/features/src/index.ts +++ b/packages/@lwc/features/src/index.ts @@ -60,8 +60,8 @@ export function setFeatureFlag(name: FeatureFlagName, value: FeatureFlagValue): ); return; } - // This may seem redundant, but `process.env.NODE_ENV === 'test-karma-lwc'` is replaced by Karma tests - if (process.env.NODE_ENV === 'test-karma-lwc' || process.env.NODE_ENV !== 'production') { + // This may seem redundant, but `process.env.NODE_ENV === 'test-lwc-integration'` is replaced by Karma tests + if (process.env.NODE_ENV === 'test-lwc-integration' || process.env.NODE_ENV !== 'production') { // Allow the same flag to be set more than once outside of production to enable testing flags[name] = value; } else { @@ -86,8 +86,8 @@ export function setFeatureFlag(name: FeatureFlagName, value: FeatureFlagValue): * @example setFeatureFlag("DISABLE_NATIVE_CUSTOM_ELEMENT_LIFECYCLE", true) */ export function setFeatureFlagForTest(name: FeatureFlagName, value: FeatureFlagValue): void { - // This may seem redundant, but `process.env.NODE_ENV === 'test-karma-lwc'` is replaced by Karma tests - if (process.env.NODE_ENV === 'test-karma-lwc' || process.env.NODE_ENV !== 'production') { + // This may seem redundant, but `process.env.NODE_ENV === 'test-lwc-integration'` is replaced by Karma tests + if (process.env.NODE_ENV === 'test-lwc-integration' || process.env.NODE_ENV !== 'production') { setFeatureFlag(name, value); } } diff --git a/packages/@lwc/integration-karma/scripts/karma-plugins/hydration-tests.js b/packages/@lwc/integration-karma/scripts/karma-plugins/hydration-tests.js index 7a5d1cad1a..0866da5967 100644 --- a/packages/@lwc/integration-karma/scripts/karma-plugins/hydration-tests.js +++ b/packages/@lwc/integration-karma/scripts/karma-plugins/hydration-tests.js @@ -266,7 +266,10 @@ function createHCONFIG2JSPreprocessor(config, logger, emitter) { testWatchFiles.concat(componentWatchFilesCSR).concat(componentWatchFilesSSR) ); ssrOutput = await getSsrCode( - componentDefSSR.replace(`process.env.NODE_ENV === 'test-karma-lwc'`, 'true'), + componentDefSSR.replace( + `process.env.NODE_ENV === 'test-lwc-integration'`, + 'true' + ), testCode, path.join(suiteDir, 'ssr.js'), expectedSSRConsoleCalls diff --git a/packages/@lwc/integration-karma/scripts/karma-plugins/transform-framework.js b/packages/@lwc/integration-karma/scripts/karma-plugins/transform-framework.js index ce3863cafd..dba4ec5da8 100644 --- a/packages/@lwc/integration-karma/scripts/karma-plugins/transform-framework.js +++ b/packages/@lwc/integration-karma/scripts/karma-plugins/transform-framework.js @@ -46,13 +46,13 @@ function createPreprocessor(config, emitter, logger) { const magicString = new MagicString(content.replace(/\/\/# sourceMappingURL=\S+/, '')); /** - * This transformation replaces `process.env.NODE_ENV === 'test-karma-lwc'` with `true`. + * This transformation replaces `process.env.NODE_ENV === 'test-lwc-integration'` with `true`. * * You might wonder why we replace the whole thing rather than just `process.env.NODE_ENV`. Well, because we need a way * to test `process.env.NODE_ENV === "production"` (prod mode) vs `process.env.NODE_ENV !== "production"` (dev mode). * If we replaced `process.env.NODE_ENV`, then that would be impossible. * - * Then you might wonder why we call it "test-karma-lwc" rather than something simple like "test". Well, because + * Then you might wonder why we call it "test-lwc-integration" rather than something simple like "test". Well, because * "test" was already squatted by Jest, and we actually use it for Jest-specific (not Karma-specific) behavior: * - https://jestjs.io/docs/environment-variables#node_env * - https://github.com/search?q=repo%3Asalesforce%2Flwc%20node_env%20%3D%3D%3D%20%27test%27&type=code @@ -71,7 +71,7 @@ function createPreprocessor(config, emitter, logger) { * * So that's why this is so weird and complicated. I'm sorry. */ - const replacee = `process.env.NODE_ENV === 'test-karma-lwc'`; + const replacee = `process.env.NODE_ENV === 'test-lwc-integration'`; // pad to keep things pretty in Istanbul coverage HTML magicString.replaceAll(replacee, 'true'.padEnd(replacee.length, ' ')); diff --git a/packages/@lwc/integration-not-karma/configs/base.js b/packages/@lwc/integration-not-karma/configs/base.js index cae6f5336e..9dae0c90c7 100644 --- a/packages/@lwc/integration-not-karma/configs/base.js +++ b/packages/@lwc/integration-not-karma/configs/base.js @@ -56,7 +56,7 @@ export default (options) => { if (ctx.type === 'application/javascript') { // FIXME: copy/paste Nolan's spiel about why we do this ugly thing return ctx.body.replace( - /process\.env\.NODE_ENV === 'test-karma-lwc'/g, + /process\.env\.NODE_ENV === 'test-lwc-integration'/g, 'true' ); } diff --git a/packages/@lwc/integration-not-karma/helpers/reset.js b/packages/@lwc/integration-not-karma/helpers/reset.js new file mode 100644 index 0000000000..ca4e2a5c92 --- /dev/null +++ b/packages/@lwc/integration-not-karma/helpers/reset.js @@ -0,0 +1,82 @@ +/** + * @fileoverview There are a number of __lwcReset functions scattered throughout + * the LWC codebase that are only defined when running integration tests, which + * reset various global states used by the framework. When updating tests, it is + * not always obvious to new developers where each function is defined and what + * purpose it serves. This file is intended to consolidate the functions, in + * order to reduce the amout of "magic" in the code. + */ + +function clearElement(elm) { + // `childNodes` and `attributes` are live lists, so we clone them to avoid + // skipping values as we delete items in the list + for (const child of [...elm.childNodes]) { + // Synthetic custom element lifecycle only patches DOM manipulation + // methods on Node.prototype, not Element.prototype, so we must use Node + // methods for signals tests to pass when using synthetic lifecycle. + elm.removeChild(child); + } + expect(elm.hasChildNodes(), `${elm.outerHTML} should not have child nodes`).toBe(false); + + for (const attr of [...elm.attributes]) { + elm.removeAttributeNode(attr); + } + expect(elm.attributes, `${elm.outerHTML} should not have attributes`).toHaveSize(0); +} + +/** + * Clears all nodes from the document's `head` and `body` and resets LWC's + * stylesheets cache. + * @see engine-dom/src/styles.ts + */ +export function resetDOM() { + clearElement(document.body); + clearElement(document.head); + + // LWC caches stylesheet data; we need to reset the cache when we clear the + // DOM, otherwise components will render with incorrect styles + // Defined in engine-dom/src/styles.ts + globalThis.__lwcResetGlobalStylesheets(); +} + +/** + * Resets the tracker for messages that only get logged once. + * @see engine-core/src/shared/logger.ts + */ +export function resetAlreadyLoggedMessages() { + globalThis.__lwcResetAlreadyLoggedMessages(); +} + +/** + * Resets the global state used for managing hot swaps. + * @see engine-core/src/framework/hot-swaps.ts + * @see engine-core/src/framework/stylehseet.ts + */ +export function resetHotSwaps() { + globalThis.__lwcResetHotSwaps(); + globalThis.__lwcResetStylesheetCache(); +} + +/** + * Resets the tracker for "trusted" signals, reverting to "untrusted" mode. + * @see shared/src/signals.ts + */ +export function resetTrustedSignals() { + globalThis.__lwcResetTrustedSignals(); +} + +/** + * Resets the fragment cache. TBH, I don't know what that is. + * @see engine-core/src/framework/fragment-cache.ts + */ +export function resetFragmentCache() { + return globalThis.__lwcResetFragmentCache(); +} + +/** + * The warning is supposed to only be logged once. This resets that tracker. + * @see engine-core/src/framework/check-version-mismatch.ts + */ +export function resetWarnedOnVersionMismatch() { + return globalThis.__lwcResetWarnedOnVersionMismatch(); +} diff --git a/packages/@lwc/integration-not-karma/helpers/setup.js b/packages/@lwc/integration-not-karma/helpers/setup.js index 691332359c..911d0de687 100644 --- a/packages/@lwc/integration-not-karma/helpers/setup.js +++ b/packages/@lwc/integration-not-karma/helpers/setup.js @@ -58,19 +58,3 @@ hijackGlobal('after', (after) => { // Expose as an alias for migration globalThis.afterAll = after; }); - -hijackGlobal('afterEach', (afterEach) => { - afterEach(() => { - // FIXME: Boost test speed by moving this to only files that need it - // Ensure the DOM is in a clean state - document.body.replaceChildren(); - document.head.replaceChildren(); - }); -}); - -hijackGlobal('beforeEach', (beforeEach) => { - beforeEach(() => { - // FIXME: Boost test speed by moving this to only files that need it - window.__lwcResetAlreadyLoggedMessages(); - }); -}); diff --git a/packages/@lwc/integration-not-karma/test/accessibility/non-standard-aria-props/index.spec.js b/packages/@lwc/integration-not-karma/test/accessibility/non-standard-aria-props/index.spec.js index 34fda932a0..e93335515e 100644 --- a/packages/@lwc/integration-not-karma/test/accessibility/non-standard-aria-props/index.spec.js +++ b/packages/@lwc/integration-not-karma/test/accessibility/non-standard-aria-props/index.spec.js @@ -7,6 +7,7 @@ import { attachReportingControlDispatcher, detachReportingControlDispatcher, } from '../../../helpers/reporting-control.js'; +import { resetAlreadyLoggedMessages } from '../../../helpers/reset.js'; // This test only works if the ARIA reflection polyfill is loaded describe.runIf(process.env.ENABLE_ARIA_REFLECTION_GLOBAL_POLYFILL)( @@ -21,6 +22,7 @@ describe.runIf(process.env.ENABLE_ARIA_REFLECTION_GLOBAL_POLYFILL)( afterEach(() => { detachReportingControlDispatcher(); + resetAlreadyLoggedMessages(); }); nonStandardAriaProperties.forEach((prop) => { diff --git a/packages/@lwc/integration-not-karma/test/accessibility/synthetic-cross-root-aria/index.spec.js b/packages/@lwc/integration-not-karma/test/accessibility/synthetic-cross-root-aria/index.spec.js index 4a0935b845..2b11edcc60 100644 --- a/packages/@lwc/integration-not-karma/test/accessibility/synthetic-cross-root-aria/index.spec.js +++ b/packages/@lwc/integration-not-karma/test/accessibility/synthetic-cross-root-aria/index.spec.js @@ -7,6 +7,7 @@ import { attachReportingControlDispatcher, detachReportingControlDispatcher, } from '../../../helpers/reporting-control.js'; +import { resetAlreadyLoggedMessages, resetDOM } from '../../../helpers/reset.js'; const expectedMessageForCrossRoot = 'Error: [LWC warn]: Element uses attribute "aria-labelledby" to reference element