diff --git a/src/test-reader/mocha-reader/index.js b/src/test-reader/mocha-reader/index.js index 3a46c7909..6e5f10c31 100644 --- a/src/test-reader/mocha-reader/index.js +++ b/src/test-reader/mocha-reader/index.js @@ -8,6 +8,7 @@ const { TreeBuilderDecorator } = require("./tree-builder-decorator"); const { TestReaderEvents } = require("../../events"); const { MasterEvents } = require("../../events"); const { getMethodsByInterface } = require("./utils"); +const { enableSourceMaps } = require("../../utils/typescript"); async function readFiles(files, { esmDecorator, config, eventBus, runnableOpts }) { const mocha = new Mocha(config); @@ -104,6 +105,8 @@ function addLocationToRunnables(inBus, config, runnableOpts) { return; } + enableSourceMaps(); + const sourceMapSupport = tryToRequireSourceMapSupport(); const { suiteMethods, testMethods } = getMethodsByInterface(config.ui); diff --git a/src/utils/typescript.ts b/src/utils/typescript.ts index 3575ade3d..ab0a7ae66 100644 --- a/src/utils/typescript.ts +++ b/src/utils/typescript.ts @@ -4,7 +4,7 @@ import * as logger from "./logger"; const TESTPLANE_TRANSFORM_HOOK = Symbol.for("testplane.transform.hook"); -const TRANSFORM_EXTENSIONS = [".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".mts", ".cts"]; +const TRANSFORM_CODE_EXTENSIONS = [".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".mts", ".cts"]; const ASSET_EXTENSIONS = [ ".css", ".scss", @@ -20,6 +20,10 @@ const ASSET_EXTENSIONS = [ ".woff2", ]; +type ProcessWithTransformHook = typeof process & { + [TESTPLANE_TRANSFORM_HOOK]?: { revert: () => void; enableSourceMaps: () => void }; +}; + let transformFunc: null | ((code: string, sourceFile: string, sourceMaps: boolean) => string) = null; export const transformCode = ( @@ -83,37 +87,62 @@ export const transformCode = ( }; export const registerTransformHook = (isSilent: boolean = false): void => { - const processWithEsbuildSymbol = process as typeof process & { - [TESTPLANE_TRANSFORM_HOOK]?: { revert: () => void }; - }; + const processWithTranspileSymbol = process as ProcessWithTransformHook; - if (processWithEsbuildSymbol[TESTPLANE_TRANSFORM_HOOK] || process.env.TS_ENABLE === "false") { + if (processWithTranspileSymbol[TESTPLANE_TRANSFORM_HOOK] || process.env.TS_ENABLE === "false") { return; } try { - const revertTransformHook = addHook( - (code, filename) => transformCode(code, { sourceFile: filename, sourceMaps: false, isSilent }), - { - exts: TRANSFORM_EXTENSIONS, - matcher: filename => !filename.includes("node_modules"), - ignoreNodeModules: false, - }, - ); + const mkTransformCodeHook = + (sourceMaps = false): Parameters[0] => + (code, sourceFile) => + transformCode(code, { sourceFile, sourceMaps, isSilent }); + + const transformCodeOptions: Parameters[1] = { + exts: TRANSFORM_CODE_EXTENSIONS, + ignoreNodeModules: true, + }; + + let areSourceMapsEnabled = false; + + let revertTransformHook = addHook(mkTransformCodeHook(), transformCodeOptions); const revertAssetHook = addHook(() => "module.exports = {};", { exts: ASSET_EXTENSIONS, ignoreNodeModules: false, }); + const enableSourceMaps = (): void => { + if (areSourceMapsEnabled) { + return; + } + + areSourceMapsEnabled = true; + + revertTransformHook(); + + revertTransformHook = addHook(mkTransformCodeHook(true), transformCodeOptions); + }; + const revertAll = (): void => { revertTransformHook(); revertAssetHook(); - delete processWithEsbuildSymbol[TESTPLANE_TRANSFORM_HOOK]; + delete processWithTranspileSymbol[TESTPLANE_TRANSFORM_HOOK]; }; - processWithEsbuildSymbol[TESTPLANE_TRANSFORM_HOOK] = { revert: revertAll }; + processWithTranspileSymbol[TESTPLANE_TRANSFORM_HOOK] = { revert: revertAll, enableSourceMaps }; } catch (err) { logger.warn(`testplane: an error occurred while trying to register transform hook.`, err); } }; + +export const enableSourceMaps = (): void => { + const processWithTranspileSymbol = process as ProcessWithTransformHook; + + if (!processWithTranspileSymbol[TESTPLANE_TRANSFORM_HOOK]) { + return; + } + + processWithTranspileSymbol[TESTPLANE_TRANSFORM_HOOK].enableSourceMaps(); +}; diff --git a/test/src/test-reader/mocha-reader/index.js b/test/src/test-reader/mocha-reader/index.js index e1cf28ba7..b17186047 100644 --- a/test/src/test-reader/mocha-reader/index.js +++ b/test/src/test-reader/mocha-reader/index.js @@ -17,6 +17,7 @@ describe("test-reader/mocha-reader", () => { let MochaConstructorStub; let SourceMapSupportStub; let getMethodsByInterfaceStub; + let enableSourceMapsStub; let readFiles; const mkMochaSuiteStub_ = () => { @@ -47,11 +48,13 @@ describe("test-reader/mocha-reader", () => { install: sinon.stub(), }; getMethodsByInterfaceStub = sinon.stub().returns({ suiteMethods: [], testMethods: [] }); + enableSourceMapsStub = sinon.stub(); readFiles = proxyquire("src/test-reader/mocha-reader", { mocha: MochaConstructorStub, "@cspotcode/source-map-support": SourceMapSupportStub, "./utils": { getMethodsByInterface: getMethodsByInterfaceStub }, + "../../utils/typescript": { enableSourceMaps: enableSourceMapsStub }, }).readFiles; sandbox.stub(MochaEventBus, "create").returns(Object.create(MochaEventBus.prototype)); @@ -262,6 +265,13 @@ describe("test-reader/mocha-reader", () => { assert.doesNotThrow(() => globalCtx.describe()); }); + it("should enable testplane source maps before installing 'source-map-support'", async () => { + await readFiles_({ config: { ui: "bdd" }, runnableOpts: { saveLocations: true } }); + + assert.calledOnce(enableSourceMapsStub); + assert.callOrder(enableSourceMapsStub, SourceMapSupportStub.install); + }); + it("should set 'hookRequire' option on install source-map-support", async () => { await readFiles_({ config: { ui: "bdd" }, runnableOpts: { saveLocations: true } }); diff --git a/test/src/utils/typescript.ts b/test/src/utils/typescript.ts index 2e4b684fa..1355b3002 100644 --- a/test/src/utils/typescript.ts +++ b/test/src/utils/typescript.ts @@ -7,10 +7,12 @@ describe("utils/typescript", () => { let ts: typeof import("src/utils/typescript"); let addHookStub: SinonStub; + let revertHookStub: SinonStub; const TESTPLANE_TRANSFORM_HOOK = Symbol.for("testplane.transform.hook"); beforeEach(() => { - addHookStub = sinon.stub(); + revertHookStub = sinon.stub(); + addHookStub = sinon.stub().returns(revertHookStub); ts = proxyquire.noCallThru().load("src/utils/typescript", { pirates: { addHook: addHookStub, @@ -27,7 +29,7 @@ describe("utils/typescript", () => { it("should add pirates hook", () => { ts.registerTransformHook(); - assert.calledOnce(addHookStub); + assert.calledTwice(addHookStub); }); it("should not call register if typescript was already installed", () => { @@ -48,4 +50,22 @@ describe("utils/typescript", () => { process.env.TS_ENABLE = "undefined"; }); }); + + describe("enableSourceMaps", () => { + it("should not do anything if transform hook is not registered", () => { + ts.enableSourceMaps(); + + assert.notCalled(addHookStub); + }); + + it("should re-register transform hook with source maps", () => { + ts.registerTransformHook(); + const addHookPrevCallCount = addHookStub.callCount; + + ts.enableSourceMaps(); + + assert.calledOnce(revertHookStub); + assert.equal(addHookStub.callCount, addHookPrevCallCount + 1); + }); + }); });