From bb405210c58919a61d8b2dd9577411784bcbed85 Mon Sep 17 00:00:00 2001 From: Marco Ippolito Date: Thu, 19 Sep 2024 15:36:52 +0200 Subject: [PATCH] test_runner: support typescript module mocking PR-URL: https://github.com/nodejs/node/pull/54878 Fixes: https://github.com/nodejs/node/issues/54428 Reviewed-By: James M Snell Reviewed-By: Colin Ihrig Reviewed-By: Moshe Atlow Reviewed-By: Erick Wendel Reviewed-By: Benjamin Gruenbaum Reviewed-By: Stephen Belanger Reviewed-By: Paolo Insogna --- lib/internal/modules/esm/translators.js | 4 ++-- lib/internal/test_runner/mock/loader.js | 2 +- lib/internal/test_runner/mock/mock.js | 7 ++++-- test/es-module/test-typescript.mjs | 19 ++++++++++++++- .../fixtures/typescript/ts/commonjs-logger.ts | 5 ++++ test/fixtures/typescript/ts/module-logger.ts | 1 + .../typescript/ts/test-mock-module.ts | 23 +++++++++++++++++++ 7 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 test/fixtures/typescript/ts/commonjs-logger.ts create mode 100644 test/fixtures/typescript/ts/module-logger.ts create mode 100644 test/fixtures/typescript/ts/test-mock-module.ts diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index 6a5804e656a..44840ad5f38 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -459,7 +459,7 @@ translators.set('wasm', async function(url, source) { // Strategy for loading a commonjs TypeScript module translators.set('commonjs-typescript', function(url, source) { emitExperimentalWarning('Type Stripping'); - assertBufferSource(source, false, 'load'); + assertBufferSource(source, true, 'load'); const code = stripTypeScriptTypes(stringify(source), url); debug(`Translating TypeScript ${url}`); return FunctionPrototypeCall(translators.get('commonjs'), this, url, code, false); @@ -468,7 +468,7 @@ translators.set('commonjs-typescript', function(url, source) { // Strategy for loading an esm TypeScript module translators.set('module-typescript', function(url, source) { emitExperimentalWarning('Type Stripping'); - assertBufferSource(source, false, 'load'); + assertBufferSource(source, true, 'load'); const code = stripTypeScriptTypes(stringify(source), url); debug(`Translating TypeScript ${url}`); return FunctionPrototypeCall(translators.get('module'), this, url, code, false); diff --git a/lib/internal/test_runner/mock/loader.js b/lib/internal/test_runner/mock/loader.js index 25d9b31d0cb..29d1ef70ebf 100644 --- a/lib/internal/test_runner/mock/loader.js +++ b/lib/internal/test_runner/mock/loader.js @@ -137,7 +137,7 @@ async function load(url, context, nextLoad) { async function createSourceFromMock(mock, format) { // Create mock implementation from provided exports. const { exportNames, hasDefaultExport, url } = mock; - const useESM = format === 'module'; + const useESM = format === 'module' || format === 'module-typescript'; const source = `${testImportSource(useESM)} if (!$__test.mock._mockExports.has('${url}')) { throw new Error(${JSONStringify(`mock exports not found for "${url}"`)}); diff --git a/lib/internal/test_runner/mock/mock.js b/lib/internal/test_runner/mock/mock.js index 5d3bce816aa..99fe1baebb7 100644 --- a/lib/internal/test_runner/mock/mock.js +++ b/lib/internal/test_runner/mock/mock.js @@ -70,7 +70,7 @@ const kMockUnknownMessage = 3; const kWaitTimeout = 5_000; const kBadExportsMessage = 'Cannot create mock because named exports ' + 'cannot be applied to the provided default export.'; -const kSupportedFormats = ['builtin', 'commonjs', 'module']; +const kSupportedFormats = ['builtin', 'commonjs', 'module', 'module-typescript', 'commonjs-typescript']; let sharedModuleState; class MockFunctionContext { @@ -517,7 +517,10 @@ class MockTracker { // Get the file that called this function. We need four stack frames: // vm context -> getStructuredStack() -> this function -> actual caller. - const caller = pathToFileURL(getStructuredStack()[3]?.getFileName()).href; + const filename = getStructuredStack()[3]?.getFileName(); + // If the caller is already a file URL, use it as is. Otherwise, convert it. + const hasFileProtocol = StringPrototypeStartsWith(filename, 'file://'); + const caller = hasFileProtocol ? filename : pathToFileURL(filename).href; const { format, url } = sharedState.moduleLoader.resolveSync( mockSpecifier, caller, null, ); diff --git a/test/es-module/test-typescript.mjs b/test/es-module/test-typescript.mjs index 7982e538f3d..496e42178f4 100644 --- a/test/es-module/test-typescript.mjs +++ b/test/es-module/test-typescript.mjs @@ -1,4 +1,4 @@ -import { skip, spawnPromisified } from '../common/index.mjs'; +import { skip, spawnPromisified, isWindows } from '../common/index.mjs'; import * as fixtures from '../common/fixtures.mjs'; import { match, strictEqual } from 'node:assert'; import { test } from 'node:test'; @@ -336,3 +336,20 @@ test('execute a JavaScript file importing a cjs TypeScript file', async () => { match(result.stdout, /Hello, TypeScript!/); strictEqual(result.code, 0); }); + +// TODO(marco-ippolito) Due to a bug in SWC, the TypeScript loader +// does not work on Windows arm64. This test should be re-enabled +// when https://github.com/nodejs/node/issues/54645 is fixed +test('execute a TypeScript test mocking module', { skip: isWindows && process.arch === 'arm64' }, async () => { + const result = await spawnPromisified(process.execPath, [ + '--test', + '--experimental-test-module-mocks', + '--experimental-strip-types', + '--no-warnings', + fixtures.path('typescript/ts/test-mock-module.ts'), + ]); + strictEqual(result.stderr, ''); + match(result.stdout, /Hello, TypeScript-Module!/); + match(result.stdout, /Hello, TypeScript-CommonJS!/); + strictEqual(result.code, 0); +}); diff --git a/test/fixtures/typescript/ts/commonjs-logger.ts b/test/fixtures/typescript/ts/commonjs-logger.ts new file mode 100644 index 00000000000..91b171231e5 --- /dev/null +++ b/test/fixtures/typescript/ts/commonjs-logger.ts @@ -0,0 +1,5 @@ +const logger = (): void => { }; + +module.exports = { + logger +} diff --git a/test/fixtures/typescript/ts/module-logger.ts b/test/fixtures/typescript/ts/module-logger.ts new file mode 100644 index 00000000000..50aecfdf350 --- /dev/null +++ b/test/fixtures/typescript/ts/module-logger.ts @@ -0,0 +1 @@ +export const logger = (): void => { }; diff --git a/test/fixtures/typescript/ts/test-mock-module.ts b/test/fixtures/typescript/ts/test-mock-module.ts new file mode 100644 index 00000000000..adc5f5ce57e --- /dev/null +++ b/test/fixtures/typescript/ts/test-mock-module.ts @@ -0,0 +1,23 @@ +import { before, mock, test } from 'node:test'; +import { strictEqual } from 'node:assert'; + +const logger = mock.fn((s) => console.log(`Hello, ${s}!`)); + +before(async () => { + mock.module('./module-logger.ts', { + namedExports: { logger } + }); + mock.module('./commonjs-logger.ts', { + namedExports: { logger } + }); +}); + +test('logger', async () => { + const { logger: mockedModule } = await import('./module-logger.ts'); + mockedModule('TypeScript-Module'); + strictEqual(logger.mock.callCount(), 1); + + const { logger: mockedCommonjs } = await import('./commonjs-logger.ts'); + mockedCommonjs('TypeScript-CommonJS'); + strictEqual(logger.mock.callCount(), 2); +});