Skip to content

Commit

Permalink
Upgrade the jest test suite to match the clients (#379)
Browse files Browse the repository at this point in the history
* Upgrade the jest test suite to match the clients

* Update makeStaticByteArray
  • Loading branch information
Hinton authored Dec 12, 2023
1 parent 36ab295 commit 1546cc2
Show file tree
Hide file tree
Showing 24 changed files with 379 additions and 107 deletions.
27 changes: 27 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const { pathsToModuleNameMapper } = require("ts-jest");

const { compilerOptions } = require("./tsconfig");

/** @type {import('jest').Config} */
module.exports = {
reporters: ["default", "jest-junit"],

collectCoverage: true,
coverageReporters: ["html", "lcov"],
coverageDirectory: "coverage",

moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, {
prefix: "<rootDir>/",
}),
projects: [
"<rootDir>/jslib/angular/jest.config.js",
"<rootDir>/jslib/common/jest.config.js",
"<rootDir>/jslib/electron/jest.config.js",
"<rootDir>/jslib/node/jest.config.js",
],

// Workaround for a memory leak that crashes tests in CI:
// https://github.com/facebook/jest/issues/9430#issuecomment-1149882002
// Also anecdotally improves performance when run locally
maxWorkers: 3,
};
17 changes: 8 additions & 9 deletions jslib/angular/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
const { pathsToModuleNameMapper } = require("ts-jest/utils");
const { pathsToModuleNameMapper } = require("ts-jest");

const { compilerOptions } = require("./tsconfig");
const { compilerOptions } = require("../shared/tsconfig.libs");

const sharedConfig = require("../shared/jest.config.angular");

/** @type {import('jest').Config} */
module.exports = {
name: "angular",
displayName: "angular tests",
...sharedConfig,
displayName: "libs/angular tests",
preset: "jest-preset-angular",
testMatch: ["**/+(*.)+(spec).+(ts)"],
setupFilesAfterEnv: ["<rootDir>/spec/test.ts"],
collectCoverage: true,
coverageReporters: ["html", "lcov"],
coverageDirectory: "coverage",
setupFilesAfterEnv: ["<rootDir>/test.setup.ts"],
moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, {
prefix: "<rootDir>/",
}),
Expand Down
28 changes: 28 additions & 0 deletions jslib/angular/test.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { webcrypto } from "crypto";
import "jest-preset-angular/setup-jest";

Object.defineProperty(window, "CSS", { value: null });
Object.defineProperty(window, "getComputedStyle", {
value: () => {
return {
display: "none",
appearance: ["-webkit-appearance"],
};
},
});

Object.defineProperty(document, "doctype", {
value: "<!DOCTYPE html>",
});
Object.defineProperty(document.body.style, "transform", {
value: () => {
return {
enumerable: true,
configurable: true,
};
},
});

Object.defineProperty(window, "crypto", {
value: webcrypto,
});
17 changes: 8 additions & 9 deletions jslib/common/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
const { pathsToModuleNameMapper } = require("ts-jest/utils");
const { pathsToModuleNameMapper } = require("ts-jest");

const { compilerOptions } = require("./tsconfig");
const { compilerOptions } = require("../shared/tsconfig.libs");

const sharedConfig = require("../shared/jest.config.ts");

/** @type {import('jest').Config} */
module.exports = {
name: "common",
displayName: "common jslib tests",
...sharedConfig,
displayName: "libs/common tests",
preset: "ts-jest",
testEnvironment: "jsdom",
testMatch: ["**/+(*.)+(spec).+(ts)"],
setupFilesAfterEnv: ["<rootDir>/spec/test.ts"],
collectCoverage: true,
coverageReporters: ["html", "lcov"],
coverageDirectory: "coverage",
setupFilesAfterEnv: ["<rootDir>/test.setup.ts"],
moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, {
prefix: "<rootDir>/",
}),
Expand Down
1 change: 1 addition & 0 deletions jslib/common/spec/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./matchers";
1 change: 1 addition & 0 deletions jslib/common/spec/matchers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./to-equal-buffer";
25 changes: 25 additions & 0 deletions jslib/common/spec/matchers/to-equal-buffer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { makeStaticByteArray } from "../utils";

