Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 86 additions & 12 deletions packages/typespec-vscode/test/extension/common/mock-dialogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,41 @@ 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<T> {
[SEQUENCE_MARKER]: true;
results: T[];
}

/** Create a sequence of results where each call returns the next item in the list */
export function sequence<T>(...results: T[]): DialogResultSequence<T> {
return {
[SEQUENCE_MARKER]: true,
results,
};
}

function isSequence<T>(value: unknown): value is DialogResultSequence<T> {
return typeof value === "object" && value !== null && SEQUENCE_MARKER in value;
}

export type DialogMethodStub<T extends keyof Dialog> = {
method: T;
value: Awaited<ReturnType<Dialog[T]>>;
};

export type DialogMethodStubPartial<T extends keyof Dialog> = {
method: T;
value: Partial<Awaited<ReturnType<Dialog[T]>>>;
value:
| Partial<Awaited<ReturnType<Dialog[T]>>>
| DialogResultSequence<Partial<Awaited<ReturnType<Dialog[T]>>>>;
};

type DialogMethodStubResolved<T extends keyof Dialog> = {
method: T;
value: Awaited<ReturnType<Dialog[T]>> | DialogResultSequence<Awaited<ReturnType<Dialog[T]>>>;
};

type DialogDefaults = {
Expand Down Expand Up @@ -43,39 +70,86 @@ export function stubMultipleDialogs<T extends keyof Dialog>(
) {
const mocksRequired = mocks.map((mock) => {
const methodDefault = dialogDefaults[mock.method];
if (!methodDefault) return mock as DialogMethodStub<T>;
if (!methodDefault) return mock as DialogMethodStubResolved<T>;

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<T>;
}

if (typeof mock.value === "object") {
mock.value = { ...methodDefault, ...mock.value };
} else {
mock.value = mock.value ?? methodDefault;
}
return mock as DialogMethodStub<T>;
return mock as DialogMethodStubResolved<T>;
});

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

/**
* 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<string[]>,
) {
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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();
});
});
Loading