diff --git a/.changeset/sixty-maps-call.md b/.changeset/sixty-maps-call.md new file mode 100644 index 000000000..3005c2a9d --- /dev/null +++ b/.changeset/sixty-maps-call.md @@ -0,0 +1,7 @@ +--- +'@web/test-runner-module-mocking': patch +--- + +Add docs on plugin import order to avoid conflict with other plugins ++ refactoring ++ using sinon dependency for new test using stub diff --git a/package-lock.json b/package-lock.json index 936c3cd72..71f8acb00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5338,6 +5338,55 @@ "node": ">=6" } }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://pkgs.dev.azure.com/IngEurCDaaS01/IngOne/_packaging/central-npm-feed/npm/registry/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha1-ECk1fkTKkBphVYX20nc428iQhM0=", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "11.2.2", + "resolved": "https://pkgs.dev.azure.com/IngEurCDaaS01/IngOne/_packaging/central-npm-feed/npm/registry/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha1-UAY8w1dPSie9hFMYCgQXHIXMlpk=", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "8.0.0", + "resolved": "https://pkgs.dev.azure.com/IngEurCDaaS01/IngOne/_packaging/central-npm-feed/npm/registry/@sinonjs/samsam/-/samsam-8.0.0.tgz", + "integrity": "sha1-DUiMke+z+hRC4mq+qBdZ38i1rGA=", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://pkgs.dev.azure.com/IngEurCDaaS01/IngOne/_packaging/central-npm-feed/npm/registry/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha1-/UylsGNVQwfoMntFZL1W07c5JKM=", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://pkgs.dev.azure.com/IngEurCDaaS01/IngOne/_packaging/central-npm-feed/npm/registry/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha1-WYGo2xi1a6OO8O+32ZWxKqe1GRg=", + "dev": true, + "license": "(Unlicense OR Apache-2.0)" + }, "node_modules/@socket.io/component-emitter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", @@ -10602,9 +10651,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001516", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001516.tgz", - "integrity": "sha512-Wmec9pCBY8CWbmI4HsjBeQLqDTqV91nFVR83DnZpYyRnPI1wePDsTg0bGLPC5VU/3OIZV1fmxEea1b+tFKe86g==", + "version": "1.0.30001587", + "resolved": "https://pkgs.dev.azure.com/IngEurCDaaS01/IngOne/_packaging/central-npm-feed/npm/registry/caniuse-lite/-/caniuse-lite-1.0.30001587.tgz", + "integrity": "sha1-oLzpIBVfpWoYhaacdOEWP8NLSIE=", "funding": [ { "type": "opencollective", @@ -20203,6 +20252,13 @@ "node": ">=0.10.0" } }, + "node_modules/just-extend": { + "version": "6.2.0", + "resolved": "https://pkgs.dev.azure.com/IngEurCDaaS01/IngOne/_packaging/central-npm-feed/npm/registry/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha1-uBar+z1n7oYEgudAFWRnJVgWOUc=", + "dev": true, + "license": "MIT" + }, "node_modules/keygrip": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", @@ -21053,6 +21109,13 @@ "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", "license": "MIT" }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://pkgs.dev.azure.com/IngEurCDaaS01/IngOne/_packaging/central-npm-feed/npm/registry/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.isfinite": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz", @@ -23970,6 +24033,27 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "license": "MIT" }, + "node_modules/nise": { + "version": "5.1.9", + "resolved": "https://pkgs.dev.azure.com/IngEurCDaaS01/IngOne/_packaging/central-npm-feed/npm/registry/nise/-/nise-5.1.9.tgz", + "integrity": "sha1-DLc7XkSZ1zgjGkc82JvYr7thgTk=", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" + } + }, + "node_modules/nise/node_modules/path-to-regexp": { + "version": "6.2.1", + "resolved": "https://pkgs.dev.azure.com/IngEurCDaaS01/IngOne/_packaging/central-npm-feed/npm/registry/path-to-regexp/-/path-to-regexp-6.2.1.tgz", + "integrity": "sha1-1Uk01nmOueXvFOeveWLJRZBpGOU=", + "dev": true, + "license": "MIT" + }, "node_modules/nlcst-is-literal": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nlcst-is-literal/-/nlcst-is-literal-2.1.1.tgz", @@ -30584,6 +30668,48 @@ "integrity": "sha512-QBCnYcpgfoXB7oTyyjgoGV7Dkw7Kz7ZppHvoMmn0UMmHj377sD+gB/VzrDU7ze64sfySZGWS9UKxej99faChfA==", "license": "MIT" }, + "node_modules/sinon": { + "version": "17.0.1", + "resolved": "https://pkgs.dev.azure.com/IngEurCDaaS01/IngOne/_packaging/central-npm-feed/npm/registry/sinon/-/sinon-17.0.1.tgz", + "integrity": "sha1-JrjvcZJhv430P5JZJMzMlnSOQHo=", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.5", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://pkgs.dev.azure.com/IngEurCDaaS01/IngOne/_packaging/central-npm-feed/npm/registry/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha1-lEdx/ZyByBJlxNaUGGDaBrtZR5s=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sinon/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://pkgs.dev.azure.com/IngEurCDaaS01/IngOne/_packaging/central-npm-feed/npm/registry/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha1-G33NyzK4E4gBs+R4umpRyqiWSNo=", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -35747,13 +35873,13 @@ }, "packages/dev-server": { "name": "@web/dev-server", - "version": "0.4.1", + "version": "0.4.2", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.12.11", "@types/command-line-args": "^5.0.0", "@web/config-loader": "^0.3.0", - "@web/dev-server-core": "^0.7.0", + "@web/dev-server-core": "^0.7.1", "@web/dev-server-rollup": "^0.6.1", "camelcase": "^6.2.0", "command-line-args": "^5.1.1", @@ -35780,7 +35906,7 @@ }, "packages/dev-server-core": { "name": "@web/dev-server-core", - "version": "0.7.0", + "version": "0.7.1", "license": "MIT", "dependencies": { "@types/koa": "^2.11.6", @@ -36348,7 +36474,7 @@ }, "packages/mocks": { "name": "@web/mocks", - "version": "1.1.0", + "version": "1.1.1", "license": "MIT", "dependencies": { "@storybook/manager-api": "^7.0.0", @@ -36738,7 +36864,7 @@ }, "packages/storybook-builder": { "name": "@web/storybook-builder", - "version": "0.1.5", + "version": "0.1.6", "license": "MIT", "dependencies": { "@chialab/esbuild-plugin-commonjs": "^0.17.2", @@ -37225,7 +37351,8 @@ }, "devDependencies": { "@web/test-runner-chrome": "^0.15.0", - "@web/test-runner-core": "^0.13.0" + "@web/test-runner-core": "^0.13.0", + "sinon": "^17.0.1" }, "engines": { "node": ">=18.0.0" diff --git a/packages/test-runner-module-mocking/README.md b/packages/test-runner-module-mocking/README.md index 347fb415b..9ad6f5b90 100644 --- a/packages/test-runner-module-mocking/README.md +++ b/packages/test-runner-module-mocking/README.md @@ -38,6 +38,8 @@ export default { }; ``` +Take care of the plugins order, some plugins can modify path of imported modules and moduleMockingPlugin may be not able to retreive them correctly, so it's advise to use it first in the list. + ### Simple test scenario ```js diff --git a/packages/test-runner-module-mocking/package.json b/packages/test-runner-module-mocking/package.json index ada4d4e0b..3029f55d8 100644 --- a/packages/test-runner-module-mocking/package.json +++ b/packages/test-runner-module-mocking/package.json @@ -53,6 +53,7 @@ }, "devDependencies": { "@web/test-runner-chrome": "^0.15.0", - "@web/test-runner-core": "^0.13.0" + "@web/test-runner-core": "^0.13.0", + "sinon": "^17.0.1" } } diff --git a/packages/test-runner-module-mocking/src/createResolveImport.ts b/packages/test-runner-module-mocking/src/createResolveImport.ts index fb41294c6..209338d00 100644 --- a/packages/test-runner-module-mocking/src/createResolveImport.ts +++ b/packages/test-runner-module-mocking/src/createResolveImport.ts @@ -15,7 +15,7 @@ export type ResolveImport = ( ) => ResolveResult | Promise; /** - * TODO: check if `resolveImport()` can be provied by `@web/dev-server-core`'s API + * TODO: check if `resolveImport()` can be provided by `@web/dev-server-core`'s API * @param args start param args * @param thisPlugin plugin to exclude */ diff --git a/packages/test-runner-module-mocking/src/moduleMockingPlugin.ts b/packages/test-runner-module-mocking/src/moduleMockingPlugin.ts index c826401e9..0a13f654d 100644 --- a/packages/test-runner-module-mocking/src/moduleMockingPlugin.ts +++ b/packages/test-runner-module-mocking/src/moduleMockingPlugin.ts @@ -37,15 +37,37 @@ export function moduleMockingPlugin(): Plugin { const namedExports = exports.map(e => e.n).filter(n => n !== 'default'); const hasDefaultExport = exports.some(e => e.n === 'default'); - body = ` + body = generateInterceptedModuleBody(body, url, namedExports, hasDefaultExport); + } + } catch (error) { + // Server side errors might contain ANSI color escape sequences. + // These sequences are not readable in a browser's console, so we strip them. + const errorMessage = stripColor((error as Error).message).replace(/'/g, "\\'"); + body = `export const __wtr_error__ = '${errorMessage}';`; + } + + return body ? { body, type: 'text/javascript' } : undefined; + }, + + resolveImport({ source, context }) { + if (context.path === '/__intercept-module__') { + absolutePaths.push(source); + } else if (absolutePaths.includes(source)) { + return `${source}?intercept-module`; + } + return undefined; + }, + }; + + function generateInterceptedModuleBody(body: any, url: URL, namedExports: string[], hasDefaultExport: boolean) { + body = ` import * as original from '${url.pathname}'; const newOriginal = {...original}; ${namedExports.map(x => `export let ${x} = newOriginal['${x}'];`).join('\n')} - ${ - hasDefaultExport - ? ` + ${hasDefaultExport + ? ` function computeDefault() { if(typeof newOriginal.default === 'function'){ return (...args) => newOriginal.default.call(undefined, ...args); @@ -55,8 +77,7 @@ export function moduleMockingPlugin(): Plugin { export default computeDefault() ` - : '' - } + : ''} export const __wtr_intercepted_module__ = new Proxy(newOriginal, { set: function(obj, prop, value) { @@ -69,24 +90,6 @@ export function moduleMockingPlugin(): Plugin { }, }); `; - } - } catch (error) { - // Server side errors might contain ANSI color escape sequences. - // These sequences are not readable in a browser's console, so we strip them. - const errorMessage = stripColor((error as Error).message).replace(/'/g, "\\'"); - body = `export const __wtr_error__ = '${errorMessage}';`; - } - - return body ? { body, type: 'text/javascript' } : undefined; - }, - - resolveImport({ source, context }) { - if (context.path === '/__intercept-module__') { - absolutePaths.push(source); - } else if (absolutePaths.includes(source)) { - return `${source}?intercept-module`; - } - return undefined; - }, - }; + return body; + } } diff --git a/packages/test-runner-module-mocking/test/fixtures/server-relative-with-stub/fixture/src/flow/libs/time-library.js b/packages/test-runner-module-mocking/test/fixtures/server-relative-with-stub/fixture/src/flow/libs/time-library.js new file mode 100644 index 000000000..636846664 --- /dev/null +++ b/packages/test-runner-module-mocking/test/fixtures/server-relative-with-stub/fixture/src/flow/libs/time-library.js @@ -0,0 +1,3 @@ +export function getCurrentHour() { + return 2; +} diff --git a/packages/test-runner-module-mocking/test/fixtures/server-relative-with-stub/fixture/src/flow/step/getTimeOfDay.js b/packages/test-runner-module-mocking/test/fixtures/server-relative-with-stub/fixture/src/flow/step/getTimeOfDay.js new file mode 100644 index 000000000..4301d1d98 --- /dev/null +++ b/packages/test-runner-module-mocking/test/fixtures/server-relative-with-stub/fixture/src/flow/step/getTimeOfDay.js @@ -0,0 +1,9 @@ +import { getCurrentHour } from '../libs/time-library.js'; + +export function getTimeOfDay() { + const hour = getCurrentHour(); + if (hour < 6 || hour > 18) { + return 'night'; + } + return 'day'; +} diff --git a/packages/test-runner-module-mocking/test/fixtures/server-relative-with-stub/test/step/browser-test.js b/packages/test-runner-module-mocking/test/fixtures/server-relative-with-stub/test/step/browser-test.js new file mode 100644 index 000000000..4e5257e8a --- /dev/null +++ b/packages/test-runner-module-mocking/test/fixtures/server-relative-with-stub/test/step/browser-test.js @@ -0,0 +1,33 @@ +import { expect } from '../../../chai.js'; +import sinon from 'sinon' +import { importMockable } from '../../../../../browser/index.js'; + +const path = new URL(import.meta.resolve('../../fixture/src/flow/libs/time-library.js')).pathname; +const timeLibrary = await importMockable(path); +const { getTimeOfDay } = await import('../../fixture/src/flow/step/getTimeOfDay.js'); + +let backup; +it('can run a module normally', () => { + backup = timeLibrary.getCurrentHour; + const timeOfDay = getTimeOfDay(); + expect(timeOfDay).to.equal('night'); +}); + +it('can intercept a module', () => { + timeLibrary.getCurrentHour = () => 12; + const timeOfDay = getTimeOfDay(); + expect(timeOfDay).to.equal('day'); +}); + +it('can restore an intercepted module', () => { + timeLibrary.getCurrentHour = backup; + const timeOfDay = getTimeOfDay(); + expect(timeOfDay).to.equal('night'); +}); + +it('can have stub applied on intercepted module', () => { + const stub = sinon.stub(timeLibrary, 'getCurrentHour').returns(15) + const timeOfDay = getTimeOfDay(); + expect(timeOfDay).to.equal('day'); + expect(stub.calledOnce).to.be.true; +}); \ No newline at end of file diff --git a/packages/test-runner-module-mocking/test/moduleMockingPlugin.test.ts b/packages/test-runner-module-mocking/test/moduleMockingPlugin.test.ts index 2b365dcc7..a0c19b276 100644 --- a/packages/test-runner-module-mocking/test/moduleMockingPlugin.test.ts +++ b/packages/test-runner-module-mocking/test/moduleMockingPlugin.test.ts @@ -11,7 +11,7 @@ const dirname = fileURLToPath(new URL('.', import.meta.url)); describe('moduleMockingPlugin', function test() { this.timeout(20000); - + it('can intercept server relative modules', async () => { await runTests({ files: [path.join(dirname, 'fixtures', 'server-relative', 'browser-test.js')], @@ -20,6 +20,14 @@ describe('moduleMockingPlugin', function test() { }); }); + it('can intercept server relative modules with stub', async () => { + await runTests({ + files: [path.join(dirname, 'fixtures', 'server-relative-with-stub', 'test', 'step', 'browser-test.js')], + browsers: [chromeLauncher()], + plugins: [moduleMockingPlugin(), nodeResolvePlugin('', false, {})], + }); + }); + it('can intercept bare modules', async () => { const rootDir = path.resolve(dirname, 'fixtures', 'bare', 'fixture'); // Define the bare module as duped to force nodeResolve to use the passed rootDir instead of the cwd