describe("toEqualBuffer custom matcher", () => {
it("matches identical ArrayBuffers", () => {
const array = makeStaticByteArray(10);
expect(array.buffer).toEqualBuffer(array.buffer);
});

it("matches an identical ArrayBuffer and Uint8Array", () => {
const array = makeStaticByteArray(10);
expect(array.buffer).toEqualBuffer(array);
});

it("doesn't match different ArrayBuffers", () => {
const array1 = makeStaticByteArray(10);
const array2 = makeStaticByteArray(10, 11);
expect(array1.buffer).not.toEqualBuffer(array2.buffer);
});

it("doesn't match a different ArrayBuffer and Uint8Array", () => {
const array1 = makeStaticByteArray(10);
const array2 = makeStaticByteArray(10, 11);
expect(array1.buffer).not.toEqualBuffer(array2);
});
});
31 changes: 31 additions & 0 deletions jslib/common/spec/matchers/to-equal-buffer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* The inbuilt toEqual() matcher will always return TRUE when provided with 2 ArrayBuffers.
* This is because an ArrayBuffer must be wrapped in a new Uint8Array to be accessible.
* This custom matcher will automatically instantiate a new Uint8Array on the received value
* (and optionally, the expected value) and then call toEqual() on the resulting Uint8Arrays.
*/
export const toEqualBuffer: jest.CustomMatcher = function (
received: ArrayBuffer | Uint8Array,
expected: ArrayBuffer | Uint8Array,
) {
received = new Uint8Array(received);
expected = new Uint8Array(expected);

if (this.equals(received, expected)) {
return {
message: () => `expected
${received}
not to match
${expected}`,
pass: true,
};
}

return {
message: () => `expected
${received}
to match
${expected}`,
pass: false,
};
};
4 changes: 2 additions & 2 deletions jslib/common/spec/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ export function mockEnc(s: string): EncString {
return mock;
}

