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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ coverage
.vscode
package-lock.json
*tsbuildinfo
/types/index.d.ts
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,27 @@
"version": "5.0.3",
"description": "Additional Jest matchers",
"main": "dist/index.js",
"types": "types/index.d.ts",
"files": [
"dist",
"types/index.d.ts",
"all.js"
],
"scripts": {
"clean": "node clean.js",
"clean": "node scripts/clean.js",
"prebuild": "yarn clean",
"build": "tsc && tsc-alias",
"postbuild": "yarn generate-types",
"generate-types": "ts-node scripts/generate-types.ts",
"lint": "eslint .",
"lint:fix": "yarn lint --fix",
"prepare": "husky",
"prepublishOnly": "yarn build",
"precommit": "lint-staged",
"pretest": "yarn generate-types",
"test": "jest --color=true",
"test:clearCache": "yarn test --clearCache",
"test:updateSnapshot": "yarn test --updateSnapshot",
"pretest:coverage": "yarn generate-types",
"test:coverage": "yarn test --coverage",
"test:watch": "yarn test --watch",
"typecheck": "tsc --noEmit",
Expand Down Expand Up @@ -61,6 +64,7 @@
"lint-staged": "~15.5.0",
"prettier": "^3.0.0",
"ts-jest": "^29.0.0",
"ts-node": "^10.9.2",
"tsc-alias": "^1.8.0",
"typescript": "^5.0.0"
},
Expand Down
2 changes: 1 addition & 1 deletion clean.js → scripts/clean.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const fs = require('fs');
const path = require('path');
const distPath = path.join(__dirname, 'dist');
const distPath = path.join(__dirname, '..', 'dist');
if (fs.existsSync(distPath)) {
fs.rmSync(distPath, { recursive: true, force: true });
}
141 changes: 141 additions & 0 deletions scripts/generate-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import * as ts from 'typescript';

Check warning on line 1 in scripts/generate-types.ts

View workflow job for this annotation

GitHub Actions / Run ESLint & TypeScript compiler

`typescript` import should occur after import of `path`
import * as fs from 'fs';
import * as path from 'path';

interface MatcherInfo {
name: string;
docComment: string;
parameters: {
name: string;
type: string;
optional?: boolean;
rest?: boolean;
}[];
returnType: string;
}

function extractMatcherInfo(sourceFile: ts.SourceFile): MatcherInfo[] {
const matchers: MatcherInfo[] = [];
ts.forEachChild(sourceFile, node => {
if (ts.isFunctionDeclaration(node) && node.name) {
const matcherName = node.name.text;
// Extract the full JSDoc block (not just tags)
let docComment = '';
const jsDocs = (node as any).jsDoc;
if (jsDocs && jsDocs.length > 0 && jsDocs[0].comment) {
docComment = jsDocs[0].comment;
// If there are tags, add them as well
if (jsDocs[0].tags && jsDocs[0].tags.length > 0) {
const tags = jsDocs[0].tags.map((tag: any) => {
let tagLine = `@${tag.tagName.escapedText}`;
if (tag.typeExpression && tag.typeExpression.type) {
tagLine += ` {${tag.typeExpression.type.getText()}}`;
}
if (tag.name) tagLine += ` ${tag.name.escapedText}`;
if (tag.comment) tagLine += ` ${tag.comment}`;
return tagLine;
});
docComment += '\n' + tags.join('\n');
}
}
// Skip the first parameter (actual) as it's implicit in Jest matchers
const parameters = node.parameters.slice(1).map(param => {
const paramName = param.name.getText(sourceFile);
const paramType = param.type ? param.type.getText(sourceFile) : 'unknown';
// Check if parameter is optional (has default value or is marked with ?)
const isOptional = param.initializer !== undefined || param.questionToken !== undefined;
// Check if parameter is a rest parameter
const isRest = param.dotDotDotToken !== undefined;
return {

Check failure on line 49 in scripts/generate-types.ts

View workflow job for this annotation

GitHub Actions / Run ESLint & TypeScript compiler

Delete `·`

Check warning on line 49 in scripts/generate-types.ts

View workflow job for this annotation

GitHub Actions / Run ESLint & TypeScript compiler

Trailing spaces not allowed
name: paramName,

Check failure on line 50 in scripts/generate-types.ts

View workflow job for this annotation

GitHub Actions / Run ESLint & TypeScript compiler

Delete `·`

Check warning on line 50 in scripts/generate-types.ts

View workflow job for this annotation

GitHub Actions / Run ESLint & TypeScript compiler

Trailing spaces not allowed
type: paramType,

Check failure on line 51 in scripts/generate-types.ts

View workflow job for this annotation

GitHub Actions / Run ESLint & TypeScript compiler

Delete `·`

Check warning on line 51 in scripts/generate-types.ts

View workflow job for this annotation

GitHub Actions / Run ESLint & TypeScript compiler

Trailing spaces not allowed
optional: isOptional,
rest: isRest

Check failure on line 53 in scripts/generate-types.ts

View workflow job for this annotation

GitHub Actions / Run ESLint & TypeScript compiler

Replace `·` with `,`

Check warning on line 53 in scripts/generate-types.ts

View workflow job for this annotation

GitHub Actions / Run ESLint & TypeScript compiler

Trailing spaces not allowed
};
});
const returnType = node.type ? node.type.getText(sourceFile) : 'unknown';
matchers.push({ name: matcherName, docComment, parameters, returnType });
}
});
return matchers;
}

