From b5737fc535701caff9b197b3cba6dd4c832e745f Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 2 Jul 2019 16:29:09 -0700 Subject: [PATCH] Refactor tests so its easy to edit and reason about them --- src/harness/virtualFileSystemWithWatch.ts | 4 +- src/testRunner/unittests/tsserver/helpers.ts | 4 +- .../unittests/tsserver/projectReferences.ts | 1321 +++++++++++------ 3 files changed, 835 insertions(+), 494 deletions(-) diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index f49f15cadc2..9e615a02638 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -196,8 +196,8 @@ interface Array {}` } } - export function checkWatchedFiles(host: TestServerHost, expectedFiles: string[]) { - checkMapKeys("watchedFiles", host.watchedFiles, expectedFiles); + export function checkWatchedFiles(host: TestServerHost, expectedFiles: string[], additionalInfo?: string) { + checkMapKeys(`watchedFiles:: ${additionalInfo || ""}::`, host.watchedFiles, expectedFiles); } export function checkWatchedFilesDetailed(host: TestServerHost, expectedFiles: ReadonlyMap): void; diff --git a/src/testRunner/unittests/tsserver/helpers.ts b/src/testRunner/unittests/tsserver/helpers.ts index 1c2383ab004..1c6bd4e9f59 100644 --- a/src/testRunner/unittests/tsserver/helpers.ts +++ b/src/testRunner/unittests/tsserver/helpers.ts @@ -488,8 +488,8 @@ namespace ts.projectSystem { checkArray("Open files", arrayFrom(projectService.openFiles.keys(), path => projectService.getScriptInfoForPath(path as Path)!.fileName), expectedFiles.map(file => file.path)); } - export function checkScriptInfos(projectService: server.ProjectService, expectedFiles: ReadonlyArray) { - checkArray("ScriptInfos files", arrayFrom(projectService.filenameToScriptInfo.values(), info => info.fileName), expectedFiles); + export function checkScriptInfos(projectService: server.ProjectService, expectedFiles: ReadonlyArray, additionInfo?: string) { + checkArray(`ScriptInfos files: ${additionInfo || ""}`, arrayFrom(projectService.filenameToScriptInfo.values(), info => info.fileName), expectedFiles); } export function protocolLocationFromSubstring(str: string, substring: string): protocol.Location { diff --git a/src/testRunner/unittests/tsserver/projectReferences.ts b/src/testRunner/unittests/tsserver/projectReferences.ts index 1eb7e8ecd18..c5712df2e1a 100644 --- a/src/testRunner/unittests/tsserver/projectReferences.ts +++ b/src/testRunner/unittests/tsserver/projectReferences.ts @@ -150,66 +150,17 @@ fn5(); const files = [dependencyTs, dependencyConfig, mainTs, mainConfig, libFile, randomFile, randomConfig]; - function verifyScriptInfos(session: TestSession, host: TestServerHost, openInfos: ReadonlyArray, closedInfos: ReadonlyArray, otherWatchedFiles: ReadonlyArray) { - checkScriptInfos(session.getProjectService(), openInfos.concat(closedInfos)); - checkWatchedFiles(host, closedInfos.concat(otherWatchedFiles).map(f => f.toLowerCase())); + function verifyScriptInfos(session: TestSession, host: TestServerHost, openInfos: ReadonlyArray, closedInfos: ReadonlyArray, otherWatchedFiles: ReadonlyArray, additionalInfo: string) { + checkScriptInfos(session.getProjectService(), openInfos.concat(closedInfos), additionalInfo); + checkWatchedFiles(host, closedInfos.concat(otherWatchedFiles).map(f => f.toLowerCase()), additionalInfo); } - function verifyInfosWithRandom(session: TestSession, host: TestServerHost, openInfos: ReadonlyArray, closedInfos: ReadonlyArray, otherWatchedFiles: ReadonlyArray) { - verifyScriptInfos(session, host, openInfos.concat(randomFile.path), closedInfos, otherWatchedFiles.concat(randomConfig.path)); + function verifyInfosWithRandom(session: TestSession, host: TestServerHost, openInfos: ReadonlyArray, closedInfos: ReadonlyArray, otherWatchedFiles: ReadonlyArray, reqName: string) { + verifyScriptInfos(session, host, openInfos.concat(randomFile.path), closedInfos, otherWatchedFiles.concat(randomConfig.path), reqName); } function verifyOnlyRandomInfos(session: TestSession, host: TestServerHost) { - verifyScriptInfos(session, host, [randomFile.path], [libFile.path], [randomConfig.path]); - } - - // Returns request and expected Response, expected response when no map file - interface SessionAction { - reqName: string; - request: Partial; - expectedResponse: Response; - expectedResponseNoMap?: Response; - expectedResponseNoDts?: Response; - requestDependencyChange?: Partial; - expectedResponseDependencyChange: Response; - } - function gotoDefintinionFromMainTs(fn: number): SessionAction { - const textSpan = usageSpan(fn); - const definition: protocol.FileSpan = { file: dependencyTs.path, ...declarationSpan(fn) }; - const declareSpaceLength = "declare ".length; - return { - reqName: "goToDef", - request: { - command: protocol.CommandTypes.DefinitionAndBoundSpan, - arguments: { file: mainTs.path, ...textSpan.start } - }, - expectedResponse: { - // To dependency - definitions: [definition], - textSpan - }, - expectedResponseNoMap: { - // To the dts - definitions: [{ - file: dtsPath, - start: { line: fn, offset: definition.start.offset + declareSpaceLength }, - end: { line: fn, offset: definition.end.offset + declareSpaceLength }, - contextStart: { line: fn, offset: 1 }, - contextEnd: { line: fn, offset: 37 } - }], - textSpan - }, - expectedResponseNoDts: { - // To import declaration - definitions: [{ file: mainTs.path, ...importSpan(fn) }], - textSpan - }, - expectedResponseDependencyChange: { - // Definition on fn + 1 line - definitions: [{ file: dependencyTs.path, ...declarationSpan(fn + 1) }], - textSpan - } - }; + verifyScriptInfos(session, host, [randomFile.path], [libFile.path], [randomConfig.path], "Random"); } function declarationSpan(fn: number): protocol.TextSpanWithContext { @@ -232,11 +183,93 @@ fn5(); return { start: { line: fn + 8, offset: 1 }, end: { line: fn + 8, offset: 4 } }; } - function renameFromDependencyTs(fn: number): SessionAction { + function goToDefFromMainTs(fn: number): Action { + const textSpan = usageSpan(fn); + const definition: protocol.FileSpan = { file: dependencyTs.path, ...declarationSpan(fn) }; + return { + reqName: "goToDef", + request: { + command: protocol.CommandTypes.DefinitionAndBoundSpan, + arguments: { file: mainTs.path, ...textSpan.start } + }, + expectedResponse: { + // To dependency + definitions: [definition], + textSpan + } + }; + } + + function goToDefFromMainTsWithNoMap(fn: number): Action { + const textSpan = usageSpan(fn); + const definition = declarationSpan(fn); + const declareSpaceLength = "declare ".length; + return { + reqName: "goToDef", + request: { + command: protocol.CommandTypes.DefinitionAndBoundSpan, + arguments: { file: mainTs.path, ...textSpan.start } + }, + expectedResponse: { + // To the dts + definitions: [{ + file: dtsPath, + start: { line: fn, offset: definition.start.offset + declareSpaceLength }, + end: { line: fn, offset: definition.end.offset + declareSpaceLength }, + contextStart: { line: fn, offset: 1 }, + contextEnd: { line: fn, offset: 37 } + }], + textSpan + } + }; + } + + function goToDefFromMainTsWithNoDts(fn: number): Action { + const textSpan = usageSpan(fn); + return { + reqName: "goToDef", + request: { + command: protocol.CommandTypes.DefinitionAndBoundSpan, + arguments: { file: mainTs.path, ...textSpan.start } + }, + expectedResponse: { + // To import declaration + definitions: [{ file: mainTs.path, ...importSpan(fn) }], + textSpan + } + }; + } + + function goToDefFromMainTsWithDependencyChange(fn: number): Action { + const textSpan = usageSpan(fn); + return { + reqName: "goToDef", + request: { + command: protocol.CommandTypes.DefinitionAndBoundSpan, + arguments: { file: mainTs.path, ...textSpan.start } + }, + expectedResponse: { + // Definition on fn + 1 line + definitions: [{ file: dependencyTs.path, ...declarationSpan(fn + 1) }], + textSpan + } + }; + } + + function goToDefFromMainTsProjectInfoVerifier(withRefs: boolean): ProjectInfoVerifier { + return { + openFile: mainTs, + openFileLastLine: 14, + configFile: mainConfig, + expectedProjectActualFiles: withRefs ? + [mainTs.path, libFile.path, mainConfig.path, dependencyTs.path] : + [mainTs.path, libFile.path, mainConfig.path, dtsPath] + }; + } + + function renameFromDependencyTs(fn: number): Action { const defSpan = declarationSpan(fn); const { contextStart: _, contextEnd: _1, ...triggerSpan } = defSpan; - const defSpanPlusOne = declarationSpan(fn + 1); - const { contextStart: _2, contextEnd: _3, ...triggerSpanPlusOne } = defSpanPlusOne; return { reqName: "rename", request: { @@ -256,30 +289,37 @@ fn5(); locs: [ { file: dependencyTs.path, locs: [defSpan] } ] - }, - requestDependencyChange: { - command: protocol.CommandTypes.Rename, - arguments: { file: dependencyTs.path, ...triggerSpanPlusOne.start } - }, - expectedResponseDependencyChange: { - info: { - canRename: true, - fileToRename: undefined, - displayName: `fn${fn}`, - fullDisplayName: `"${dependecyLocation}/FnS".fn${fn}`, - kind: ScriptElementKind.functionElement, - kindModifiers: "export", - triggerSpan: triggerSpanPlusOne - }, - locs: [ - { file: dependencyTs.path, locs: [defSpanPlusOne] } - ] } }; } - function renameFromDependencyTsWithBothProjectsOpen(fn: number): SessionAction { - const { reqName, request, expectedResponse, expectedResponseDependencyChange, requestDependencyChange } = renameFromDependencyTs(fn); + function renameFromDependencyTsWithDependencyChange(fn: number): Action { + const { expectedResponse: { info, locs }, ...rest } = renameFromDependencyTs(fn + 1); + + return { + ...rest, + expectedResponse: { + info: { + ...info as protocol.RenameInfoSuccess, + displayName: `fn${fn}`, + fullDisplayName: `"${dependecyLocation}/FnS".fn${fn}`, + }, + locs + } + }; + } + + function renameFromDependencyTsProjectInfoVerifier(): ProjectInfoVerifier { + return { + openFile: dependencyTs, + openFileLastLine: 6, + configFile: dependencyConfig, + expectedProjectActualFiles: [dependencyTs.path, libFile.path, dependencyConfig.path] + }; + } + + function renameFromDependencyTsWithBothProjectsOpen(fn: number): Action { + const { reqName, request, expectedResponse } = renameFromDependencyTs(fn); const { info, locs } = expectedResponse; return { reqName, @@ -296,15 +336,20 @@ fn5(); ] } ] - }, - // Only dependency result - expectedResponseNoMap: expectedResponse, - expectedResponseNoDts: expectedResponse, - requestDependencyChange, - expectedResponseDependencyChange: { - info: expectedResponseDependencyChange.info, + } + }; + } + + function renameFromDependencyTsWithBothProjectsOpenWithDependencyChange(fn: number): Action { + const { reqName, request, expectedResponse, } = renameFromDependencyTsWithDependencyChange(fn); + const { info, locs } = expectedResponse; + return { + reqName, + request, + expectedResponse: { + info, locs: [ - expectedResponseDependencyChange.locs[0], + locs[0], { file: mainTs.path, locs: [ @@ -317,401 +362,453 @@ fn5(); }; } - // Returns request and expected Response - type SessionActionGetter = (fn: number) => SessionAction; - // Open File, expectedProjectActualFiles, actionGetter, openFileLastLine - interface DocumentPositionMapperVerifier { + interface Action { + reqName: string; + request: Partial; + expectedResponse: Response; + } + interface ActionInfo { + action: (fn: number) => Action; + closedInfos: () => readonly string[]; + otherWatchedFiles: () => readonly string[]; + expectsDts: boolean; + expectsMap: boolean; + } + type ActionKey = keyof ActionInfoVerifier; + type ActionInfoGetterFn = () => ActionInfo; + type ActionInfoGetter = ActionInfoGetterFn | ActionKey; + interface ProjectInfoVerifier { openFile: File; - expectedProjectActualFiles: ReadonlyArray; - actionGetter: SessionActionGetter; openFileLastLine: number; + configFile: File; + expectedProjectActualFiles: readonly string[]; + } + interface ActionInfoVerifier { + main: ActionInfoGetter; + noMap: ActionInfoGetter; + mapFileCreated: ActionInfoGetter; + mapFileDeleted: ActionInfoGetter; + noDts: ActionInfoGetter; + dtsFileCreated: ActionInfoGetter; + dtsFileDeleted: ActionInfoGetter; + dependencyChange: ActionInfoGetter; + noBuild: ActionInfoGetter; + } + interface DocumentPositionMapperVerifier extends ProjectInfoVerifier, ActionInfoVerifier { } - function verifyDocumentPositionMapperUpdates( - mainScenario: string, - verifier: ReadonlyArray, - closedInfos: ReadonlyArray, - withRefs: boolean) { - const openFiles = verifier.map(v => v.openFile); - const expectedProjectActualFiles = verifier.map(v => v.expectedProjectActualFiles); - const openFileLastLines = verifier.map(v => v.openFileLastLine); + interface VerifierAndWithRefs { + withRefs: boolean; + verifier: (withRefs: boolean) => readonly DocumentPositionMapperVerifier[]; + } - const configFiles = openFiles.map(openFile => `${getDirectoryPath(openFile.path)}/tsconfig.json`); - const openInfos = openFiles.map(f => f.path); - // When usage and dependency are used, dependency config is part of closedInfo so ignore - const otherWatchedFiles = withRefs && verifier.length > 1 ? [configFiles[0]] : configFiles; - function openTsFile(onHostCreate?: (host: TestServerHost) => void) { - const host = createHost(files, [mainConfig.path]); - if (!withRefs) { - // Erase project reference - host.writeFile(mainConfig.path, JSON.stringify({ - compilerOptions: { composite: true, declarationMap: true } - })); - } - if (onHostCreate) { - onHostCreate(host); - } - const session = createSession(host); - openFilesForSession([...openFiles, randomFile], session); - return { host, session }; + function openFiles(verifiers: readonly DocumentPositionMapperVerifier[]) { + return verifiers.map(v => v.openFile); + } + interface OpenTsFile extends VerifierAndWithRefs { + onHostCreate?: (host: TestServerHost) => void; + } + function openTsFile({ withRefs, verifier, onHostCreate }: OpenTsFile) { + const host = createHost(files, [mainConfig.path]); + if (!withRefs) { + // Erase project reference + host.writeFile(mainConfig.path, JSON.stringify({ + compilerOptions: { composite: true, declarationMap: true } + })); } - - function checkProject(session: TestSession, noDts?: true) { - const service = session.getProjectService(); - checkNumberOfProjects(service, { configuredProjects: 1 + verifier.length }); - configFiles.forEach((configFile, index) => { - checkProjectActualFiles( - service.configuredProjects.get(configFile)!, - noDts ? - expectedProjectActualFiles[index].filter(f => f.toLowerCase() !== dtsPath) : - expectedProjectActualFiles[index] - ); - }); + if (onHostCreate) { + onHostCreate(host); } + const session = createSession(host); + const verifiers = verifier(withRefs); + openFilesForSession([...openFiles(verifiers), randomFile], session); + return { host, session, verifiers }; + } - function verifyInfos(session: TestSession, host: TestServerHost) { - verifyInfosWithRandom(session, host, openInfos, closedInfos, otherWatchedFiles); - } - - function verifyInfosWhenNoMapFile(session: TestSession, host: TestServerHost, dependencyTsOK?: boolean) { - const dtsMapClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsMapPath ? f : undefined); - verifyInfosWithRandom( - session, - host, - openInfos, - closedInfos.filter(f => f !== dtsMapClosedInfo && (dependencyTsOK || f !== dependencyTs.path)), - dtsMapClosedInfo ? otherWatchedFiles.concat(dtsMapClosedInfo) : otherWatchedFiles + function checkProject(session: TestSession, verifiers: readonly DocumentPositionMapperVerifier[], noDts?: true) { + const service = session.getProjectService(); + checkNumberOfProjects(service, { configuredProjects: 1 + verifiers.length }); + verifiers.forEach(({ configFile, expectedProjectActualFiles }) => { + checkProjectActualFiles( + service.configuredProjects.get(configFile.path.toLowerCase())!, + noDts ? + expectedProjectActualFiles.filter(f => f.toLowerCase() !== dtsPath) : + expectedProjectActualFiles ); - } + }); + } - function verifyInfosWhenNoDtsFile(session: TestSession, host: TestServerHost, watchDts: boolean, dependencyTsOk?: boolean, depedencyMapOk?: boolean) { - const dtsMapClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsMapPath ? f : undefined); - const dtsClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsPath ? f : undefined); - verifyInfosWithRandom( - session, - host, - openInfos, - closedInfos.filter(f => (depedencyMapOk || f !== dtsMapClosedInfo) && f !== dtsClosedInfo && (dependencyTsOk || f !== dependencyTs.path)), - dtsClosedInfo && watchDts ? - otherWatchedFiles.concat(dtsClosedInfo) : - otherWatchedFiles - ); + function firstAction(session: TestSession, verifiers: readonly DocumentPositionMapperVerifier[]) { + for (const { action } of getActionInfo(verifiers, "main")) { + const { request } = action(1); + session.executeCommandSeq(request); } + } - function verifyDocumentPositionMapper(session: TestSession, dependencyMap: server.ScriptInfo | undefined, documentPositionMapper: server.ScriptInfo["documentPositionMapper"], notEqual?: true) { - assert.strictEqual(session.getProjectService().filenameToScriptInfo.get(dtsMapPath), dependencyMap); - if (dependencyMap) { - if (notEqual) { - assert.notStrictEqual(dependencyMap.documentPositionMapper, documentPositionMapper); - } - else { - assert.strictEqual(dependencyMap.documentPositionMapper, documentPositionMapper); - } + function verifyAction(session: TestSession, { reqName, request, expectedResponse }: Action) { + const { response } = session.executeCommandSeq(request); + assert.deepEqual(response, expectedResponse, `Failed Request: ${reqName}`); + } + + function verifyScriptInfoPresence(session: TestSession, path: string, expectedToBePresent: boolean, reqName: string) { + const info = session.getProjectService().filenameToScriptInfo.get(path); + if (expectedToBePresent) { + assert.isDefined(info, `${reqName}:: ${path} expected to be present`); + } + else { + assert.isUndefined(info, `${reqName}:: ${path} expected to be not present`); + } + return info; + } + + function verifyDocumentPositionMapper(session: TestSession, dependencyMap: server.ScriptInfo | undefined, documentPositionMapper: server.ScriptInfo["documentPositionMapper"], equal: boolean) { + assert.strictEqual(session.getProjectService().filenameToScriptInfo.get(dtsMapPath), dependencyMap); + if (dependencyMap) { + if (equal) { + assert.strictEqual(dependencyMap.documentPositionMapper, documentPositionMapper); + } + else { + assert.notStrictEqual(dependencyMap.documentPositionMapper, documentPositionMapper); } } + } - function action(verifier: DocumentPositionMapperVerifier, fn: number, session: TestSession, useDependencyChange?: boolean) { - const { reqName, request, expectedResponse, expectedResponseNoMap, expectedResponseNoDts, requestDependencyChange, expectedResponseDependencyChange } = verifier.actionGetter(fn); - const { response } = session.executeCommandSeq(useDependencyChange ? requestDependencyChange || request : request); - return { reqName, response, expectedResponse, expectedResponseNoMap, expectedResponseNoDts, expectedResponseDependencyChange, verifier }; - } - - function firstAction(session: TestSession) { - verifier.forEach(v => action(v, 1, session)); - } - - function verifyAllFnActionWorker(session: TestSession, verifyAction: (result: ReturnType, dtsInfo: server.ScriptInfo | undefined, isFirst: boolean) => void, dtsAbsent?: boolean, useDependencyChange?: boolean) { - // action - let isFirst = true; - for (const v of verifier) { - for (let fn = 1; fn <= 5; fn++) { - const result = action(v, fn, session, useDependencyChange); - const dtsInfo = session.getProjectService().filenameToScriptInfo.get(dtsPath); - if (dtsAbsent) { - assert.isUndefined(dtsInfo); - } - else { - assert.isDefined(dtsInfo); - } - verifyAction(result, dtsInfo, isFirst); - isFirst = false; - } + function getActionInfo(verifiers: readonly DocumentPositionMapperVerifier[], actionKey: ActionKey): ActionInfo[] { + return verifiers.map(v => { + let actionInfoGetter = v[actionKey]; + while (isString(actionInfoGetter)) { + actionInfoGetter = v[actionInfoGetter]; } - } + return actionInfoGetter(); + }); + } - function dtsAbsent() { - return withRefs && !contains(closedInfos, dtsPath, (a, b) => a.toLowerCase() === b.toLowerCase()); - } - - function verifyAllFnAction( - session: TestSession, - host: TestServerHost, - firstDocumentPositionMapperNotEquals?: true, - dependencyMap?: server.ScriptInfo, - documentPositionMapper?: server.ScriptInfo["documentPositionMapper"], - useDependencyChange?: boolean - ) { - // action - verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse, expectedResponseDependencyChange }, dtsInfo, isFirst) => { - assert.deepEqual(response, useDependencyChange ? expectedResponseDependencyChange || expectedResponse : expectedResponse, `Failed on ${reqName}`); - verifyInfos(session, host); - if (dtsInfo) assert.equal(dtsInfo.sourceMapFilePath, dtsMapPath); - if (isFirst) { - if (dependencyMap) { - verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper, firstDocumentPositionMapperNotEquals); - documentPositionMapper = dependencyMap.documentPositionMapper; - } - else { - dependencyMap = session.getProjectService().filenameToScriptInfo.get(dtsMapPath); - documentPositionMapper = dependencyMap && dependencyMap.documentPositionMapper; - } - } - else { - verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper); - } - }, dtsAbsent(), useDependencyChange); - return { dependencyMap, documentPositionMapper }; - } - - function verifyAllFnActionWithNoMap( - session: TestSession, - host: TestServerHost, - dependencyTsOK?: true - ) { - let sourceMapFilePath: server.ScriptInfo["sourceMapFilePath"]; - // action - verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse, expectedResponseNoMap }, dtsInfo, isFirst) => { - assert.deepEqual(response, withRefs ? expectedResponse : expectedResponseNoMap || expectedResponse, `Failed on ${reqName}`); - verifyInfosWhenNoMapFile(session, host, dependencyTsOK); - assert.isUndefined(session.getProjectService().filenameToScriptInfo.get(dtsMapPath)); - if (!withRefs) { - if (isFirst) { - assert.isNotString(dtsInfo!.sourceMapFilePath); - assert.isNotFalse(dtsInfo!.sourceMapFilePath); - assert.isDefined(dtsInfo!.sourceMapFilePath); - sourceMapFilePath = dtsInfo!.sourceMapFilePath; - } - else { - assert.equal(dtsInfo!.sourceMapFilePath, sourceMapFilePath); - } - } - }, dtsAbsent()); - return sourceMapFilePath; - } - - function verifyAllFnActionWithNoDts( - session: TestSession, - host: TestServerHost, - dependencyTsAndMapOk?: true - ) { - // action - verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse, expectedResponseNoDts, verifier }) => { - assert.deepEqual(response, withRefs ? expectedResponse : expectedResponseNoDts || expectedResponse, `Failed on ${reqName}`); - verifyInfosWhenNoDtsFile( + interface VerifyAllFnAction { + session: TestSession; + host: TestServerHost; + verifiers: readonly DocumentPositionMapperVerifier[]; + actionKey: ActionKey; + sourceMapPath?: server.ScriptInfo["sourceMapFilePath"]; + dependencyMap?: server.ScriptInfo | undefined; + documentPositionMapper?: server.ScriptInfo["documentPositionMapper"]; + firstEquals?: boolean; + } + interface VerifyAllFnActionResult { + actionInfos: readonly ActionInfo[]; + actionKey: ActionKey; + dependencyMap: server.ScriptInfo | undefined; + documentPositionMapper: server.ScriptInfo["documentPositionMapper"] | undefined; + } + function verifyAllFnAction({ + session, + host, + verifiers, + actionKey, + dependencyMap, + documentPositionMapper, + firstEquals + }: VerifyAllFnAction): VerifyAllFnActionResult { + const actionInfos = getActionInfo(verifiers, actionKey); + let sourceMapPath: server.ScriptInfo["sourceMapFilePath"] | undefined; + // action + let first = true; + for (const { action, closedInfos, otherWatchedFiles, expectsDts, expectsMap } of actionInfos) { + for (let fn = 1; fn <= 5; fn++) { + const fnAction = action(fn); + verifyAction(session, fnAction); + const dtsInfo = verifyScriptInfoPresence(session, dtsPath, expectsDts, fnAction.reqName); + const dtsMapInfo = verifyScriptInfoPresence(session, dtsMapPath, expectsMap, fnAction.reqName); + verifyInfosWithRandom( session, host, - // Even when project actual file contains dts, its not watched because the dts is in another folder and module resolution just fails - // instead of succeeding to source file and then mapping using project reference (When using usage location) - // But watched if sourcemapper is in source project since we need to keep track of dts to update the source mapper for any potential usages - verifier.expectedProjectActualFiles.every(f => f.toLowerCase() !== dtsPath), - /*dependencyTsOk*/ withRefs || dependencyTsAndMapOk, - /*dependencyMapOk*/ dependencyTsAndMapOk + openFiles(verifiers).map(f => f.path), + closedInfos(), + otherWatchedFiles(), + `${actionKey}:: ${fnAction.reqName}` ); - }, /*dtsAbsent*/ true); + + if (dtsInfo) { + if (first) { + if (dtsMapInfo) { + assert.equal(dtsInfo.sourceMapFilePath, dtsMapPath, `${actionKey}:: ${fnAction.reqName}`); + } + else { + assert.isNotString(dtsInfo.sourceMapFilePath); + assert.isNotFalse(dtsInfo.sourceMapFilePath); + assert.isDefined(dtsInfo.sourceMapFilePath); + } + } + else { + assert.equal(dtsInfo.sourceMapFilePath, sourceMapPath, `${actionKey}:: ${fnAction.reqName}`); + } + } + if (!first || firstEquals !== undefined) { + verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper, !first || !!firstEquals); + } + sourceMapPath = dtsInfo && dtsInfo.sourceMapFilePath; + dependencyMap = dtsMapInfo; + documentPositionMapper = dependencyMap && dependencyMap.documentPositionMapper; + first = false; + } } - function verifyScenarioWithChangesWorker( - change: (host: TestServerHost, session: TestSession) => void, - afterActionDocumentPositionMapperNotEquals: true | undefined, - timeoutBeforeAction: boolean, - useDependencyChange?: boolean - ) { - const { host, session } = openTsFile(); + return { actionInfos, actionKey, dependencyMap, documentPositionMapper }; + } + + function verifyScriptInfoCollection( + session: TestSession, + host: TestServerHost, + verifiers: readonly DocumentPositionMapperVerifier[], + { dependencyMap, documentPositionMapper, actionInfos, actionKey }: VerifyAllFnActionResult + ) { + // Collecting at this point retains dependency.d.ts and map + closeFilesForSession([randomFile], session); + openFilesForSession([randomFile], session); + + const { closedInfos, otherWatchedFiles } = last(actionInfos); + verifyInfosWithRandom( + session, + host, + openFiles(verifiers).map(f => f.path), + closedInfos(), + otherWatchedFiles(), + `${actionKey} Collection` + ); + verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper, /*equal*/ true); + + // Closing open file, removes dependencies too + closeFilesForSession([...openFiles(verifiers), randomFile], session); + openFilesForSession([randomFile], session); + verifyOnlyRandomInfos(session, host); + } + + function verifyScenarioAndScriptInfoCollection( + session: TestSession, + host: TestServerHost, + verifiers: readonly DocumentPositionMapperVerifier[], + actionKey: ActionKey, + noDts?: true + ) { + // Main scenario action + const result = verifyAllFnAction({ session, host, verifiers, actionKey }); + checkProject(session, verifiers, noDts); + verifyScriptInfoCollection(session, host, verifiers, result); + } + + function verifyScenarioWithChangesWorker( + { + scenarioName, + verifier, + withRefs, + change, + afterActionDocumentPositionMapperNotEquals, + afterChangeActionKey + }: VerifyScenarioWithChanges, + timeoutBeforeAction: boolean, + ) { + it(scenarioName, () => { + const { host, session, verifiers } = openTsFile({ verifier, withRefs }); // Create DocumentPositionMapper - firstAction(session); + firstAction(session, verifiers); const dependencyMap = session.getProjectService().filenameToScriptInfo.get(dtsMapPath); const documentPositionMapper = dependencyMap && dependencyMap.documentPositionMapper; // change - change(host, session); + change(host, session, verifiers); if (timeoutBeforeAction) { host.runQueuedTimeoutCallbacks(); - checkProject(session); - verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper); + checkProject(session, verifiers); + verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper, /*equal*/ true); } // action - verifyAllFnAction(session, host, afterActionDocumentPositionMapperNotEquals, dependencyMap, documentPositionMapper, useDependencyChange); - } - - function verifyScenarioWithChanges( - scenarioName: string, - change: (host: TestServerHost, session: TestSession) => void, - afterActionDocumentPositionMapperNotEquals?: true, - useDependencyChange?: boolean - ) { - describe(scenarioName, () => { - it("when timeout occurs before request", () => { - verifyScenarioWithChangesWorker(change, afterActionDocumentPositionMapperNotEquals, /*timeoutBeforeAction*/ true, useDependencyChange); - }); - - it("when timeout does not occur before request", () => { - verifyScenarioWithChangesWorker(change, afterActionDocumentPositionMapperNotEquals, /*timeoutBeforeAction*/ false, useDependencyChange); - }); - }); - } - - function verifyMainScenarioAndScriptInfoCollection(session: TestSession, host: TestServerHost) { - // Main scenario action - const { dependencyMap, documentPositionMapper } = verifyAllFnAction(session, host); - checkProject(session); - verifyInfos(session, host); - - // Collecting at this point retains dependency.d.ts and map - closeFilesForSession([randomFile], session); - openFilesForSession([randomFile], session); - verifyInfos(session, host); - verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper); - - // Closing open file, removes dependencies too - closeFilesForSession([...openFiles, randomFile], session); - openFilesForSession([randomFile], session); - verifyOnlyRandomInfos(session, host); - } - - function verifyMainScenarioAndScriptInfoCollectionWithNoMap(session: TestSession, host: TestServerHost, dependencyTsOKInScenario?: true) { - // Main scenario action - verifyAllFnActionWithNoMap(session, host, withRefs || dependencyTsOKInScenario); - - // Collecting at this point retains dependency.d.ts and map watcher - closeFilesForSession([randomFile], session); - openFilesForSession([randomFile], session); - verifyInfosWhenNoMapFile(session, host, withRefs); - - // Closing open file, removes dependencies too - closeFilesForSession([...openFiles, randomFile], session); - openFilesForSession([randomFile], session); - verifyOnlyRandomInfos(session, host); - } - - function verifyMainScenarioAndScriptInfoCollectionWithNoDts(session: TestSession, host: TestServerHost, dependencyTsAndMapOk?: true) { - // Main scenario action - verifyAllFnActionWithNoDts(session, host, dependencyTsAndMapOk); - - // Collecting at this point retains dependency.d.ts and map watcher - closeFilesForSession([randomFile], session); - openFilesForSession([randomFile], session); - verifyInfosWhenNoDtsFile( + verifyAllFnAction({ session, host, - !!forEach(verifier, v => v.expectedProjectActualFiles.every(f => f.toLowerCase() !== dtsPath)), - /*dependencyTsOk*/ withRefs - ); + verifiers, + actionKey: afterChangeActionKey, + dependencyMap, + documentPositionMapper, + firstEquals: !afterActionDocumentPositionMapperNotEquals + }); + }); + } - // Closing open file, removes dependencies too - closeFilesForSession([...openFiles, randomFile], session); - openFilesForSession([randomFile], session); - verifyOnlyRandomInfos(session, host); - } + interface VerifyScenarioWithChanges extends VerifierAndWithRefs { + scenarioName: string; + change: (host: TestServerHost, session: TestSession, verifiers: readonly DocumentPositionMapperVerifier[]) => void; + afterActionDocumentPositionMapperNotEquals?: true; + afterChangeActionKey: ActionKey; + } + function verifyScenarioWithChanges(verify: VerifyScenarioWithChanges) { + describe("when timeout occurs before request", () => { + verifyScenarioWithChangesWorker(verify, /*timeoutBeforeAction*/ true); + }); - function verifyScenarioWhenFileNotPresent( - scenarioName: string, - fileLocation: string, - verifyScenarioAndScriptInfoCollection: (session: TestSession, host: TestServerHost, dependencyTsOk?: true) => void, - noDts?: true - ) { - describe(scenarioName, () => { - it(mainScenario, () => { - const { host, session } = openTsFile(host => host.deleteFile(fileLocation)); - checkProject(session, noDts); + describe("when timeout does not occur before request", () => { + verifyScenarioWithChangesWorker(verify, /*timeoutBeforeAction*/ false); + }); + } - verifyScenarioAndScriptInfoCollection(session, host); + interface VerifyScenarioWhenFileNotPresent extends VerifierAndWithRefs { + scenarioName: string; + fileLocation: string; + fileNotPresentKey: ActionKey; + fileCreatedKey: ActionKey; + fileDeletedKey: ActionKey; + noDts?: true; + } + function verifyScenarioWhenFileNotPresent({ + scenarioName, + verifier, + withRefs, + fileLocation, + fileNotPresentKey, + fileCreatedKey, + fileDeletedKey, + noDts + }: VerifyScenarioWhenFileNotPresent) { + describe(scenarioName, () => { + it("when file is not present", () => { + const { host, session, verifiers } = openTsFile({ + verifier, + withRefs, + onHostCreate: host => host.deleteFile(fileLocation) }); + checkProject(session, verifiers, noDts); - it("when file is created", () => { - let fileContents: string | undefined; - const { host, session } = openTsFile(host => { + verifyScenarioAndScriptInfoCollection(session, host, verifiers, fileNotPresentKey, noDts); + }); + + it("when file is created after actions on projects", () => { + let fileContents: string | undefined; + const { host, session, verifiers } = openTsFile({ + verifier, + withRefs, + onHostCreate: host => { fileContents = host.readFile(fileLocation); host.deleteFile(fileLocation); - }); - firstAction(session); - - host.writeFile(fileLocation, fileContents!); - verifyMainScenarioAndScriptInfoCollection(session, host); + } }); + firstAction(session, verifiers); - it("when file is deleted", () => { - const { host, session } = openTsFile(); - firstAction(session); - - // The dependency file is deleted when orphan files are collected - host.deleteFile(fileLocation); - verifyScenarioAndScriptInfoCollection(session, host, /*dependencyTsOk*/ true); - }); + host.writeFile(fileLocation, fileContents!); + verifyScenarioAndScriptInfoCollection(session, host, verifiers, fileCreatedKey); }); - } + it("when file is deleted after actions on the projects", () => { + const { host, session, verifiers } = openTsFile({ verifier, withRefs }); + firstAction(session, verifiers); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(fileLocation); + // Verify with deleted action key + const result = verifyAllFnAction({ session, host, verifiers, actionKey: fileDeletedKey }); + checkProject(session, verifiers, noDts); + + // Script info collection should behave as fileNotPresentKey + verifyScriptInfoCollection( + session, + host, + verifiers, + { + actionInfos: getActionInfo(verifiers, fileNotPresentKey), + actionKey: result.actionKey, + dependencyMap: undefined, + documentPositionMapper: undefined + } + ); + }); + }); + } + + function verifyScenarioWorker({ mainScenario, verifier }: VerifyScenario, withRefs: boolean) { it(mainScenario, () => { - const { host, session } = openTsFile(); - checkProject(session); - - verifyMainScenarioAndScriptInfoCollection(session, host); + const { host, session, verifiers } = openTsFile({ withRefs, verifier }); + checkProject(session, verifiers); + verifyScenarioAndScriptInfoCollection(session, host, verifiers, "main"); }); // Edit - verifyScenarioWithChanges( - "when usage file changes, document position mapper doesnt change", - (_host, session) => openFiles.forEach( - (openFile, index) => session.executeCommandSeq({ + verifyScenarioWithChanges({ + scenarioName: "when usage file changes, document position mapper doesnt change", + verifier, + withRefs, + change: (_host, session, verifiers) => verifiers.forEach( + verifier => session.executeCommandSeq({ command: protocol.CommandTypes.Change, - arguments: { file: openFile.path, line: openFileLastLines[index], offset: 1, endLine: openFileLastLines[index], endOffset: 1, insertString: "const x = 10;" } + arguments: { + file: verifier.openFile.path, + line: verifier.openFileLastLine, + offset: 1, + endLine: verifier.openFileLastLine, + endOffset: 1, + insertString: "const x = 10;" + } }) - ) - ); + ), + afterChangeActionKey: "main" + }); // Edit dts to add new fn - verifyScenarioWithChanges( - "when dependency .d.ts changes, document position mapper doesnt change", - host => host.writeFile( + verifyScenarioWithChanges({ + scenarioName: "when dependency .d.ts changes, document position mapper doesnt change", + verifier, + withRefs, + change: host => host.writeFile( dtsLocation, host.readFile(dtsLocation)!.replace( "//# sourceMappingURL=FnS.d.ts.map", `export declare function fn6(): void; //# sourceMappingURL=FnS.d.ts.map` ) - ) - ); + ), + afterChangeActionKey: "main" + }); // Edit map file to represent added new line - verifyScenarioWithChanges( - "when dependency file's map changes", - host => host.writeFile( + verifyScenarioWithChanges({ + scenarioName: "when dependency file's map changes", + verifier, + withRefs, + change: host => host.writeFile( dtsMapLocation, `{"version":3,"file":"FnS.d.ts","sourceRoot":"","sources":["../dependency/FnS.ts"],"names":[],"mappings":"AAAA,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,eAAO,MAAM,CAAC,KAAK,CAAC"}` ), - /*afterActionDocumentPositionMapperNotEquals*/ true - ); + afterChangeActionKey: "main", + afterActionDocumentPositionMapperNotEquals: true + }); - verifyScenarioWhenFileNotPresent( - "when map file is not present", - dtsMapLocation, - verifyMainScenarioAndScriptInfoCollectionWithNoMap - ); + verifyScenarioWhenFileNotPresent({ + scenarioName: "with depedency files map file", + verifier, + withRefs, + fileLocation: dtsMapLocation, + fileNotPresentKey: "noMap", + fileCreatedKey: "mapFileCreated", + fileDeletedKey: "mapFileDeleted" + }); - verifyScenarioWhenFileNotPresent( - "when .d.ts file is not present", - dtsLocation, - verifyMainScenarioAndScriptInfoCollectionWithNoDts, - /*noDts*/ true - ); + verifyScenarioWhenFileNotPresent({ + scenarioName: "with depedency .d.ts file", + verifier, + withRefs, + fileLocation: dtsLocation, + fileNotPresentKey: "noDts", + fileCreatedKey: "dtsFileCreated", + fileDeletedKey: "dtsFileDeleted", + noDts: true + }); if (withRefs) { - verifyScenarioWithChanges( - "when defining project source changes", - (host, session) => { + verifyScenarioWithChanges({ + scenarioName: "when defining project source changes", + verifier, + withRefs, + change: (host, session, verifiers) => { // Make change, without rebuild of solution - if (contains(openInfos, dependencyTs.path)) { + if (contains(openFiles(verifiers), dependencyTs)) { session.executeCommandSeq({ command: protocol.CommandTypes.Change, arguments: { @@ -724,96 +821,340 @@ fn5(); ${dependencyTs.content}`); } }, - /*afterActionDocumentPositionMapperNotEquals*/ undefined, - /*useDepedencyChange*/ true - ); + afterChangeActionKey: "dependencyChange" + }); it("when d.ts file is not generated", () => { const host = createServerHost(files); const session = createSession(host); - openFilesForSession([...openFiles, randomFile], session); - - const expectedClosedInfos = closedInfos.filter(f => f.toLowerCase() !== dtsPath && f.toLowerCase() !== dtsMapPath); - // If closed infos includes dts and dtsMap, watch dts since its not present - const expectedWatchedFiles = closedInfos.length === expectedClosedInfos.length ? - otherWatchedFiles : - otherWatchedFiles.concat(dtsPath); - // Main scenario action - verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse }) => { - assert.deepEqual(response, expectedResponse, `Failed on ${reqName}`); - verifyInfosWithRandom(session, host, openInfos, expectedClosedInfos, expectedWatchedFiles); - verifyDocumentPositionMapper(session, /*dependencyMap*/ undefined, /*documentPositionMapper*/ undefined); - }, /*dtsAbsent*/ true); - checkProject(session); - - // Collecting at this point retains dependency.d.ts and map - closeFilesForSession([randomFile], session); - openFilesForSession([randomFile], session); - verifyInfosWithRandom(session, host, openInfos, expectedClosedInfos, expectedWatchedFiles); - verifyDocumentPositionMapper(session, /*dependencyMap*/ undefined, /*documentPositionMapper*/ undefined); - - // Closing open file, removes dependencies too - closeFilesForSession([...openFiles, randomFile], session); - openFilesForSession([randomFile], session); - verifyOnlyRandomInfos(session, host); + const verifiers = verifier(withRefs); + openFilesForSession([...openFiles(verifiers), randomFile], session); + verifyScenarioAndScriptInfoCollection(session, host, verifiers, "noBuild"); }); } } - function verifyScenarios(withRefs: boolean) { - describe(withRefs ? "when main tsconfig has project reference" : "when main tsconfig doesnt have project reference", () => { - const usageVerifier: DocumentPositionMapperVerifier = { - openFile: mainTs, - expectedProjectActualFiles: withRefs ? - [mainTs.path, libFile.path, mainConfig.path, dependencyTs.path] : - [mainTs.path, libFile.path, mainConfig.path, dtsPath], - actionGetter: gotoDefintinionFromMainTs, - openFileLastLine: 14 - }; - describe("from project that uses dependency", () => { - const closedInfos = withRefs ? - [dependencyTs.path, dependencyConfig.path, libFile.path] : - [dependencyTs.path, libFile.path, dtsPath, dtsMapLocation]; - verifyDocumentPositionMapperUpdates( - "can go to definition correctly", - [usageVerifier], - closedInfos, - withRefs - ); - }); - - const definingVerifier: DocumentPositionMapperVerifier = { - openFile: dependencyTs, - expectedProjectActualFiles: [dependencyTs.path, libFile.path, dependencyConfig.path], - actionGetter: renameFromDependencyTs, - openFileLastLine: 6, - }; - describe("from defining project", () => { - const closedInfos = [libFile.path, dtsLocation, dtsMapLocation]; - verifyDocumentPositionMapperUpdates( - "rename locations from dependency", - [definingVerifier], - closedInfos, - withRefs - ); - }); - - describe("when opening depedency and usage project", () => { - const closedInfos = withRefs ? - [libFile.path, dependencyConfig.path] : - [libFile.path, dtsPath, dtsMapLocation]; - verifyDocumentPositionMapperUpdates( - "goto Definition in usage and rename locations from defining project", - [usageVerifier, { ...definingVerifier, actionGetter: renameFromDependencyTsWithBothProjectsOpen }], - closedInfos, - withRefs - ); - }); + interface VerifyScenario { + mainScenario: string; + verifier: (withRefs: boolean) => readonly DocumentPositionMapperVerifier[]; + } + function verifyScenario(scenario: VerifyScenario) { + describe("when main tsconfig doesnt have project reference", () => { + verifyScenarioWorker(scenario, /*withRefs*/ false); + }); + describe("when main tsconfig has project reference", () => { + verifyScenarioWorker(scenario, /*withRefs*/ true); }); } - verifyScenarios(/*withRefs*/ false); - verifyScenarios(/*withRefs*/ true); + describe("from project that uses dependency", () => { + function goToDefActionInfo(withRefs: boolean): ActionInfo { + return { + action: goToDefFromMainTs, + closedInfos: () => withRefs ? + [dependencyTs.path, dependencyConfig.path, libFile.path] : + [dependencyTs.path, libFile.path, dtsPath, dtsMapLocation], + otherWatchedFiles: () => [mainConfig.path], + expectsDts: !withRefs, // Dts script info present only if no project reference + expectsMap: !withRefs // Map script info present only if no project reference + }; + } + + function goToDefNoMapActionInfo(withRefs: boolean): ActionInfo { + return { + ...goToDefActionInfo(withRefs), + action: withRefs ? + goToDefFromMainTs : + goToDefFromMainTsWithNoMap, + closedInfos: () => withRefs ? + [dependencyTs.path, dependencyConfig.path, libFile.path] : + [libFile.path, dtsPath], // Because map is deleted, dts and dependency are released + otherWatchedFiles: () => withRefs ? + [mainConfig.path] : + [mainConfig.path, dtsMapPath], // Watches deleted file + expectsMap: false + }; + } + + function goToDefNoDtsActionInfo(withRefs: boolean): ActionInfo { + return { + ...goToDefActionInfo(withRefs), + action: withRefs ? + goToDefFromMainTs : + goToDefFromMainTsWithNoDts, + closedInfos: () => withRefs ? + [dependencyTs.path, dependencyConfig.path, libFile.path] : + [libFile.path], // No dts means no map, no dependency + expectsDts: false, + expectsMap: false + }; + } + + verifyScenario({ + mainScenario: "can go to definition correctly", + verifier: withRefs => [ + { + ...goToDefFromMainTsProjectInfoVerifier(withRefs), + main: () => goToDefActionInfo(withRefs), + noMap: () => goToDefNoMapActionInfo(withRefs), + mapFileCreated: "main", + mapFileDeleted: () => ({ + ...goToDefNoMapActionInfo(withRefs), + closedInfos: () => withRefs ? + [dependencyTs.path, dependencyConfig.path, libFile.path] : + // The script info for depedency is collected only after file open + [dependencyTs.path, libFile.path, dtsPath] + }), + noDts: () => goToDefNoDtsActionInfo(withRefs), + dtsFileCreated: "main", + dtsFileDeleted: () => ({ + ...goToDefNoDtsActionInfo(withRefs), + // The script info for map is collected only after file open + closedInfos: () => withRefs ? + [dependencyTs.path, dependencyConfig.path, libFile.path] : + [dependencyTs.path, libFile.path, dtsMapLocation], + expectsMap: !withRefs + }), + dependencyChange: () => ({ + ...goToDefActionInfo(withRefs), + action: goToDefFromMainTsWithDependencyChange, + expectsDts: false, + expectsMap: false + }), + noBuild: "main" + } + ] + }); + }); + + describe("from defining project", () => { + function renameActionInfo(): ActionInfo { + return { + action: renameFromDependencyTs, + closedInfos: () => [libFile.path, dtsLocation, dtsMapLocation], + otherWatchedFiles: () => [dependencyConfig.path], + expectsDts: true, + expectsMap: true + }; + } + + function renameNoMapActionInfo(): ActionInfo { + return { + ...renameActionInfo(), + closedInfos: () => [libFile.path, dtsLocation], // No map + otherWatchedFiles: () => [dependencyConfig.path, dtsMapLocation], // watch map + expectsMap: false + }; + } + + function renameNoDtsActionInfo(): ActionInfo { + return { + action: renameFromDependencyTs, + closedInfos: () => [libFile.path], // no dts or map since dts itself doesnt exist + otherWatchedFiles: () => [dependencyConfig.path, dtsPath], // watch deleted file + expectsDts: false, + expectsMap: false + }; + } + + verifyScenario({ + mainScenario: "rename locations from dependency", + verifier: () => [ + { + ...renameFromDependencyTsProjectInfoVerifier(), + main: renameActionInfo, + noMap: renameNoMapActionInfo, + mapFileCreated: "main", + mapFileDeleted: "noMap", + noDts: renameNoDtsActionInfo, + dtsFileCreated: "main", + dtsFileDeleted: () => ({ + ...renameNoDtsActionInfo(), + // Map is collected after file open + closedInfos: () => [libFile.path, dtsMapLocation], + expectsMap: true + }), + dependencyChange: () => ({ + ...renameActionInfo(), + action: renameFromDependencyTsWithDependencyChange + }), + noBuild: () => ({ + action: renameFromDependencyTs, + closedInfos: () => [libFile.path], // No dts or map since its not built/present + // Watching for creation of dts so that it can give correct results across projects + otherWatchedFiles: () => [dependencyConfig.path, dtsPath], + expectsDts: false, + expectsMap: false + }) + } + ] + }); + }); + + describe("when opening depedency and usage project", () => { + function closedInfos(withRefs: boolean) { + // DependencyTs is open, so omit it from closed infos + return () => withRefs ? + [dependencyConfig.path, libFile.path] : + [libFile.path, dtsPath, dtsMapLocation]; + } + + function otherWatchedFiles(withRefs: boolean) { + return () => withRefs ? + [mainConfig.path] : // Its in closed info + [mainConfig.path, dependencyConfig.path]; + } + + function goToDefActionInfo(withRefs: boolean): ActionInfo { + return { + action: goToDefFromMainTs, + closedInfos: closedInfos(withRefs), + otherWatchedFiles: otherWatchedFiles(withRefs), + expectsDts: !withRefs, // Dts script info present only if no project reference + expectsMap: !withRefs // Map script info present only if no project reference + }; + } + + function renameActionInfo(withRefs: boolean): ActionInfo { + return { + action: renameFromDependencyTsWithBothProjectsOpen, + closedInfos: closedInfos(withRefs), + otherWatchedFiles: otherWatchedFiles(withRefs), + expectsDts: !withRefs, // Dts script info present only if no project reference + expectsMap: !withRefs // Map script info present only if no project reference + }; + } + + function closedInfosNoMap(withRefs: boolean) { + return withRefs ? + closedInfos(withRefs) : + () => [libFile.path, dtsPath]; // No map + } + + function otherWatchedFilesNoMap(withRefs: boolean) { + return withRefs ? + otherWatchedFiles(withRefs) : + () => [mainConfig.path, dependencyConfig.path, dtsMapLocation]; // Watch map file + } + + function goToDefNoMapActionInfo(withRefs: boolean): ActionInfo { + return { + ...goToDefActionInfo(withRefs), + action: withRefs ? + goToDefFromMainTs : + goToDefFromMainTsWithNoMap, + closedInfos: closedInfosNoMap(withRefs), + otherWatchedFiles: otherWatchedFilesNoMap(withRefs), + expectsMap: false + }; + } + + function renameNoMapActionInfo(withRefs: boolean): ActionInfo { + return { + ...renameActionInfo(withRefs), + action: withRefs ? + renameFromDependencyTsWithBothProjectsOpen : + renameFromDependencyTs, + closedInfos: closedInfosNoMap(withRefs), + otherWatchedFiles: otherWatchedFilesNoMap(withRefs), + expectsMap: false + }; + } + + function closedInfosNoDts(withRefs: boolean) { + return withRefs ? + closedInfos(withRefs) : + () => [libFile.path]; // No dts or map + } + + function otherWatchedFilesNoDts(withRefs: boolean) { + return withRefs ? + otherWatchedFiles(withRefs) : + () => [mainConfig.path, dependencyConfig.path, dtsPath]; // Watch dts + } + + function goToDefNoDtsActionInfo(withRefs: boolean): ActionInfo { + return { + ...goToDefActionInfo(withRefs), + action: withRefs ? + goToDefFromMainTs : + goToDefFromMainTsWithNoDts, + closedInfos: closedInfosNoDts(withRefs), + expectsDts: false, + expectsMap: false + }; + } + + function renameNoDtsActionInfo(withRefs: boolean): ActionInfo { + return { + action: withRefs ? + renameFromDependencyTsWithBothProjectsOpen : + renameFromDependencyTs, + closedInfos: closedInfosNoDts(withRefs), + otherWatchedFiles: otherWatchedFilesNoDts(withRefs), + expectsDts: false, + expectsMap: false + }; + } + + function closedInfosDtsFileDeleted(withRefs: boolean) { + // Map collection after file open + return withRefs ? + closedInfos(withRefs) : + () => [libFile.path, dtsMapLocation]; + } + + verifyScenario({ + mainScenario: "goto Definition in usage and rename locations from defining project", + verifier: withRefs => [ + { + ...goToDefFromMainTsProjectInfoVerifier(withRefs), + main: () => goToDefActionInfo(withRefs), + noMap: () => goToDefNoMapActionInfo(withRefs), + mapFileCreated: "main", + mapFileDeleted: "noMap", + noDts: () => goToDefNoDtsActionInfo(withRefs), + dtsFileCreated: "main", + dtsFileDeleted: () => ({ + ...goToDefNoDtsActionInfo(withRefs), + // Map collection after file open + closedInfos: closedInfosDtsFileDeleted(withRefs), + expectsMap: !withRefs + }), + dependencyChange: () => ({ + ...goToDefActionInfo(withRefs), + action: goToDefFromMainTsWithDependencyChange, + expectsDts: false, + expectsMap: false + }), + noBuild: "main" + }, + { + ...renameFromDependencyTsProjectInfoVerifier(), + main: () => renameActionInfo(withRefs), + noMap: () => renameNoMapActionInfo(withRefs), + mapFileCreated: "main", + mapFileDeleted: "noMap", + noDts: () => renameNoDtsActionInfo(withRefs), + dtsFileCreated: "main", + dtsFileDeleted: () => ({ + ...renameNoDtsActionInfo(withRefs), + // Map collection after file open + closedInfos: closedInfosDtsFileDeleted(withRefs), + expectsMap: !withRefs + }), + dependencyChange: () => ({ + ...renameActionInfo(withRefs), + action: renameFromDependencyTsWithBothProjectsOpenWithDependencyChange + }), + noBuild: () => ({ + ...renameActionInfo(withRefs), + expectDts: false + }) + } + ] + }); + }); }); it("reusing d.ts files from composite and non composite projects", () => {