jest-extended
jest-extended copied to clipboard
Typescript error when manually import the matchers
Bug
jest-extended version: 1.2.0
Problem
Currently jest-extended types don't export any of the matchers, so it ends up causing an error with the typescript.
We managed to solve this by declaring a module in the types
declare module "jest-extended" {
export function toBeAfter(received: Date, after: Date): jest.CustomMatcherResult;
}
But doing it this way brings another problem that would be related to types, all manual imports that we are not using will be typed and will cause runtime issues
I did some tests to try to solve this problem and a possible solution, but it still wouldn't be a good solution for this, it would be to move each declaration type to its own folder and import the file directly from the dist
- get declaration type for matcher
//@file: types/index.d.ts declare namespace jest { // noinspection JSUnusedGlobalSymbols interface Matchers<R> { /** * Note: Currently unimplemented * Passing assertion * * @param {String} message */ pass(message: string): R; ... } }
- moved to src/matchers/pass/index.d.ts:
// @file: src/matchers/pass/index.d.ts <- not need this line /// <reference types="jest" /> declare global { namespace jest { interface Matchers<R> { /** * Note: Currently unimplemented * Passing assertion * * @param {String} message */ pass(message?: string): never; } interface Expect { /** * Note: Currently unimplemented * Passing assertion * * @param {String} message */ pass(message?: string): void; } } } export declare function pass(expected: string, message?: string): jest.CustomMatcherResult;
- use in setup.ts
import { pass } from 'jest-extended/dist/matchers/pass' expect.extend({ pass })
I couldn't find another solution for this, as the behavior of the typescript consists of every js file must accompany its declaration file, if we use for example the strategy of re-exporting all files in an index.js, and importing only what we need, The typescript will follow all re-exported files and find declaration files (*.d.ts), which will have declaration type that you didn't import and merges with global jest -> expect/matchers.
I managed to make the manual import work with the correct types.
I separated the matcher declaration as above and added the declarations for the typescript to start understanding exports
- In src/matchers/index.d.ts
export { matcher } from './matcher'
//...same line as above for all matchers
- In src/index.d.ts
export * from './matchers';
- I created the file to extend the matcher
//@file: jest.setup.ts
import { toBeAfter } from "jest-extended";
expect.extend({
toBeAfter,
});
- In tsconfig only added the matcher declaration that I extended
//@file: tsconfig.json
{
...,
"files": ["./node_modules/jest-extended/dist/matchers/toBeAfter/index.d.ts"],
}
And the types worked perfectly, it just recognized the matcher type that I extended. The only problem with this solution is that I haven't found a better way to import the types, I made a branch with the separate types if you want to test it yourself.
Another problem that I forgot to mention is that if you separate the types every time you add a new matcher, you would have to update the file src/matchers/index.d.ts.
We can fix this if we transform the lib into typescript and let it typescript issue the declarations, then each matcher.ts would have something like:
//@file: src/matchers/toBeArray/index.ts
import predicate from './predicate';
declare global {
namespace jest {
interface Matchers<R> {
/**
* Use `.toBeArray` when checking if a value is an `Array`.
*/
toBeArray(): R;
}
interface Expect {
/**
* Use `.toBeArray` when checking if a value is an `Array`.
*/
toBeArray(): void;
}
}
}
const passMessage =
({ matcherHint, printReceived }: jest.MatcherContext['utils'], received: unknown) =>
() =>
matcherHint('.not.toBeArray', 'received', '') +
'\n\n' +
'Expected value to not be an array received:\n' +
` ${printReceived(received)}`;
const failMessage =
({ matcherHint, printReceived }: jest.MatcherContext['utils'], received: unknown) =>
() =>
matcherHint('.toBeArray', 'received', '') +
'\n\n' +
'Expected value to be an array received:\n' +
` ${printReceived(received)}`;
export function toBeArray(this: jest.MatcherContext, expected: any): jest.CustomMatcherResult {
const pass = predicate(expected);
if (pass) {
return { pass: true, message: passMessage(this.utils, expected) };
}
return { pass: false, message: failMessage(this.utils, expected) };
}
This matcher toBeArray transformed into typescript, brings this output to its declaration file. (we can say that it is the same as the one we would have to write manually)
//@file: dist/matchers/toBeArray/index.d.ts
/// <reference types="jest" />
declare global {
namespace jest {
interface Matchers<R> {
/**
* Use `.toBeArray` when checking if a value is an `Array`.
*/
toBeArray(): R;
}
interface Expect {
/**
* Use `.toBeArray` when checking if a value is an `Array`.
*/
toBeArray(): void;
}
}
}
export declare function toBeArray(this: jest.MatcherContext, expected: any): jest.CustomMatcherResult;
I'm seeing this issue too, hope to see it resolved soon!
Edit: I've found that this work-around is working for me
I have a minimal setup with no Jest config file and only one file of tests, and—going off the link from the comment above—I was able to get it running today by only:
-
npm i -D jest-extended
(v2.0.0) - Adding the line
"setupFilesAfterEnv": ["jest-extended/all"]
to thejest
object in mypackage.json
- Adding the line
import 'jest-extended';
to the top of mytests.ts
file
Given my setup, I did not need a global.d.ts
file at all.
I have a minimal setup with no Jest config file and only one file of tests, and—going off the link from the comment above—I was able to get it running today by only:
npm i -D jest-extended
(v2.0.0)- Adding the line
"setupFilesAfterEnv": ["jest-extended/all"]
to thejest
object in mypackage.json
- Adding the line
import 'jest-extended';
to the top of mytests.ts
fileGiven my setup, I did not need a
global.d.ts
file at all.
I believe you are confusing the problem, what I'm saying is that have problems importing a matcher instead of importing them all.
Yeah, apologies. I had been beating my head against my desk for so long just to get this library to work with TypeScript, period, that I wanted to leave some help anyone else that found their way here, and that comment above mine pointed me in a helpful direction. But you're right, you've got a more specific problem.
I have a minimal setup with no Jest config file and only one file of tests, and—going off the link from the comment above—I was able to get it running today by only:
npm i -D jest-extended
(v2.0.0)- Adding the line
"setupFilesAfterEnv": ["jest-extended/all"]
to thejest
object in mypackage.json
- Adding the line
import 'jest-extended';
to the top of mytests.ts
fileGiven my setup, I did not need a
global.d.ts
file at all.
This doesn't work when running jest in esm mode, as there is no global jest
object
Weird how import "jest-extended"
doesn't work (get the original is not a function
error).
Then import "jest-extended/all"
doesn't work either. I get another error TypeError: Cannot redefine property: toBeEmpty
.
How difficult can it be to import some functions 😆