-
Notifications
You must be signed in to change notification settings - Fork 62
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(component-testing): implement mocks
- Loading branch information
Showing
17 changed files
with
976 additions
and
54 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// TODO: use from browser code when migrate to esm | ||
type MockFactory = (originalImport?: unknown) => Promise<unknown>; | ||
|
||
/** | ||
* Re-export mock types | ||
*/ | ||
export * from "@vitest/spy"; | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
export function mock(_moduleName: string, _factory?: MockFactory): void {} | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
export function unmock(_moduleName: string): void {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
export * from "@vitest/spy"; | ||
import type { MockFactory } from "./types.js"; | ||
|
||
// solution found here - https://stackoverflow.com/questions/48674303/resolve-relative-path-to-absolute-url-from-es6-module-in-browser | ||
const a = document.createElement("a"); | ||
function resolveUrl(path: string): string { | ||
a.href = path; | ||
return a.href; | ||
} | ||
|
||
export async function mock(moduleName: string, factory?: MockFactory, originalImport?: unknown): Promise<void> { | ||
// Mock call without factory parameter is handled by manual-mock module and removed from the source code by mock vite plugin | ||
if (!factory || typeof factory !== "function") { | ||
return; | ||
} | ||
|
||
const { file, mockCache } = window.__testplane__; | ||
const isModuleLocal = moduleName.startsWith("/") || moduleName.startsWith("./") || moduleName.startsWith("../"); | ||
|
||
let mockPath: string; | ||
|
||
if (isModuleLocal) { | ||
const absModuleUrl = resolveUrl(file.split("/").slice(0, -1).join("/") + `/${moduleName}`); | ||
mockPath = new URL(absModuleUrl).pathname; | ||
} else { | ||
mockPath = moduleName; | ||
} | ||
|
||
try { | ||
const resolvedMock = await factory(originalImport); | ||
mockCache.set(mockPath, resolvedMock); | ||
} catch (err: unknown) { | ||
const error = err as Error; | ||
throw new Error(`There was an error in mock factory of module "${moduleName}"\n${error.stack}`); | ||
} | ||
} | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
export function unmock(_moduleName: string): void { | ||
// implement in manual-mock module and removed from the source code by mock vite plugin | ||
} |
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import path from "node:path"; | ||
import fs from "node:fs/promises"; | ||
import _ from "lodash"; | ||
import { DEFAULT_AUTOMOCK, DEFAULT_AUTOMOCK_DIRECTORY } from "./constants"; | ||
|
||
import type { InlineConfig } from "vite"; | ||
import type { BrowserTestRunEnvOptions } from "./types"; | ||
|
||
type MockOnFs = { | ||
fullPath: string; | ||
moduleName: string; | ||
}; | ||
|
||
type ManualMockOptions = { | ||
automock: boolean; | ||
mocksOnFs: MockOnFs[]; | ||
}; | ||
|
||
export class ManualMock { | ||
private _automock: boolean; | ||
private _mocksOnFs: MockOnFs[]; | ||
private _mocks: string[]; | ||
private _unmocks: string[]; | ||
|
||
static async create<T extends ManualMock>( | ||
this: new (opts: ManualMockOptions) => T, | ||
config: Partial<InlineConfig>, | ||
options?: BrowserTestRunEnvOptions, | ||
): Promise<T> { | ||
const automock = typeof options?.automock === "boolean" ? options?.automock : DEFAULT_AUTOMOCK; | ||
const automockDir = path.resolve(config?.root || "", options?.automockDir || DEFAULT_AUTOMOCK_DIRECTORY); | ||
const mocksOnFs = await getMocksOnFs(automockDir); | ||
|
||
return new this({ automock, mocksOnFs }); | ||
} | ||
|
||
constructor(options: ManualMockOptions) { | ||
this._automock = options.automock; | ||
this._mocksOnFs = options.mocksOnFs; | ||
this._mocks = []; | ||
this._unmocks = []; | ||
} | ||
|
||
async resolveId(id: string): Promise<string | void> { | ||
const foundMockOnFs = this._mocksOnFs.find(mock => id === mock.moduleName); | ||
|
||
if ((this._mocks.includes(id) || this._automock) && foundMockOnFs && !this._unmocks.includes(id)) { | ||
return foundMockOnFs.fullPath; | ||
} | ||
} | ||
|
||
mock(moduleName: string): void { | ||
this._mocks.push(moduleName); | ||
} | ||
|
||
unmock(moduleName: string): void { | ||
this._unmocks.push(moduleName); | ||
} | ||
|
||
resetMocks(): void { | ||
this._mocks = []; | ||
this._unmocks = []; | ||
} | ||
} | ||
|
||
async function getMocksOnFs(automockDir: string): Promise<{ fullPath: string; moduleName: string }[]> { | ||
const mockedModules = await getFilesFromDirectory(automockDir); | ||
|
||
return mockedModules.map(filePath => { | ||
const extName = path.extname(filePath); | ||
|
||
return { | ||
fullPath: filePath, | ||
moduleName: filePath.slice(automockDir.length + 1, -extName.length), | ||
}; | ||
}); | ||
} | ||
|
||
async function getFilesFromDirectory(dir: string): Promise<string[]> { | ||
const isDirExists = await fs.access(dir).then( | ||
() => true, | ||
() => false, | ||
); | ||
|
||
if (!isDirExists) { | ||
return []; | ||
} | ||
|
||
const files = await fs.readdir(dir); | ||
const allFiles = await Promise.all( | ||
files.map(async (file: string): Promise<string | string[]> => { | ||
const filePath = path.join(dir, file); | ||
const stats = await fs.stat(filePath); | ||
|
||
return stats.isDirectory() ? getFilesFromDirectory(filePath) : filePath; | ||
}), | ||
); | ||
|
||
return _.flatten(allFiles).filter(Boolean) as string[]; | ||
} |
Oops, something went wrong.