function generateTypeDefinition(matcher: MatcherInfo): string {
// Split docComment into lines, trim, and wrap in JSDoc
let docBlock = '';
if (matcher.docComment && matcher.docComment.trim().length > 0) {
const lines = matcher.docComment.split('\n').map(line => ` * ${line.trim()}`);
docBlock = [' /**', ...lines, ' */'].join('\n');
}
const params = matcher.parameters.map(p => {

Check failure on line 70 in scripts/generate-types.ts

View workflow job for this annotation

GitHub Actions / Run ESLint & TypeScript compiler

Insert `⏎····`
const prefix = p.rest ? '...' : '';

Check failure on line 71 in scripts/generate-types.ts

View workflow job for this annotation

GitHub Actions / Run ESLint & TypeScript compiler

Insert `··`
const suffix = p.optional ? '?' : '';

Check failure on line 72 in scripts/generate-types.ts

View workflow job for this annotation

GitHub Actions / Run ESLint & TypeScript compiler

Insert `··`
return `${prefix}${p.name}${suffix}: ${p.type}`;

Check failure on line 73 in scripts/generate-types.ts

View workflow job for this annotation

GitHub Actions / Run ESLint & TypeScript compiler

Insert `··`
}).join(', ');

Check failure on line 74 in scripts/generate-types.ts

View workflow job for this annotation

GitHub Actions / Run ESLint & TypeScript compiler

Replace `})` with `··})⏎····`

Check failure on line 75 in scripts/generate-types.ts

View workflow job for this annotation

GitHub Actions / Run ESLint & TypeScript compiler

Delete `··`

Check warning on line 75 in scripts/generate-types.ts

View workflow job for this annotation

GitHub Actions / Run ESLint & TypeScript compiler

Trailing spaces not allowed
// Check if the function uses the E type parameter
const needsGenericE = matcher.parameters.some(p => p.type.includes('E')) ||

Check warning on line 77 in scripts/generate-types.ts

View workflow job for this annotation

GitHub Actions / Run ESLint & TypeScript compiler

Trailing spaces not allowed
matcher.returnType.includes('E');

Check warning on line 79 in scripts/generate-types.ts

View workflow job for this annotation

GitHub Actions / Run ESLint & TypeScript compiler

Trailing spaces not allowed
// Add generic type parameter if needed
const genericParams = needsGenericE ? '<E>' : '';

Check warning on line 82 in scripts/generate-types.ts

View workflow job for this annotation

GitHub Actions / Run ESLint & TypeScript compiler

Trailing spaces not allowed
// Add two newlines after each method for clarity
return `\n${docBlock}\n ${matcher.name}${genericParams}(${params}): R;\n`;
}

function generateTypeFile(matchers: MatcherInfo[]): string {
return `interface CustomMatchers<R> extends Record<string, any> {${matchers
.map(generateTypeDefinition)
.join('')}
}

declare namespace jest {
interface Matchers<R> {${matchers
.map(generateTypeDefinition)
.join('')}
}

interface Expect extends CustomMatchers<any> {}
interface InverseAsymmetricMatchers extends Expect {}
}

declare module 'jest-extended' {
const matchers: CustomMatchers<any>;
export = matchers;
}`;
}

function main() {
const matchersDir = path.join(__dirname, '../src/matchers');
const typesDir = path.join(__dirname, '../types');
const outputFile = path.join(typesDir, 'index.d.ts');

// Read all matcher files
const matcherFiles = fs.readdirSync(matchersDir)
.filter(file => file.endsWith('.ts') && file !== 'index.ts');

const matchers: MatcherInfo[] = [];

// Process each matcher file
for (const file of matcherFiles) {
const filePath = path.join(matchersDir, file);
const sourceFile = ts.createSourceFile(
filePath,
fs.readFileSync(filePath, 'utf8').replace(/\r/g, ''),
ts.ScriptTarget.Latest,
true
);

const matcherInfos = extractMatcherInfo(sourceFile);
matchers.push(...matcherInfos);
}

// Generate and write type definitions
const typeDefinitions = generateTypeFile(matchers);
fs.mkdirSync(typesDir, { recursive: true });
fs.writeFileSync(outputFile, typeDefinitions);
console.log(`Generated type definitions in ${outputFile}`);
}

