diff --git a/docs/api/browser/assertions.md b/docs/api/browser/assertions.md index 953bb93a6065..3db9c6d3397d 100644 --- a/docs/api/browser/assertions.md +++ b/docs/api/browser/assertions.md @@ -1152,10 +1152,9 @@ await expect.element(getByTestId('button')).toMatchScreenshot('fancy-button', { - `comparatorName: "pixelmatch" = "pixelmatch"` - The name of the algorithm/library used for comparing images. + The algorithm/library used for comparing images. - Currently, [`"pixelmatch"`](https://github.com/mapbox/pixelmatch) is the only - supported comparator. + `"pixelmatch"` is the only built-in comparator, but you can use custom ones by [registering them in the config file](/config/browser/expect#browser-expect-tomatchscreenshot-comparators). - `comparatorOptions: object` @@ -1210,7 +1209,7 @@ await expect.element(getByTestId('button')).toMatchScreenshot('fancy-button', { #### `"pixelmatch"` comparator options -The following options are available when using the `"pixelmatch"` comparator: +The `"pixelmatch"` comparator uses [`@blazediff/core`](https://blazediff.dev/docs/core) under the hood. The following options are available when using it: - `allowedMismatchedPixelRatio: number | undefined = undefined` diff --git a/packages/browser/package.json b/packages/browser/package.json index 6e4235375445..87f4baa9cf0c 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -64,10 +64,10 @@ "vitest": "workspace:*" }, "dependencies": { + "@blazediff/core": "1.9.1", "@vitest/mocker": "workspace:*", "@vitest/utils": "workspace:*", "magic-string": "catalog:", - "pixelmatch": "7.1.0", "pngjs": "^7.0.0", "sirv": "catalog:", "tinyrainbow": "catalog:", diff --git a/packages/browser/src/node/commands/screenshotMatcher/comparators/pixelmatch.ts b/packages/browser/src/node/commands/screenshotMatcher/comparators/pixelmatch.ts index 0d9a905c3368..5c8b29c4d06f 100644 --- a/packages/browser/src/node/commands/screenshotMatcher/comparators/pixelmatch.ts +++ b/packages/browser/src/node/commands/screenshotMatcher/comparators/pixelmatch.ts @@ -1,6 +1,6 @@ import type { ScreenshotComparatorRegistry } from '../../../../../context' import type { Comparator } from '../types' -import pm from 'pixelmatch' +import { diff } from '@blazediff/core' const defaultOptions = { allowedMismatchedPixelRatio: undefined, @@ -36,7 +36,7 @@ export const pixelmatch: Comparator ? new Uint8Array(reference.data.length) : undefined - const mismatchedPixels = pm( + const mismatchedPixels = diff( reference.data, actual.data, diffBuffer, diff --git a/packages/mocker/src/automocker.ts b/packages/mocker/src/automocker.ts index c90f3b274859..d363688ab35e 100644 --- a/packages/mocker/src/automocker.ts +++ b/packages/mocker/src/automocker.ts @@ -134,9 +134,28 @@ export function mockObject( continue } + if (options.type === 'autospy' && type === 'Module') { + // Replace with clean object to recursively autospy exported module objects: + // export * as ns from "./ns" + // or + // import * as ns from "./ns" + // export { ns } + const exports = Object.create(null) + Object.defineProperty(exports, Symbol.toStringTag, { + value: 'Module', + configurable: true, + writable: true, + }) + try { + newContainer[property] = exports + } + catch { + continue + } + } // Sometimes this assignment fails for some unknown reason. If it does, // just move along. - if (!define(newContainer, property, isFunction || options.type === 'autospy' ? value : {})) { + else if (!define(newContainer, property, isFunction || options.type === 'autospy' ? value : {})) { continue } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6201105c9113..f626ac513716 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -493,6 +493,9 @@ importers: packages/browser: dependencies: + '@blazediff/core': + specifier: 1.9.1 + version: 1.9.1 '@vitest/mocker': specifier: workspace:* version: link:../mocker @@ -502,9 +505,6 @@ importers: magic-string: specifier: 'catalog:' version: 0.30.21 - pixelmatch: - specifier: 7.1.0 - version: 7.1.0 pngjs: specifier: ^7.0.0 version: 7.0.0 @@ -2271,6 +2271,9 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} + '@blazediff/core@1.9.1': + resolution: {integrity: sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==} + '@bomb.sh/tab@0.0.12': resolution: {integrity: sha512-dYRwg4MqfHR5/BcTy285XOGRhjQFmNpaJBZ0tl2oU+RY595MQ5ApTF6j3OvauPAooHL6cfoOZMySQrOQztT8RQ==} hasBin: true @@ -8322,10 +8325,6 @@ packages: resolution: {integrity: sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==} hasBin: true - pixelmatch@7.1.0: - resolution: {integrity: sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng==} - hasBin: true - pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} @@ -10951,6 +10950,8 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} + '@blazediff/core@1.9.1': {} + '@bomb.sh/tab@0.0.12(cac@6.7.14(patch_hash=a8f0f3517a47ce716ed90c0cfe6ae382ab763b021a664ada2a608477d0621588))(citty@0.1.6)': optionalDependencies: cac: 6.7.14(patch_hash=a8f0f3517a47ce716ed90c0cfe6ae382ab763b021a664ada2a608477d0621588) @@ -17221,10 +17222,6 @@ snapshots: dependencies: pngjs: 6.0.0 - pixelmatch@7.1.0: - dependencies: - pngjs: 7.0.0 - pkg-types@1.3.1: dependencies: confbox: 0.1.8 diff --git a/test/cli/test/stacktraces.test.ts b/test/cli/test/stacktraces.test.ts index 717fcae406e3..c2817e28ac56 100644 --- a/test/cli/test/stacktraces.test.ts +++ b/test/cli/test/stacktraces.test.ts @@ -1,6 +1,7 @@ import { resolve } from 'pathe' import { glob } from 'tinyglobby' import { describe, expect, it } from 'vitest' +import { rolldownVersion } from 'vitest/node' import { runInlineTests, runVitest } from '../../test-utils' // To prevent the warning coming up in snapshots @@ -217,7 +218,87 @@ it('resolves/rejects', async () => { `, }) - expect(stderr).toMatchInlineSnapshot(` + if (rolldownVersion) { + expect(stderr).toMatchInlineSnapshot(` + " + ⎯⎯⎯⎯⎯⎯⎯ Failed Tests 4 ⎯⎯⎯⎯⎯⎯⎯ + + FAIL repro.test.ts > resolves: resolved promise with mismatched value + AssertionError: expected 3 to be 4 // Object.is equality + + - Expected + + Received + + - 4 + + 3 + + ❯ repro.test.ts:5:41 + 3| + 4| test('resolves: resolved promise with mismatched value', async (… + 5| await expect(Promise.resolve(3)).resolves.toBe(4) + | ^ + 6| }) + 7| + + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/4]⎯ + + FAIL repro.test.ts > rejects: rejected promise with mismatched value + AssertionError: expected 3 to be 4 // Object.is equality + + - Expected + + Received + + - 4 + + 3 + + ❯ repro.test.ts:9:40 + 7| + 8| test('rejects: rejected promise with mismatched value', async ()… + 9| await expect(Promise.reject(3)).rejects.toBe(4) + | ^ + 10| }) + 11| + + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/4]⎯ + + FAIL repro.test.ts > rejects: resolves when rejection expected + AssertionError: promise resolved "3" instead of rejecting + + - Expected: + Error { + "message": "rejected promise", + } + + + Received: + 3 + + ❯ repro.test.ts:13:41 + 11| + 12| test('rejects: resolves when rejection expected', async () => { + 13| await expect(Promise.resolve(3)).rejects.toBe(4) + | ^ + 14| }) + 15| + + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/4]⎯ + + FAIL repro.test.ts > resolves: rejects when resolve expected + AssertionError: promise rejected "3" instead of resolving + ❯ repro.test.ts:17:40 + 15| + 16| test('resolves: rejects when resolve expected', async () => { + 17| await expect(Promise.reject(3)).resolves.toBe(4) + | ^ + 18| }) + 19| + + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[4/4]⎯ + + " + `) + } + else { + expect(stderr).toMatchInlineSnapshot(` " ⎯⎯⎯⎯⎯⎯⎯ Failed Tests 4 ⎯⎯⎯⎯⎯⎯⎯ @@ -294,6 +375,7 @@ it('resolves/rejects', async () => { " `) + } expect(errorTree()).toMatchInlineSnapshot(` { "repro.test.ts": { diff --git a/test/core/src/mocks/autospying-namespace/index.ts b/test/core/src/mocks/autospying-namespace/index.ts new file mode 100644 index 000000000000..f52813cf63cf --- /dev/null +++ b/test/core/src/mocks/autospying-namespace/index.ts @@ -0,0 +1 @@ +export * as NamespaceTarget from './namespaceTarget.js' diff --git a/test/core/src/mocks/autospying-namespace/namespaceTarget.ts b/test/core/src/mocks/autospying-namespace/namespaceTarget.ts new file mode 100644 index 000000000000..510523429bba --- /dev/null +++ b/test/core/src/mocks/autospying-namespace/namespaceTarget.ts @@ -0,0 +1 @@ +export const computeSquare = (n: number) => n * n diff --git a/test/core/test/mocking/autospying.test.ts b/test/core/test/mocking/autospying.test.ts index 8760fdbacfd7..f4999dde98c7 100644 --- a/test/core/test/mocking/autospying.test.ts +++ b/test/core/test/mocking/autospying.test.ts @@ -1,10 +1,12 @@ import axios from 'axios' import { expect, test, vi } from 'vitest' import { getAuthToken } from '../../src/env' +import * as NamespaceModule from '../../src/mocks/autospying-namespace/index.js' vi.mock(import('../../src/env'), { spy: true }) vi.mock('axios', { spy: true }) +vi.mock('../../src/mocks/autospying-namespace/index.js', { spy: true }) test('getAuthToken is spied', async () => { import.meta.env.AUTH_TOKEN = '123' @@ -23,3 +25,9 @@ test('package in __mocks__ has lower priority', async () => { expect(axios.isAxiosError(new Error('test'))).toBe(false) expect(axios.isAxiosError).toHaveBeenCalled() }) + +test('spies on namespace re-exports', async () => { + expect(vi.isMockFunction(NamespaceModule.NamespaceTarget.computeSquare)).toBe(true) + expect(NamespaceModule.NamespaceTarget.computeSquare(5)).toBe(25) + expect(NamespaceModule.NamespaceTarget.computeSquare).toHaveBeenCalledTimes(1) +})