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 <jasnell@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: Erick Wendel <erick.workspace@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
This commit is contained in:
Marco Ippolito 2024-09-19 15:36:52 +02:00 committed by GitHub
parent be4babb3c2
commit bb405210c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 55 additions and 6 deletions

View File

@ -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);

View File

@ -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}"`)});

View File

@ -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,
);

View File

@ -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);
});

View File

@ -0,0 +1,5 @@
const logger = (): void => { };
module.exports = {
logger
}

View File

@ -0,0 +1 @@
export const logger = (): void => { };

View File

@ -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);
});