main();

Check warning on line 141 in scripts/generate-types.ts

View workflow job for this annotation

GitHub Actions / Run ESLint & TypeScript compiler

Trailing spaces not allowed
6 changes: 6 additions & 0 deletions src/matchers/fail.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
/**
* Note: Currently unimplemented
* Failing assertion
*
* @param {String} message
*/
export function fail(_: unknown, message: string) {
return {
pass: false,
Expand Down
6 changes: 6 additions & 0 deletions src/matchers/pass.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
/**
* Note: Currently unimplemented
* Passing assertion
*
* @param {String} message
*/
export function pass(_: unknown, message: string) {
return {
pass: true,
Expand Down
4 changes: 4 additions & 0 deletions src/matchers/toBeAfter.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/**
* Use `.toBeAfter` when checking if a date occurs after `date`.
* @param {Date} after
*/
export function toBeAfter(actual: unknown, after: Date) {
// @ts-expect-error OK to have implicit any for this.utils
const { printReceived, matcherHint } = this.utils;
Expand Down
4 changes: 4 additions & 0 deletions src/matchers/toBeAfterOrEqualTo.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/**
* Use `.toBeAfterOrEqualTo` when checking if a date equals to or occurs after `date`.
* @param {Date} date
*/
export function toBeAfterOrEqualTo(actual: unknown, expected: Date) {
// @ts-expect-error OK to have implicit any for this.utils
const { printReceived, matcherHint } = this.utils;
Expand Down
11 changes: 7 additions & 4 deletions src/matchers/toBeArray.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
export function toBeArray(expected: unknown) {
/**
* Use `.toBeArray` when checking if a value is an `Array`.
*/
export function toBeArray(actual: unknown) {
// @ts-expect-error OK to have implicit any for this.utils
const { matcherHint, printReceived } = this.utils;

const pass = Array.isArray(expected);
const pass = Array.isArray(actual);

return {
pass,
Expand All @@ -11,10 +14,10 @@ export function toBeArray(expected: unknown) {
? matcherHint('.not.toBeArray', 'received', '') +
'\n\n' +
'Expected value to not be an array received:\n' +
` ${printReceived(expected)}`
` ${printReceived(actual)}`
: matcherHint('.toBeArray', 'received', '') +
'\n\n' +
'Expected value to be an array received:\n' +
` ${printReceived(expected)}`,
` ${printReceived(actual)}`,
};
}
4 changes: 4 additions & 0 deletions src/matchers/toBeArrayOfSize.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { determinePropertyMessage } from 'src/utils';

/**
* Use `.toBeArrayOfSize` when checking if a value is an `Array` of size x.
* @param {Number} expected
*/
export function toBeArrayOfSize(actual: unknown, expected: number) {
// @ts-expect-error OK to have implicit any for this.utils
const { printExpected, printReceived, matcherHint } = this.utils;
Expand Down
3 changes: 3 additions & 0 deletions src/matchers/toBeBefore.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* Use `.toBeBefore` when checking if a date occurs before `date`* @param {Date} expected
*/
export function toBeBefore(actual: unknown, expected: Date) {
// @ts-expect-error OK to have implicit any for this.utils
const { matcherHint, printReceived } = this.utils;
Expand Down
4 changes: 4 additions & 0 deletions src/matchers/toBeBeforeOrEqualTo.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/**
* Use `.toBeBeforeOrEqualTo` when checking if a date equals to or occurs before `date`.
* @param {Date} date
*/
export function toBeBeforeOrEqualTo(actual: unknown, expected: Date) {
// @ts-expect-error OK to have implicit any for this.utils
const { matcherHint, printReceived } = this.utils;
Expand Down
5 changes: 5 additions & 0 deletions src/matchers/toBeBetween.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* Use `.toBeBetween` when checking if a date occurs between `startDate` and `endDate`.
* @param {Date} startDate
* @param {Date} endDate
*/
export function toBeBetween(actual: unknown, startDate: Date, endDate: Date) {
// @ts-expect-error OK to have implicit any for this.utils
const { matcherHint, printReceived } = this.utils;
Expand Down
3 changes: 3 additions & 0 deletions src/matchers/toBeBoolean.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* Use `.toBeBoolean` when checking if a value is a `Boolean`.
*/
export function toBeBoolean(actual: unknown) {
// @ts-expect-error OK to have implicit any for this.utils
const { matcherHint, printReceived } = this.utils;
Expand Down
3 changes: 3 additions & 0 deletions src/matchers/toBeDate.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* Use `.toBeDate` when checking if a value is a `Date`.
*/
export function toBeDate(actual: unknown) {
// @ts-expect-error OK to have implicit any for this.utils
const { matcherHint, printReceived } = this.utils;
Expand Down
3 changes: 3 additions & 0 deletions src/matchers/toBeDateString.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* Use `.toBeDateString` when checking if a value is a valid date string.
*/
export function toBeDateString(actual: unknown) {
// @ts-expect-error OK to have implicit any for this.utils
const { matcherHint, printReceived } = this.utils;
Expand Down
3 changes: 3 additions & 0 deletions src/matchers/toBeEmpty.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* Use .toBeEmpty when checking if a String '', Array [], Object {} is empty.
*/
export function toBeEmpty(actual: unknown) {
// @ts-expect-error OK to have implicit any for this.utils
const { printReceived, matcherHint } = this.utils;
Expand Down
3 changes: 3 additions & 0 deletions src/matchers/toBeEmptyObject.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* Use `.toBeEmptyObject` when checking if a value is an empty `Object`.
*/
export function toBeEmptyObject(actual: unknown) {
// @ts-expect-error OK to have implicit any for this.utils
const { printReceived, matcherHint } = this.utils;
Expand Down
3 changes: 3 additions & 0 deletions src/matchers/toBeEven.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* Use `.toBeEven` when checking if a value is an even `Number`.
*/
export function toBeEven(actual: unknown) {
// @ts-expect-error OK to have implicit any for this.utils
const { printReceived, matcherHint } = this.utils;
Expand Down
3 changes: 3 additions & 0 deletions src/matchers/toBeExtensible.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* Use `.toBeExtensible` when checking if an object is extensible.
*/
export function toBeExtensible(actual: unknown) {
// @ts-expect-error OK to have implicit any for this.utils
const { matcherHint, printExpected, printReceived } = this.utils;
Expand Down
3 changes: 3 additions & 0 deletions src/matchers/toBeFalse.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* Use `.toBeFalse` when checking a value is equal (===) to `false`.
*/
export function toBeFalse(actual: unknown) {
// @ts-expect-error OK to have implicit any for this.utils
const { printReceived, matcherHint, printExpected } = this.utils;
Expand Down
3 changes: 3 additions & 0 deletions src/matchers/toBeFinite.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* Use `.toBeFinite` when checking if a value is a `Number`, not `NaN` or `Infinity`.
*/
export function toBeFinite(actual: unknown) {
// @ts-expect-error OK to have implicit any for this.utils
const { printReceived, matcherHint } = this.utils;
Expand Down
3 changes: 3 additions & 0 deletions src/matchers/toBeFrozen.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* Use `.toBeFrozen` when checking if an object is frozen.
*/
export function toBeFrozen(actual: unknown) {
// @ts-expect-error OK to have implicit any for this.utils
const { matcherHint } = this.utils;
Expand Down
3 changes: 3 additions & 0 deletions src/matchers/toBeFunction.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* Use `.toBeFunction` when checking if a value is a `Function`.
*/
export function toBeFunction(actual: unknown) {
// @ts-expect-error OK to have implicit any for this.utils
const { printReceived, matcherHint } = this.utils;
Expand Down
3 changes: 3 additions & 0 deletions src/matchers/toBeHexadecimal.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* Use `.toBeHexadecimal` when checking if a value is a valid HTML hex color.
*/
export function toBeHexadecimal(actual: unknown) {
// @ts-expect-error OK to have implicit any for this.utils
const { printReceived, matcherHint } = this.utils;
Expand Down
6 changes: 6 additions & 0 deletions src/matchers/toBeInRange.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
/**
* Use `.toBeInRange` when checking if an array has elements in range min (inclusive) and max (exclusive).
*
* @param min
* @param max
*/
export function toBeInRange(actual: unknown[], min: number, max: number) {
// @ts-expect-error OK to have implicit any for this.utils
const { printReceived, printExpected, matcherHint } = this.utils;
Expand Down
3 changes: 3 additions & 0 deletions src/matchers/toBeInteger.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* Use `.toBeInteger` when checking if a value is an integer.
*/
export function toBeInteger(actual: unknown) {
// @ts-expect-error OK to have implicit any for this.utils
const { printReceived, matcherHint } = this.utils;
Expand Down
3 changes: 3 additions & 0 deletions src/matchers/toBeNaN.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* Use `.toBeNaN` when checking a value is `NaN`.
*/
export function toBeNaN(actual: any) {
// @ts-expect-error OK to have implicit any for this.utils
const { printReceived, matcherHint } = this.utils;
Expand Down
Loading
Loading