dx_over_dt
dx_over_dt

Reputation: 14318

Use jest's expect.extend() with TypeScript scoped to a single test file

This question is similar to Can you limit the scope of a TypeScript global type? but a little different (though I would love an answer to it, as well).

In Jest, I want to do something like:

declare global {
  namespace jest {
    interface Matchers<R> {
      toBeAwesome(this: Matchers<void, HTMLElement>): R;
    }
  }
}

export {};

expect.extend({
  toBeAwesome() {
    // ...
  }
});

However, I only call extend in that specific file, and therefore I do not want other test files to be able to access it. The declare global {} bit seems to screw me over.

I've also tried something hacky:

declare const expect: jest.Expect & {
  <T = any>(actual: T): jest.JestMatchers<T> & {
    toBeSelected(this: jest.Matchers<void, HTMLElement>): void;
  };
};

But the method won't even show up.

I also tried directly declaring namespace jest outside of the global scope, but since jest is a global namespace, it created a new namespace instead of augmenting the other.

Is it possible to scope an augmentation to a specific file and any file that imports it?

Upvotes: 4

Views: 2370

Answers (3)

Brett Pyke
Brett Pyke

Reputation: 180

I think Typescript is picking up the Jest built-in functions first rather than your custom one. Try rearranging the order of the type composition instead:

declare const expect: {
  <T = any>(actual: T): {
    toBeSelected(this: jest.Matchers<void, HTMLElement>): void;
  } & jest.JestMatchers<T>;
} & jest.Expect;

I tried your "hacky" solution first but this is the one that worked for me.

Upvotes: 0

ecraig12345
ecraig12345

Reputation: 2437

Another workaround if you're using explicit imports from @jest/globals (rather than @types/jest which makes the globals available to TS everywhere) is to modify the type of the imported version of expect.

import { expect as _expect } from '@jest/globals';

const expect = _expect as unknown as {
  <T = unknown>(actual: T): {
    toBeAwesome(): void;
  } & ReturnType<typeof _expect>;
} & typeof _expect;

As written (there might be a better way), this doesn't provide type verification for the actual thing passed to expect(), but it at least makes the matcher available and limited to the current file.

You could also do this with global types (@types/jest) by redeclaring expect with a different name, e.g. const expectElement = expect as unknown as /* custom type */.

Upvotes: 0

Scott Willeke
Scott Willeke

Reputation: 9335

I don't think you can limit the scope of an augmented type within the same package, but you could define your own expect function with the type you want:

Something like:

function expectElement(actual: HTMLEelement): ElementExpect {
  return expect(actual) as any as ElementExpect
}

type ElementExpect = JestExpect & ElementMatchers

interface ElementMatchers<R = void> extends Matchers<void> {
  toBeSelected(): R
}

Then, arguably your tests are more explicit:

it("should be awesome", () => {
  expectElement(el).toBeAwesome()
})

Upvotes: 2

Related Questions