diff --git a/.github/workflows/web-test-runner.yml b/.github/workflows/web-test-runner.yml index 12199e8506..3db9f7dd3b 100644 --- a/.github/workflows/web-test-runner.yml +++ b/.github/workflows/web-test-runner.yml @@ -18,14 +18,18 @@ env: NODE_VERSION: '20.19.4' jobs: - # Starting with the basics, just get tests running in CI - # TODO: add env var combos we use for Karma tests # TODO: upload result artifacts # TODO: make it saucy 🥫 - wtr-group-1: + integration-tests: + name: Integration tests (${{ matrix.shadow_mode }} shadow) + strategy: + matrix: + shadow_mode: [native, synthetic] + runs-on: ubuntu-22.04 env: SAUCE_TUNNEL_ID: github-action-tunnel-wtr-${{github.run_id}}-group-1 + SHADOW_MODE_OVERRIDE: ${{ matrix.shadow_mode }} defaults: run: working-directory: ./packages/@lwc/integration-not-karma @@ -51,4 +55,93 @@ jobs: # region: us - run: yarn test + - run: API_VERSION=58 yarn test + - run: API_VERSION=59 yarn test + - run: API_VERSION=60 yarn test + - run: API_VERSION=61 yarn test + - run: API_VERSION=62 yarn test + - run: DISABLE_NATIVE_CUSTOM_ELEMENT_LIFECYCLE=1 yarn test || true + - run: DISABLE_STATIC_CONTENT_OPTIMIZATION=1 yarn test + - run: ENABLE_ARIA_REFLECTION_GLOBAL_POLYFILL=1 yarn test + - run: NODE_ENV_FOR_TEST=production yarn test + + integration-tests-not-both-modes: + # Tests that should run in only synthetic or native shadow, not both + name: Integration tests (singleton batch) + runs-on: ubuntu-22.04 + env: + SAUCE_TUNNEL_ID: github-action-tunnel-wtr-${{github.run_id}}-group-1 + SHADOW_MODE_OVERRIDE: ${{ matrix.shadow_mode }} + defaults: + run: + working-directory: ./packages/@lwc/integration-not-karma + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'yarn' + + - name: Install dependencies + run: yarn install --frozen-lockfile + working-directory: ./ + + # - uses: saucelabs/sauce-connect-action@v3.0.0 + # with: + # username: ${{ secrets.SAUCE_USERNAME }} + # accessKey: ${{ secrets.SAUCE_ACCESS_KEY }} + # tunnelName: ${{ env.SAUCE_TUNNEL_ID }} + # region: us + + # Synthetic shadow only + - run: LEGACY_BROWSERS=1 yarn test || true + - run: FORCE_NATIVE_SHADOW_MODE_FOR_TEST=1 yarn test + - run: DISABLE_DETACHED_REHYDRATION=1 yarn test + - run: DISABLE_NATIVE_CUSTOM_ELEMENT_LIFECYCLE=1 DISABLE_DETACHED_REHYDRATION=1 yarn test || true + + # Native shadow only -- don't forget SHADOW_MODE_OVERRIDE! + - run: SHADOW_MODE_OVERRIDE=native DISABLE_SYNTHETIC_SHADOW_SUPPORT_IN_COMPILER=1 yarn test + - run: SHADOW_MODE_OVERRIDE=native DISABLE_SYNTHETIC_SHADOW_SUPPORT_IN_COMPILER=1 DISABLE_STATIC_CONTENT_OPTIMIZATION=1 yarn test + + hydration-tests: + runs-on: ubuntu-22.04 + env: + SAUCE_TUNNEL_ID: github-action-tunnel-wtr-${{github.run_id}}-group-1 + defaults: + run: + working-directory: ./packages/@lwc/integration-not-karma + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'yarn' + + - name: Install dependencies + run: yarn install --frozen-lockfile + working-directory: ./ + + # - uses: saucelabs/sauce-connect-action@v3.0.0 + # with: + # username: ${{ secrets.SAUCE_USERNAME }} + # accessKey: ${{ secrets.SAUCE_ACCESS_KEY }} + # tunnelName: ${{ env.SAUCE_TUNNEL_ID }} + # region: us + - run: ENGINE_SERVER=1 yarn test:hydration + - run: ENGINE_SERVER=1 SHADOW_MODE_OVERRIDE=synthetic yarn test:hydration + - run: ENGINE_SERVER=1 NODE_ENV_FOR_TEST=production yarn test:hydration + - run: ENGINE_SERVER=1 DISABLE_NATIVE_CUSTOM_ELEMENT_LIFECYCLE=1 yarn test:hydration + - run: ENGINE_SERVER=1 DISABLE_STATIC_CONTENT_OPTIMIZATION=1 yarn test:hydration + - run: ENGINE_SERVER=1 DISABLE_DETACHED_REHYDRATION=1 yarn test:hydration + - run: ENGINE_SERVER=1 DISABLE_NATIVE_CUSTOM_ELEMENT_LIFECYCLE=1 DISABLE_DETACHED_REHYDRATION=1 yarn test:hydration - run: yarn test:hydration + - run: SHADOW_MODE_OVERRIDE=synthetic yarn test:hydration + - run: NODE_ENV_FOR_TEST=production yarn test:hydration + - run: DISABLE_NATIVE_CUSTOM_ELEMENT_LIFECYCLE=1 yarn test:hydration + - run: DISABLE_STATIC_CONTENT_OPTIMIZATION=1 yarn test:hydration diff --git a/packages/@lwc/integration-not-karma/configs/base.js b/packages/@lwc/integration-not-karma/configs/base.js index c220593ff3..cae6f5336e 100644 --- a/packages/@lwc/integration-not-karma/configs/base.js +++ b/packages/@lwc/integration-not-karma/configs/base.js @@ -1,55 +1,70 @@ import { join } from 'node:path'; import { LWC_VERSION } from '@lwc/shared'; -import * as options from '../helpers/options.js'; import { resolvePathOutsideRoot } from '../helpers/utils.js'; +/** + * We want to convert from parsed options (true/false) to a `process.env` with only strings. + * This drops `false` values and converts everything else to a string. + */ +const envify = (obj) => { + const clone = {}; + for (const [key, val] of Object.entries(obj)) { + if (val !== false) { + clone[key] = String(val); + } + } + return clone; +}; const pluck = (obj, keys) => Object.fromEntries(keys.map((k) => [k, obj[k]])); const maybeImport = (file, condition) => (condition ? `await import('${file}');` : ''); -/** `process.env` to inject into test environment. */ -const env = { - ...pluck(options, [ - 'API_VERSION', - 'DISABLE_NATIVE_CUSTOM_ELEMENT_LIFECYCLE', - 'DISABLE_STATIC_CONTENT_OPTIMIZATION', - 'DISABLE_SYNTHETIC', - 'ENABLE_ARIA_REFLECTION_GLOBAL_POLYFILL', - 'ENGINE_SERVER', - 'FORCE_NATIVE_SHADOW_MODE_FOR_TEST', - 'NATIVE_SHADOW', - 'DISABLE_DETACHED_REHYDRATION', - ]), - LWC_VERSION, - NODE_ENV: options.NODE_ENV_FOR_TEST, -}; +/** @type {() => import("@web/test-runner").TestRunnerConfig} */ +export default (options) => { + /** `process.env` to inject into test environment. */ + const env = envify({ + ...pluck(options, [ + 'API_VERSION', + 'DISABLE_DETACHED_REHYDRATION', + 'DISABLE_NATIVE_CUSTOM_ELEMENT_LIFECYCLE', + 'DISABLE_STATIC_CONTENT_OPTIMIZATION', + 'ENABLE_ARIA_REFLECTION_GLOBAL_POLYFILL', + 'ENGINE_SERVER', + 'FORCE_NATIVE_SHADOW_MODE_FOR_TEST', + 'NATIVE_SHADOW', + ]), + LWC_VERSION, + NODE_ENV: options.NODE_ENV_FOR_TEST, + }); -/** @type {import("@web/test-runner").TestRunnerConfig} */ -export default { - // FIXME: Parallelism breaks tests that rely on focus/requestAnimationFrame, because they often - // time out before they receive focus. But it also makes the full suite take 3x longer to run... - // Potential workaround: https://github.com/modernweb-dev/web/issues/2588 - concurrency: 1, - browserLogs: false, - nodeResolve: true, - rootDir: join(import.meta.dirname, '..'), - plugins: [ - { - name: 'lwc-base-plugin', - resolveImport({ source }) { - if (source === 'wire-service') { - return resolvePathOutsideRoot('../wire-service/dist/index.js'); - } - }, - async transform(ctx) { - 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, 'true'); - } + return { + // FIXME: Parallelism breaks tests that rely on focus/requestAnimationFrame, because they often + // time out before they receive focus. But it also makes the full suite take 3x longer to run... + // Potential workaround: https://github.com/modernweb-dev/web/issues/2588 + concurrency: 1, + browserLogs: false, + nodeResolve: true, + rootDir: join(import.meta.dirname, '..'), + plugins: [ + { + name: 'lwc-base-plugin', + resolveImport({ source }) { + if (source === 'wire-service') { + return resolvePathOutsideRoot('../wire-service/dist/index.js'); + } + }, + async transform(ctx) { + 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, + 'true' + ); + } + }, }, - }, - ], - testRunnerHtml: (testFramework) => - ` + ], + testRunnerHtml: (testFramework) => + `
`, + }; }; diff --git a/packages/@lwc/integration-not-karma/configs/hydration.js b/packages/@lwc/integration-not-karma/configs/hydration.js index 67a2419850..1b085ad761 100644 --- a/packages/@lwc/integration-not-karma/configs/hydration.js +++ b/packages/@lwc/integration-not-karma/configs/hydration.js @@ -1,8 +1,14 @@ -// Use native shadow by default in hydration tests; MUST be set before imports -process.env.DISABLE_SYNTHETIC ??= 'true'; -import baseConfig from './base.js'; +import * as options from '../helpers/options.js'; +import createConfig from './base.js'; import hydrationTestPlugin from './plugins/serve-hydration.js'; +const SHADOW_MODE = options.SHADOW_MODE_OVERRIDE ?? 'native'; + +const baseConfig = createConfig({ + ...options, + NATIVE_SHADOW: SHADOW_MODE === 'native' || options.FORCE_NATIVE_SHADOW_MODE_FOR_TEST, +}); + /** @type {import("@web/test-runner").TestRunnerConfig} */ export default { ...baseConfig, diff --git a/packages/@lwc/integration-not-karma/configs/integration.js b/packages/@lwc/integration-not-karma/configs/integration.js index 60cb5e4424..f7b2b8618a 100644 --- a/packages/@lwc/integration-not-karma/configs/integration.js +++ b/packages/@lwc/integration-not-karma/configs/integration.js @@ -1,11 +1,24 @@ import { importMapsPlugin } from '@web/dev-server-import-maps'; -import baseConfig from './base.js'; +import * as options from '../helpers/options.js'; +import createConfig from './base.js'; import testPlugin from './plugins/serve-integration.js'; +const SHADOW_MODE = options.SHADOW_MODE_OVERRIDE ?? 'synthetic'; + +const baseConfig = createConfig({ + ...options, + NATIVE_SHADOW: SHADOW_MODE === 'native' || options.FORCE_NATIVE_SHADOW_MODE_FOR_TEST, +}); + /** @type {import("@web/test-runner").TestRunnerConfig} */ export default { ...baseConfig, - files: ['test/**/*.spec.js'], + files: [ + 'test/**/*.spec.js', + // Make John fix this after his PR is merged + '!test/template-expressions/errors/index.spec.js', + '!test/template-expressions/smoke-test/index.spec.js', + ], plugins: [ ...baseConfig.plugins, importMapsPlugin({ inject: { importMap: { imports: { lwc: './mocks/lwc.js' } } } }), diff --git a/packages/@lwc/integration-not-karma/configs/plugins/test-hydration.js b/packages/@lwc/integration-not-karma/configs/plugins/test-hydration.js index 3c752910b0..3663a72972 100644 --- a/packages/@lwc/integration-not-karma/configs/plugins/test-hydration.js +++ b/packages/@lwc/integration-not-karma/configs/plugins/test-hydration.js @@ -52,8 +52,8 @@ export function runTest(configPath, componentPath, ssrRendered) { let Component; beforeAll(async () => { - testConfig = await import(configPath); - Component = await import(componentPath); + testConfig = (await import(configPath)).default; + Component = (await import(componentPath)).default; setFeatureFlags(testConfig.requiredFeatureFlags, true); }); @@ -93,6 +93,8 @@ export function runTest(configPath, componentPath, ssrRendered) { container, selector, }); + } else { + throw new Error(`Missing test or advancedTest function in ${configPath}.`); } }); } diff --git a/packages/@lwc/integration-not-karma/helpers/options.js b/packages/@lwc/integration-not-karma/helpers/options.js index 7124677ef4..51a9dd99f8 100644 --- a/packages/@lwc/integration-not-karma/helpers/options.js +++ b/packages/@lwc/integration-not-karma/helpers/options.js @@ -8,8 +8,6 @@ import { HIGHEST_API_VERSION } from '@lwc/shared'; export const LEGACY_BROWSERS = Boolean(process.env.LEGACY_BROWSERS); -export const DISABLE_SYNTHETIC = Boolean(process.env.DISABLE_SYNTHETIC); - export const FORCE_NATIVE_SHADOW_MODE_FOR_TEST = Boolean( process.env.FORCE_NATIVE_SHADOW_MODE_FOR_TEST ); @@ -36,20 +34,26 @@ export const ENGINE_SERVER = Boolean(process.env.ENGINE_SERVER); // --- Test config --- // +/** + * Integration tests default to synthetic shadow mode, while hydration tests default to native. + * This should be set to "native" or "synthetic" to override the default mode. + * @type {'native'|'synthetic'|undefined} + */ +// NOTE: NATIVE_SHADOW is not defined here because integration/hydration have different defaults +export const SHADOW_MODE_OVERRIDE = process.env.SHADOW_MODE_OVERRIDE; + export const API_VERSION = process.env.API_VERSION ? parseInt(process.env.API_VERSION, 10) : HIGHEST_API_VERSION; export const NODE_ENV_FOR_TEST = process.env.NODE_ENV_FOR_TEST || 'development'; -export const NATIVE_SHADOW = DISABLE_SYNTHETIC || FORCE_NATIVE_SHADOW_MODE_FOR_TEST; - /** Unique directory name that encodes the flags that the tests were executed with. */ export const COVERAGE_DIR_FOR_OPTIONS = Object.entries({ API_VERSION, DISABLE_STATIC_CONTENT_OPTIMIZATION, - DISABLE_SYNTHETIC, + SHADOW_MODE_OVERRIDE, DISABLE_SYNTHETIC_SHADOW_SUPPORT_IN_COMPILER, ENABLE_ARIA_REFLECTION_GLOBAL_POLYFILL, FORCE_NATIVE_SHADOW_MODE_FOR_TEST, diff --git a/packages/@lwc/integration-not-karma/test-hydration/synthetic-shadow/index.spec.js b/packages/@lwc/integration-not-karma/test-hydration/synthetic-shadow/index.spec.js index 7932c8bf9e..04e76568a0 100644 --- a/packages/@lwc/integration-not-karma/test-hydration/synthetic-shadow/index.spec.js +++ b/packages/@lwc/integration-not-karma/test-hydration/synthetic-shadow/index.spec.js @@ -22,7 +22,7 @@ export default { expect(child.shadowRoot.synthetic).toBeUndefined(); // sanity check that the env var is working - if (process.env.DISABLE_SYNTHETIC) { + if (process.env.NATIVE_SHADOW) { expect(document.body.attachShadow.toString()).toContain('[native code'); } else { expect(document.body.attachShadow.toString()).not.toContain('[native code'); diff --git a/packages/@lwc/integration-not-karma/test/component/LightningElement.attachInternals/elementInternals/sanity/index.spec.js b/packages/@lwc/integration-not-karma/test/component/LightningElement.attachInternals/elementInternals/sanity/index.spec.js index 8bf34045ab..71caf8af55 100644 --- a/packages/@lwc/integration-not-karma/test/component/LightningElement.attachInternals/elementInternals/sanity/index.spec.js +++ b/packages/@lwc/integration-not-karma/test/component/LightningElement.attachInternals/elementInternals/sanity/index.spec.js @@ -9,10 +9,6 @@ beforeEach(() => { document.body.appendChild(elm); }); -afterEach(() => { - document.body.removeChild(elm); -}); - describe.runIf( ENABLE_ELEMENT_INTERNALS_AND_FACE && process.env.NATIVE_SHADOW && diff --git a/packages/@lwc/integration-not-karma/test/lwc-on/index.spec.js b/packages/@lwc/integration-not-karma/test/lwc-on/index.spec.js index 32a1562a25..a054025c85 100644 --- a/packages/@lwc/integration-not-karma/test/lwc-on/index.spec.js +++ b/packages/@lwc/integration-not-karma/test/lwc-on/index.spec.js @@ -11,45 +11,40 @@ import RerenderLoop from 'x/rerenderLoop'; import PublicProp from 'x/publicProp'; import ComputedKey from 'x/computedKey'; import ValueEvaluationThrows from 'x/valueEvaluationThrows'; -import { spyOn } from '@vitest/spy'; -import { jasmine } from '../../helpers/jasmine.js'; +import { spyOn, fn as mockFn } from '@vitest/spy'; import { catchUnhandledRejectionsAndErrors } from '../../helpers/utils.js'; describe('lwc:on', () => { it('adds multiple event listeners', () => { const element = createElement('x-basic', { is: Basic }); - const testFn = jasmine.createSpy('test function'); - element.testFn = testFn; + element.testFn = mockFn(); document.body.appendChild(element); const button = element.shadowRoot.querySelector('button'); button.click(); button.dispatchEvent(new MouseEvent('mouseover')); - expect(testFn).toHaveBeenCalledWith('click handler called'); - expect(testFn).toHaveBeenCalledWith('mouseover handler called'); + expect(element.testFn).toHaveBeenCalledWith('click handler called'); + expect(element.testFn).toHaveBeenCalledWith('mouseover handler called'); }); it('event listeners added by lwc:on are bound to the owner component', () => { const element = createElement('x-execution-context', { is: ExecutionContext }); - const testFn = jasmine.createSpy('test function'); - element.testFn = testFn; + element.testFn = mockFn(); document.body.appendChild(element); const button = element.shadowRoot.querySelector('button'); button.click(); - expect(testFn).toHaveBeenCalledWith("'this' is the component"); + expect(element.testFn).toHaveBeenCalledWith("'this' is the component"); }); describe('ignored properties', () => { let element; let button; - let testFn; function setup(propType) { element = createElement('x-ignored', { is: Ignored }); - testFn = jasmine.createSpy('test function'); - element.testFn = testFn; + element.testFn = mockFn(); element.propType = propType; document.body.appendChild(element); button = element.shadowRoot.querySelector('button'); @@ -60,13 +55,13 @@ describe('lwc:on', () => { it('silently ignores non-enumerable properties', () => { setup('non-enumerable'); button.click(); - expect(testFn).not.toHaveBeenCalled(); + expect(element.testFn).not.toHaveBeenCalled(); }); it('silently ignores inherited properties', () => { setup('inherited'); button.click(); - expect(testFn).not.toHaveBeenCalled(); + expect(element.testFn).not.toHaveBeenCalled(); }); it('silently ignores symbol-keyed properties', () => { @@ -77,12 +72,10 @@ describe('lwc:on', () => { describe('event type case', () => { let element; let button; - let testFn; function setup(propCase) { element = createElement('x-case-variants', { is: CaseVariants }); - testFn = jasmine.createSpy('test function'); - element.testFn = testFn; + element.testFn = mockFn(); element.propCase = propCase; document.body.appendChild(element); button = element.shadowRoot.querySelector('button'); @@ -91,60 +84,58 @@ describe('lwc:on', () => { it('adds event listeners corresponding to lowercase keyed property', () => { setup('lower'); button.dispatchEvent(new CustomEvent('lowercase')); - expect(testFn).toHaveBeenCalledWith('lowercase handler called'); + expect(element.testFn).toHaveBeenCalledWith('lowercase handler called'); }); it('adds event listeners corresponding to kebab-case keyed property', () => { setup('kebab'); button.dispatchEvent(new CustomEvent('kebab-case')); - expect(testFn).toHaveBeenCalledWith('kebab-case handler called'); + expect(element.testFn).toHaveBeenCalledWith('kebab-case handler called'); }); it('adds event listeners corresponding to camelCase keyed property', () => { setup('camel'); button.dispatchEvent(new CustomEvent('camelCase')); - expect(testFn).toHaveBeenCalledWith('camelCase handler called'); + expect(element.testFn).toHaveBeenCalledWith('camelCase handler called'); }); it('adds event listeners corresponding to CAPScase keyed property', () => { setup('caps'); button.dispatchEvent(new CustomEvent('CAPSCASE')); - expect(testFn).toHaveBeenCalledWith('CAPSCASE handler called'); + expect(element.testFn).toHaveBeenCalledWith('CAPSCASE handler called'); }); it('adds event listeners corresponding to PascalCase keyed property', () => { setup('pascal'); button.dispatchEvent(new CustomEvent('PascalCase')); - expect(testFn).toHaveBeenCalledWith('PascalCase handler called'); + expect(element.testFn).toHaveBeenCalledWith('PascalCase handler called'); }); it('adds event listeners corresponding to empty-string keyed property', () => { setup('empty'); button.dispatchEvent(new CustomEvent('')); - expect(testFn).toHaveBeenCalledWith('empty string handler called'); + expect(element.testFn).toHaveBeenCalledWith('empty string handler called'); }); }); it('event listeners are added independently from lwc:on and lwc:spread', () => { const element = createElement('x-spread', { is: Spread }); - const testFn = jasmine.createSpy('test function'); - element.testFn = testFn; + element.testFn = mockFn(); document.body.appendChild(element); const button = element.shadowRoot.querySelector('button'); button.click(); - expect(testFn).toHaveBeenCalledWith('lwc:spread handler called'); - expect(testFn).toHaveBeenCalledWith('lwc:on handler called'); + expect(element.testFn).toHaveBeenCalledWith('lwc:spread handler called'); + expect(element.testFn).toHaveBeenCalledWith('lwc:on handler called'); }); it("event listeners are added before child's connectedCallback", () => { const element = createElement('x-lifecycle', { is: Lifecycle }); - const testFn = jasmine.createSpy('foo handler'); - element.testFn = testFn; + element.testFn = mockFn(); document.body.appendChild(element); - expect(testFn).toHaveBeenCalledWith( + expect(element.testFn).toHaveBeenCalledWith( 'handled events dispatched from child connectedCallback' ); }); @@ -214,13 +205,11 @@ describe('lwc:on', () => { describe('re-render behavior', () => { let element; let button; - let testFn; describe('without for:each loop', () => { beforeEach(() => { element = createElement('x-rerender', { is: Rerender }); - testFn = jasmine.createSpy('test function'); - element.testFn = testFn; + element.testFn = mockFn(); document.body.appendChild(element); button = element.shadowRoot.querySelector('button'); }); @@ -232,8 +221,8 @@ describe('lwc:on', () => { button.click(); button.dispatchEvent(new MouseEvent('mouseover')); - expect(testFn).toHaveBeenCalledWith('click handler called'); - expect(testFn).toHaveBeenCalledWith('mouseover handler called'); + expect(element.testFn).toHaveBeenCalledWith('click handler called'); + expect(element.testFn).toHaveBeenCalledWith('mouseover handler called'); }); it('Event listeners are removed when lwc:on is provided a new object with reduced properties', async () => { @@ -241,7 +230,7 @@ describe('lwc:on', () => { await element.triggerReRender(); button.click(); - expect(testFn).not.toHaveBeenCalledWith('click handler called'); + expect(element.testFn).not.toHaveBeenCalledWith('click handler called'); }); it('Event listeners are modified when lwc:on is provided a new object with modified properties', async () => { @@ -249,8 +238,8 @@ describe('lwc:on', () => { await element.triggerReRender(); button.click(); - expect(testFn).not.toHaveBeenCalledWith('click handler called'); - expect(testFn).toHaveBeenCalledWith('modified click handler called'); + expect(element.testFn).not.toHaveBeenCalledWith('click handler called'); + expect(element.testFn).toHaveBeenCalledWith('modified click handler called'); }); }); @@ -322,8 +311,7 @@ describe('lwc:on', () => { describe('with for:each loop and local variable passed as argument to lwc:on', () => { beforeEach(() => { element = createElement('x-rerender-loop', { is: RerenderLoop }); - testFn = jasmine.createSpy('test function'); - element.testFn = testFn; + element.testFn = mockFn(); document.body.appendChild(element); button = element.shadowRoot.querySelector('button'); }); @@ -335,8 +323,8 @@ describe('lwc:on', () => { button.click(); button.dispatchEvent(new MouseEvent('mouseover')); - expect(testFn).toHaveBeenCalledWith('click handler called'); - expect(testFn).toHaveBeenCalledWith('mouseover handler called'); + expect(element.testFn).toHaveBeenCalledWith('click handler called'); + expect(element.testFn).toHaveBeenCalledWith('mouseover handler called'); }); it('Event listeners are removed when lwc:on is provided a new object with reduced properties', async () => { @@ -344,7 +332,7 @@ describe('lwc:on', () => { await element.triggerReRender(); button.click(); - expect(testFn).not.toHaveBeenCalledWith('click handler called'); + expect(element.testFn).not.toHaveBeenCalledWith('click handler called'); }); it('Event listeners are modified when lwc:on is provided a new object with modified properties', async () => { @@ -352,8 +340,8 @@ describe('lwc:on', () => { await element.triggerReRender(); button.click(); - expect(testFn).not.toHaveBeenCalledWith('click handler called'); - expect(testFn).toHaveBeenCalledWith('modified click handler called'); + expect(element.testFn).not.toHaveBeenCalledWith('click handler called'); + expect(element.testFn).toHaveBeenCalledWith('modified click handler called'); }); }); @@ -426,10 +414,8 @@ describe('lwc:on', () => { it('works when the object is passed as public property to component', () => { // In this test, we are implicitly asserting that no error is thrown if the test passes const element = createElement('x-public-prop', { is: PublicProp }); - const testFn = jasmine.createSpy('test function'); - element.eventHandlers = { - click: testFn, - }; + const testFn = mockFn(); + element.eventHandlers = { click: testFn }; document.body.appendChild(element); const button = element.shadowRoot.querySelector('button'); @@ -439,13 +425,12 @@ describe('lwc:on', () => { it('works properly with objects whose keys are computed', () => { const element = createElement('x-computed-key', { is: ComputedKey }); - const testFn = jasmine.createSpy('test function'); - element.testFn = testFn; + element.testFn = mockFn(); document.body.appendChild(element); const button = element.shadowRoot.querySelector('button'); button.click(); - expect(testFn).toHaveBeenCalled(); + expect(element.testFn).toHaveBeenCalled(); }); describe('object passed to lwc:on has property whose value evaluation throws', () => { diff --git a/packages/@lwc/integration-not-karma/test/misc/lifecycle-remove-disconnected/lifecycle-remove-disconnected.spec.js b/packages/@lwc/integration-not-karma/test/misc/lifecycle-remove-disconnected/lifecycle-remove-disconnected.spec.js index 2903426bd9..1a07acc4b0 100644 --- a/packages/@lwc/integration-not-karma/test/misc/lifecycle-remove-disconnected/lifecycle-remove-disconnected.spec.js +++ b/packages/@lwc/integration-not-karma/test/misc/lifecycle-remove-disconnected/lifecycle-remove-disconnected.spec.js @@ -17,9 +17,13 @@ describe('vdom removes component while it is already disconnected', () => { expect(spy).not.toHaveBeenCalled(); } else { // expected since the engine calls appendChild to a disconnected DOM node - expect(spy).toHaveBeenCalledTimes(1); - expect(spy.calls.mostRecent().args[0]).toMatch( - /fired a `connectedCallback` and rendered, but was not connected to the DOM/ + expect(spy).toHaveBeenCalledExactlyOnceWith(expect.any(Error)); + expect(spy).toHaveBeenCalledExactlyOnceWith( + expect.objectContaining({ + message: expect.stringMatching( + /fired a `connectedCallback` and rendered, but was not connected to the DOM/ + ), + }) ); } }); diff --git a/packages/@lwc/integration-not-karma/test/native-shadow/force-shadow-migrate-mode/index.spec.js b/packages/@lwc/integration-not-karma/test/native-shadow/force-shadow-migrate-mode/index.spec.js index c1afb704e7..f494070a43 100644 --- a/packages/@lwc/integration-not-karma/test/native-shadow/force-shadow-migrate-mode/index.spec.js +++ b/packages/@lwc/integration-not-karma/test/native-shadow/force-shadow-migrate-mode/index.spec.js @@ -30,8 +30,9 @@ describe.runIf(process.env.NATIVE_SHADOW && !process.env.FORCE_NATIVE_SHADOW_MOD }); describe('flag on', () => { - beforeEach(() => { + beforeEach(async () => { setFeatureFlagForTest('ENABLE_FORCE_SHADOW_MIGRATE_MODE', true); + await Promise.resolve(); }); afterEach(() => { @@ -59,9 +60,8 @@ describe.runIf(process.env.NATIVE_SHADOW && !process.env.FORCE_NATIVE_SHADOW_MOD expect(isActuallyNativeShadow(elm.shadowRoot)).toBe(true); expect(isClaimingToBeSyntheticShadow(elm.shadowRoot)).toBe(false); - expect(getComputedStyle(elm.shadowRoot.querySelector('h1')).color).toBe( - 'rgb(0, 0, 0)' - ); + const computed = getComputedStyle(elm.shadowRoot.querySelector('h1')); + expect(computed.color).toBe('rgb(0, 0, 0)'); }); it('does not apply styles from global light DOM components to synthetic components', async () => { @@ -83,9 +83,8 @@ describe.runIf(process.env.NATIVE_SHADOW && !process.env.FORCE_NATIVE_SHADOW_MOD await doubleMicrotask(); - expect(getComputedStyle(elm.shadowRoot.querySelector('h1')).color).toBe( - 'rgb(0, 0, 255)' - ); + let computed = getComputedStyle(elm.shadowRoot.querySelector('h1')); + expect(computed.color).toBe('rgb(0, 0, 255)'); const style = document.createElement('style'); style.textContent = `h1 { color: purple; background-color: crimson }`; @@ -93,12 +92,9 @@ describe.runIf(process.env.NATIVE_SHADOW && !process.env.FORCE_NATIVE_SHADOW_MOD await doubleMicrotask(); - expect(getComputedStyle(elm.shadowRoot.querySelector('h1')).color).toBe( - 'rgb(128, 0, 128)' - ); - expect(getComputedStyle(elm.shadowRoot.querySelector('h1')).backgroundColor).toBe( - 'rgb(220, 20, 60)' - ); + computed = getComputedStyle(elm.shadowRoot.querySelector('h1')); + expect(computed.color).toBe('rgb(128, 0, 128)'); + expect(computed.backgroundColor).toBe('rgb(220, 20, 60)'); }); it('local styles are defined after global styles', async () => { @@ -111,12 +107,9 @@ describe.runIf(process.env.NATIVE_SHADOW && !process.env.FORCE_NATIVE_SHADOW_MOD await doubleMicrotask(); - expect(getComputedStyle(elm.shadowRoot.querySelector('h1')).backgroundColor).toBe( - 'rgb(0, 128, 0)' - ); - expect(getComputedStyle(elm.shadowRoot.querySelector('h1')).fontFamily).toBe( - 'monospace' - ); + const computed = getComputedStyle(elm.shadowRoot.querySelector('h1')); + expect(computed.backgroundColor).toBe('rgb(0, 128, 0)'); + expect(computed.fontFamily).toBe('monospace'); }); }); @@ -128,9 +121,8 @@ describe.runIf(process.env.NATIVE_SHADOW && !process.env.FORCE_NATIVE_SHADOW_MOD await doubleMicrotask(); expect(isActuallyNativeShadow(elm.shadowRoot)).toBe(true); expect(isClaimingToBeSyntheticShadow(elm.shadowRoot)).toBe(false); - expect(getComputedStyle(elm.shadowRoot.querySelector('h1')).color).toBe( - 'rgb(0, 0, 0)' - ); + const computed = getComputedStyle(elm.shadowRoot.querySelector('h1')); + expect(computed.color).toBe('rgb(0, 0, 0)'); }); it('does not use global styles for native components', async () => { @@ -140,9 +132,8 @@ describe.runIf(process.env.NATIVE_SHADOW && !process.env.FORCE_NATIVE_SHADOW_MOD await doubleMicrotask(); expect(isActuallyNativeShadow(elm.shadowRoot)).toBe(true); expect(isClaimingToBeSyntheticShadow(elm.shadowRoot)).toBe(false); - expect(getComputedStyle(elm.shadowRoot.querySelector('h1')).color).toBe( - 'rgb(0, 0, 0)' - ); + const computed = getComputedStyle(elm.shadowRoot.querySelector('h1')); + expect(computed.color).toBe('rgb(0, 0, 0)'); }); }); } diff --git a/packages/@lwc/integration-not-karma/test/shadow-dom/Node-properties/Node.hasChildNodes.spec.js b/packages/@lwc/integration-not-karma/test/shadow-dom/Node-properties/Node.hasChildNodes.spec.js index 083c32fa68..b9bb212eb4 100644 --- a/packages/@lwc/integration-not-karma/test/shadow-dom/Node-properties/Node.hasChildNodes.spec.js +++ b/packages/@lwc/integration-not-karma/test/shadow-dom/Node-properties/Node.hasChildNodes.spec.js @@ -26,7 +26,7 @@ describe('Node.hasChildNodes', () => { expect(container.shadowRoot.querySelector('.container').hasChildNodes()).toBe(true); expect(container.shadowRoot.querySelector('slot').hasChildNodes()).toBe( - process.env.NATIVE_SHADOW + Boolean(process.env.NATIVE_SHADOW) ); }); }); diff --git a/packages/@lwc/integration-not-karma/test/shadow-dom/multiple-templates/index.spec.js b/packages/@lwc/integration-not-karma/test/shadow-dom/multiple-templates/index.spec.js index 499eb919df..aea8616097 100644 --- a/packages/@lwc/integration-not-karma/test/shadow-dom/multiple-templates/index.spec.js +++ b/packages/@lwc/integration-not-karma/test/shadow-dom/multiple-templates/index.spec.js @@ -24,11 +24,13 @@ describe.runIf(process.env.NATIVE_SHADOW)( ); expect(getNumStyleSheets()).toEqual(1); element.next(); + await Promise.resolve(); expect(getComputedStyle(element.shadowRoot.querySelector('div')).color).toEqual( 'rgb(255, 0, 0)' ); expect(getNumStyleSheets()).toEqual(2); element.next(); + await Promise.resolve(); expect(getComputedStyle(element.shadowRoot.querySelector('div')).color).toEqual( 'rgb(0, 0, 255)' );