diff --git a/packages/typespec-vscode/test/extension/common/mock-dialogs.ts b/packages/typespec-vscode/test/extension/common/mock-dialogs.ts index e8fd008ea2c..9c18bd73b12 100644 --- a/packages/typespec-vscode/test/extension/common/mock-dialogs.ts +++ b/packages/typespec-vscode/test/extension/common/mock-dialogs.ts @@ -7,6 +7,26 @@ export interface Dialog { }>; } +/** Special marker to indicate a sequence of results where each call returns the next item */ +const SEQUENCE_MARKER = "__sequence__" as const; + +export interface DialogResultSequence { + [SEQUENCE_MARKER]: true; + results: T[]; +} + +/** Create a sequence of results where each call returns the next item in the list */ +export function sequence(...results: T[]): DialogResultSequence { + return { + [SEQUENCE_MARKER]: true, + results, + }; +} + +function isSequence(value: unknown): value is DialogResultSequence { + return typeof value === "object" && value !== null && SEQUENCE_MARKER in value; +} + export type DialogMethodStub = { method: T; value: Awaited>; @@ -14,7 +34,14 @@ export type DialogMethodStub = { export type DialogMethodStubPartial = { method: T; - value: Partial>>; + value: + | Partial>> + | DialogResultSequence>>>; +}; + +type DialogMethodStubResolved = { + method: T; + value: Awaited> | DialogResultSequence>>; }; type DialogDefaults = { @@ -43,30 +70,67 @@ export function stubMultipleDialogs( ) { const mocksRequired = mocks.map((mock) => { const methodDefault = dialogDefaults[mock.method]; - if (!methodDefault) return mock as DialogMethodStub; + if (!methodDefault) return mock as DialogMethodStubResolved; + + if (isSequence(mock.value)) { + // Apply defaults to each result in the sequence + const resolvedResults = mock.value.results.map((result) => { + if (typeof result === "object") { + return { ...methodDefault, ...result }; + } + return result ?? methodDefault; + }); + return { + method: mock.method, + value: { __sequence__: true, results: resolvedResults }, + } as DialogMethodStubResolved; + } + if (typeof mock.value === "object") { mock.value = { ...methodDefault, ...mock.value }; } else { mock.value = mock.value ?? methodDefault; } - return mock as DialogMethodStub; + return mock as DialogMethodStubResolved; }); // https://github.com/microsoft/playwright/issues/8278#issuecomment-1009957411 - return app.evaluate(({ dialog }, mocks) => { + return app.evaluate(({ dialog, ...args }, mocks) => { mocks.forEach((mock) => { const thisDialog = dialog[mock.method]; if (!thisDialog) { throw new Error(`can't find ${mock.method} on dialog module.`); } - if (mock.method.endsWith("Sync")) { - dialog[mock.method] = () => { - return mock.value; - }; + + const isSeq = (v: unknown): v is { __sequence__: true; results: unknown[] } => + typeof v === "object" && v !== null && "__sequence__" in v; + + if (isSeq(mock.value)) { + let callIndex = 0; + const results = mock.value.results; + if (mock.method.endsWith("Sync")) { + dialog[mock.method] = () => { + const result = results[Math.min(callIndex, results.length - 1)]; + callIndex++; + + return result; + }; + } else { + dialog[mock.method] = async () => { + const result = results[Math.min(callIndex, results.length - 1)]; + return result; + }; + } } else { - dialog[mock.method] = async () => { - return mock.value; - }; + if (mock.method.endsWith("Sync")) { + dialog[mock.method] = () => { + return mock.value; + }; + } else { + dialog[mock.method] = async () => { + return mock.value; + }; + } } }); }, mocksRequired); @@ -74,8 +138,18 @@ export function stubMultipleDialogs( /** * Mock the `showOpenDialog`. Call this before something that will trigger the dialog. + * @param filePaths - Either a single array of file paths, or a sequence of arrays for multiple calls */ -export function mockShowOpenDialog(app: ElectronApplication, filePaths: string[]) { +export function mockShowOpenDialog( + app: ElectronApplication, + filePaths: string[] | DialogResultSequence, +) { + if (isSequence(filePaths)) { + const sequenceValue = sequence( + ...filePaths.results.map((r) => ({ filePaths: r, canceled: false })), + ); + return stubMultipleDialogs(app, [{ method: "showOpenDialog", value: sequenceValue }]); + } return stubDialog(app, "showOpenDialog", { filePaths, canceled: false, diff --git a/packages/typespec-vscode/test/extension/import-typespec.test.ts b/packages/typespec-vscode/test/extension/import-typespec.test.ts index 3e4ea9a186d..09a1f5dc3b9 100644 --- a/packages/typespec-vscode/test/extension/import-typespec.test.ts +++ b/packages/typespec-vscode/test/extension/import-typespec.test.ts @@ -6,9 +6,9 @@ import { packagesInstall, packPackages, preContrastResult, - startWithRightClick, + startWithCommandPalette, } from "./common/common-steps"; -import { mockShowOpenDialog } from "./common/mock-dialogs"; +import { mockShowOpenDialog, sequence } from "./common/mock-dialogs"; import { CaseScreenshot, tempDir, test, testfilesDir } from "./common/utils"; // Test files are copied into the temporary directory before tests run @@ -85,9 +85,10 @@ describe.each(ImportCasesConfigList)("ImportTypespecFromOpenApi3", async (item) const { page, app } = await launch({ workspacePath, }); + const targetPath = path.resolve(workspacePath, "ImportTypespecProjectEmptyFolder"); const openapifilepath = path.resolve(ImportTypespecProjectFolderPath, "openapi.3.0.yaml"); - await mockShowOpenDialog(app, [openapifilepath]); - await startWithRightClick(page, "Import TypeSpec from Openapi 3", cs); + await mockShowOpenDialog(app, sequence([targetPath], [openapifilepath])); + await startWithCommandPalette(page, "Import TypeSpec from Openapi 3", cs); await preContrastResult( page, @@ -97,8 +98,8 @@ describe.each(ImportCasesConfigList)("ImportTypespecFromOpenApi3", async (item) cs, app, ); - const resultFilePath = path.resolve(workspacePath, "./ImportTypespecProjectEmptyFolder"); - await expectFilesInDir(expectedResults, resultFilePath); + + await expectFilesInDir(expectedResults, targetPath); app.close(); }); });