export function makeStaticByteArray(length: number) {
export function makeStaticByteArray(length: number, start = 0) {
const arr = new Uint8Array(length);
for (let i = 0; i < length; i++) {
arr[i] = i;
arr[i] = start + i;
}
return arr;
}
17 changes: 17 additions & 0 deletions jslib/common/test.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { webcrypto } from "crypto";

import { toEqualBuffer } from "./spec";

Object.defineProperty(window, "crypto", {
value: webcrypto,
});

// Add custom matchers

expect.extend({
toEqualBuffer: toEqualBuffer,
});

export interface CustomMatchers<R = unknown> {
toEqualBuffer(expected: Uint8Array | ArrayBuffer): R;
}
17 changes: 9 additions & 8 deletions jslib/electron/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
const { pathsToModuleNameMapper } = require("ts-jest/utils");
const { pathsToModuleNameMapper } = require("ts-jest");

const { compilerOptions } = require("./tsconfig");
const { compilerOptions } = require("../shared/tsconfig.libs");

const sharedConfig = require("../shared/jest.config.ts");

/** @type {import('jest').Config} */
module.exports = {
...sharedConfig,
displayName: "libs/electron tests",
preset: "ts-jest",
testEnvironment: "jsdom",
testMatch: ["**/+(*.)+(spec).+(ts)"],
setupFilesAfterEnv: ["<rootDir>/spec/test.ts"],
collectCoverage: true,
coverageReporters: ["html", "lcov"],
coverageDirectory: "coverage",
testEnvironment: "node",
setupFilesAfterEnv: ["<rootDir>/test.setup.ts"],
moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, {
prefix: "<rootDir>/",
}),
Expand Down
Empty file added jslib/electron/test.setup.ts
Empty file.
18 changes: 0 additions & 18 deletions jslib/jest.config.js

This file was deleted.

16 changes: 9 additions & 7 deletions jslib/node/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
const { pathsToModuleNameMapper } = require("ts-jest/utils");
const { pathsToModuleNameMapper } = require("ts-jest");

const { compilerOptions } = require("./tsconfig");
const { compilerOptions } = require("../shared/tsconfig.libs");

const sharedConfig = require("../shared/jest.config.ts");

/** @type {import('jest').Config} */
module.exports = {
...sharedConfig,
displayName: "libs/node tests",
preset: "ts-jest",
testMatch: ["**/+(*.)+(spec).+(ts)"],
setupFilesAfterEnv: ["<rootDir>/spec/test.ts"],
collectCoverage: true,
coverageReporters: ["html", "lcov"],
coverageDirectory: "coverage",
testEnvironment: "node",
setupFilesAfterEnv: ["<rootDir>/test.setup.ts"],
moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, {
prefix: "<rootDir>/",
}),
Expand Down
Empty file added jslib/node/test.setup.ts
Empty file.
13 changes: 0 additions & 13 deletions jslib/package-lock.json

This file was deleted.

22 changes: 0 additions & 22 deletions jslib/package.json

This file was deleted.

36 changes: 36 additions & 0 deletions jslib/shared/es2020-transformer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as ts from "typescript";

// Custom Typescript AST transformer for use with ts-jest / jest-preset-angular
// Removes specified ES2020 syntax from source code, as node does not support it yet
// Reference: https://kulshekhar.github.io/ts-jest/docs/getting-started/options/astTransformers
// Use this tool to understand how we identify and filter AST nodes: https://ts-ast-viewer.com/

/**
* Remember to increase the version whenever transformer's content is changed. This is to inform Jest to not reuse
* the previous cache which contains old transformer's content
*/
export const version = 1;
export const name = "bit-es2020-transformer";

// Returns true for 'import.meta' statements
const isImportMetaStatement = (node: ts.Node) =>
ts.isPropertyAccessExpression(node) &&
ts.isMetaProperty(node.expression) &&
node.expression.keywordToken === ts.SyntaxKind.ImportKeyword;

export const factory = function (/*opts?: Opts*/) {
function visitor(ctx: ts.TransformationContext, sf: ts.SourceFile) {
const visitor: ts.Visitor = (node: ts.Node): ts.VisitResult<any> => {
if (isImportMetaStatement(node)) {
return null;
}

// Continue searching child nodes
return ts.visitEachChild(node, visitor, ctx);
};
return visitor;
}
return (ctx: ts.TransformationContext): ts.Transformer<any> => {
return (sf: ts.SourceFile) => ts.visitNode(sf, visitor(ctx, sf));
};
};
32 changes: 32 additions & 0 deletions jslib/shared/jest.config.angular.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/* eslint-env node */
/* eslint-disable @typescript-eslint/no-var-requires */
const { defaultTransformerOptions } = require("jest-preset-angular/presets");

/** @type {import('jest').Config} */
module.exports = {
testMatch: ["**/+(*.)+(spec).+(ts)"],

// Workaround for a memory leak that crashes tests in CI:
// https://github.com/facebook/jest/issues/9430#issuecomment-1149882002
// Also anecdotally improves performance when run locally
maxWorkers: 3,

transform: {
"^.+\\.(ts|js|mjs|svg)$": [
"jest-preset-angular",
{
...defaultTransformerOptions,
// Jest does not use tsconfig.spec.json by default
tsconfig: "<rootDir>/tsconfig.spec.json",
// Further workaround for memory leak, recommended here:
// https://github.com/kulshekhar/ts-jest/issues/1967#issuecomment-697494014
// Makes tests run faster and reduces size/rate of leak, but loses typechecking on test code
// See https://bitwarden.atlassian.net/browse/EC-497 for more info
isolatedModules: true,
astTransformers: {
before: ["<rootDir>/../../jslib/shared/es2020-transformer.ts"],
},
},
],
},
};
Loading

0 comments on commit 1546cc2

Please sign in to comment.