Skip to content

[INT-1] visual-snapshots: add ESM support for Jest #206

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 20, 2025
Merged
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
15 changes: 8 additions & 7 deletions visual-js/visual-snapshots/jest.config.cjs
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
// [...]
preset: 'ts-jest/presets/default-esm', // or other ESM presets
preset: "ts-jest/presets/default-esm", // or other ESM presets
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1',
"^(\\.{1,2}/.*)\\.js$": "$1",
},
testPathIgnorePatterns: ['/lib/'],
testPathIgnorePatterns: ["/lib/"],
setupFiles: ["./jest.setup.mjs"],
transform: {
// '^.+\\.[tj]sx?$' to process js/ts with `ts-jest`
// '^.+\\.m?[tj]sx?$' to process js/ts/mjs/mts with `ts-jest`
'^.+\\.tsx?$': [
'ts-jest',
"^.+\\.tsx?$": [
"ts-jest",
{
useESM: true,
tsconfig: 'tsconfig.test.json'
tsconfig: "tsconfig.test.json",
},
],
},
}
};
2 changes: 2 additions & 0 deletions visual-js/visual-snapshots/jest.setup.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { jest } from "@jest/globals";
global.jest = jest;
6 changes: 3 additions & 3 deletions visual-js/visual-snapshots/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
"watch": "rimraf *.tsbuildinfo && tsc -b tsconfig.json -w",
"lint": "eslint \"{src,test}/**/*.ts\"",
"lint-fix": "eslint \"{src,test}/**/*.ts\" --fix",
"test": "jest",
"test-update-snapshots": "jest -u",
"test-with-coverage": "jest --collect-coverage"
"test": "cross-env NODE_NO_WARNINGS=1 NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest",
"test-update-snapshots": "cross-env NODE_NO_WARNINGS=1 NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest -u",
"test-with-coverage": "cross-env NODE_NO_WARNINGS=1 NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest --collect-coverage"
},
"dependencies": {
"@saucelabs/visual": "^0.13.0",
Expand Down
14 changes: 7 additions & 7 deletions visual-js/visual-snapshots/src/api/visual-client.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { getApi, VisualConfig } from "@saucelabs/visual";

export const initializeVisualApi = (
params: VisualConfig,
clientVersion: string
) =>
getApi(params, {
userAgent: `visual-snapshots/${clientVersion}`,
});
export const visualApiInitializer =
(_getApi: typeof getApi) => (params: VisualConfig, clientVersion: string) =>
_getApi(params, {
userAgent: `visual-snapshots/${clientVersion}`,
});

export const initializeVisualApi = visualApiInitializer(getApi);
6 changes: 5 additions & 1 deletion visual-js/visual-snapshots/src/app/pdf-converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ import { pdf } from "pdf-to-img";
import { PdfFile } from "./pdf-file.js";

export class PdfConverter {
constructor(private readonly _pdf: typeof pdf = pdf) {}

public async *convertPagesToImages(
pdfFilePath: string
): AsyncGenerator<Buffer> {
for await (const pdfPageImage of await pdf(pdfFilePath, { scale: 1 })) {
for await (const pdfPageImage of await this._pdf(pdfFilePath, {
scale: 1,
})) {
yield pdfPageImage;
}
}
Expand Down
6 changes: 3 additions & 3 deletions visual-js/visual-snapshots/test/api/visual-api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ describe("VisualSnapshots", () => {
const createSnapshotMock = jest.fn();
const finishBuildMock = jest.fn();
const buildStatusMock = jest.fn();
const visualApiMock: VisualApi = {
...jest.requireActual<VisualApi>("@saucelabs/visual"),
const visualApiMock = {
createBuild: createBuildMock,
uploadSnapshot: uploadSnapshotMock,
createSnapshot: createSnapshotMock,
finishBuild: finishBuildMock,
buildStatus: buildStatusMock,
};
} as never as VisualApi;

const visualSnapshots = new VisualSnapshotsApi(visualApiMock);
const files = [new TestPdfFile("file1.pdf"), new TestPdfFile("file2.pdf")];

Expand Down
7 changes: 4 additions & 3 deletions visual-js/visual-snapshots/test/api/visual-client.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { initializeVisualApi } from "../../src/api/visual-client.js";
import { visualApiInitializer } from "../../src/api/visual-client.js";
import * as sauceVisual from "@saucelabs/visual";

jest.mock("@saucelabs/visual", () => {
Expand All @@ -8,8 +8,9 @@ jest.mock("@saucelabs/visual", () => {
});

describe("visual api client", () => {
test("initializeVisualApi", async () => {
const getApiSpy = sauceVisual.getApi;
test("visualApiInitializer", async () => {
const getApiSpy = jest.fn();
const initializeVisualApi = visualApiInitializer(getApiSpy);

const pkgVersion = "0.1.0";
const params = {
Expand Down
57 changes: 32 additions & 25 deletions visual-js/visual-snapshots/test/app/pdf-converter.spec.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,42 @@
import path from "path";
import { PdfConverter } from "../../src/app/pdf-converter.js";
import { __dirname } from "../helpers.js";

jest.mock("pdf-to-img", () => {
async function* asyncIterable() {
for (let i = 0; i < 2; i++) {
yield `fake-image-buffer-${i}`;
describe("PdfConverter", () => {
test("convertPagesToImages", async () => {
const pdf = jest.fn().mockResolvedValue([]);

const pdfFilePath = "./fake-pdf-file-path.pdf";
const pdfConverter = new PdfConverter(pdf);
for await (const _ of pdfConverter.convertPagesToImages(pdfFilePath)) {
}
}

return {
pdf: asyncIterable,
};
});
expect(pdf).toHaveBeenCalledWith(pdfFilePath, { scale: 1 });
});

describe("PdfConverter", () => {
test("convertPagesToImages", async () => {
const pdfFilePath = "./fake-pdf-file-path.pdf";
const pdfFilePath = path.join(__dirname(import.meta), "../files/test.pdf");
const pdfConverter = new PdfConverter();
const pdfPageImagesGenerator = await pdfConverter.convertPagesToImages(
pdfFilePath
);

for (let i = 0; i < 2; ++i) {
const pdfPageImage = await pdfPageImagesGenerator.next();
expect(pdfPageImage).toEqual({
done: false,
value: `fake-image-buffer-${i}`,
});

let pages = 0;
for await (const _ of pdfConverter.convertPagesToImages(pdfFilePath)) {
pages++;
}
expect(await pdfPageImagesGenerator.next()).toEqual({
done: true,
value: undefined,
});

expect(pages).toEqual(3);
});

test("createPdfFile", async () => {
const pdf = jest.fn().mockResolvedValue([]);
const pdfConverter = new PdfConverter(pdf);

const pdfFilePath = "./fake-pdf-file-path.pdf";
const pdfFile = pdfConverter.createPdfFile(pdfFilePath);

for await (const _ of pdfFile.convertPagesToImages()) {
}

expect(pdfFile.path).toBe(pdfFilePath);
expect(pdf).toHaveBeenCalledWith(pdfFilePath, { scale: 1 });
});
});
Binary file added visual-js/visual-snapshots/test/files/test.pdf
Binary file not shown.
16 changes: 16 additions & 0 deletions visual-js/visual-snapshots/test/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { dirname } from "node:path";
import { fileURLToPath } from "node:url";

/**
* ESM helper for getting __filename. Pass `import.meta` to this function.
* @param meta `import.meta`
* @returns __filename equivalent
*/
export const __filename = (meta: ImportMeta) => fileURLToPath(meta.url);

/**
* ESM helper for getting __dirname. Pass `import.meta` to this function.
* @param meta `import.meta`
* @returns __dirname equivalent
*/
export const __dirname = (meta: ImportMeta) => dirname(__filename(meta));
40 changes: 22 additions & 18 deletions visual-js/visual-snapshots/test/utils/glob.spec.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,60 @@
import { getFiles } from "../../src/utils/glob.js";
import path from "path";
import { __dirname, __filename } from "../helpers.js";

describe("getFiles", () => {
function resolvePath(p: string) {
return path.resolve(p);
function normalize(paths: string[]) {
return paths.map((p) => path.resolve(p)).sort((a, b) => a.localeCompare(b));
}

it("should return a file", async () => {
const input = ["./src/index.ts"];
const expected = input.map(resolvePath);
const expected = normalize(input);

const result = await getFiles(input, "*");
expect(result.map(resolvePath)).toEqual(expected);
expect(normalize(result)).toEqual(expected);
});

it("should return multiple files", async () => {
const input = ["./src/index.ts", __filename];
const expected = input.map(resolvePath);
const input = ["./src/index.ts", __filename(import.meta)];
const expected = normalize(input);

const actual = await getFiles(input, "*");
expect(actual.map(resolvePath)).toEqual(expected);
expect(normalize(actual)).toEqual(expected);
});

it("should return files matched by glob", async () => {
const input = [path.join(__dirname, "*.spec.ts")];
const expected = [resolvePath(__filename)];
const input = [path.join(__dirname(import.meta), "*.spec.ts")];
const expected = normalize([__filename(import.meta)]);

const actual = await getFiles(input, "*");
expect(actual.map(resolvePath)).toEqual(expect.arrayContaining(expected));
expect(normalize(actual)).toEqual(expect.arrayContaining(expected));
});

it("should return files in directory matched by dir glob", async () => {
const input = [__dirname];
const expected = [resolvePath(__filename)];
const input = [__dirname(import.meta)];
const expected = normalize([__filename(import.meta)]);

const actual = await getFiles(input, "*.spec.ts");
expect(actual.map(resolvePath)).toEqual(expect.arrayContaining(expected));
expect(normalize(actual)).toEqual(expect.arrayContaining(expected));
});

it("should not return non-existing files", async () => {
const input = [__filename, __filename + ".not-existing"];
const expected = [resolvePath(__filename)];
const input = [
__filename(import.meta),
__filename(import.meta) + ".not-existing",
];
const expected = normalize([__filename(import.meta)]);

const result = await getFiles(input, "*");
expect(result.map(resolvePath)).toEqual(expected);
expect(normalize(result)).toEqual(expected);
});

it("should not return files from not existing dirs", async () => {
const input = [__dirname + ".not-existing"];
const input = [__dirname(import.meta) + ".not-existing"];
const expected: string[] = [];

const result = await getFiles(input, "*");
expect(result.map(resolvePath)).toEqual(expected);
expect(normalize(result)).toEqual(expected);
});
});
Loading