Расширение стороннего модуля, который глобально открыт

Я пытаюсь добавить пользовательский совпадение в Jest в Typescript. Это отлично работает, но я не могу получить Typescript для распознавания расширенного Matchers.

myMatcher.ts

export default function myMatcher (this: jest.MatcherUtils, received: any, expected: any): { pass: boolean; message (): string; } {
  const pass = received === expected;
  return {
    pass: pass,
    message: () => `expected ${pass ? '!' : '='}==`,
  }
}

myMatcher.d.ts

declare namespace jest {
  interface Matchers {
    myMatcher (expected: any): boolean;
  }
}

someTest.ts

import myMatcher from './myMatcher';

expect.extend({
  myMatcher,
})

it('should work', () => {
  expect('str').myMatcher('str');
})

tsconfig.json

{
  "compilerOptions": {
    "outDir": "./dist/",
    "moduleResolution": "node",
    "module": "es6",
    "target": "es5",
    "lib": [
      "es7",
      "dom"
    ]
  },
  "types": [
    "jest"
  ],
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
    "dist",
    "doc",
    "**/__mocks__/*",
    "**/__tests__/*"
  ]
}

В someTests.ts я получаю сообщение об ошибке

error TS2339: Property 'myMatcher' does not exist on type 'Matchers'

Я несколько раз читал документацию Microsoft, но не могу понять, как сменить пространство имен с глобально доступными типами (не экспортируется).

Помещая его в index.d.ts из jest, отлично работает, но не является хорошим решением для быстро меняющейся базы кода и классов, расширяемых несколькими сторонами.

Ответ 1

ОК, здесь есть несколько вопросов

Когда файл исходного файла (.ts или .tsx) и файл декларации (.d.ts) являются кандидатами для разрешения модуля, как в данном случае, компилятор разрешит исходный файл.

Вероятно, у вас есть два файла, потому что вы хотите экспортировать значение, а также изменить тип глобального объекта jest. Однако для этого вам не нужны два файла, так как TypeScript имеет специальную конструкцию для расширения глобальной области видимости из модуля. То есть все, что вам нужно, это следующий файл .ts

myMatcher.ts

// use declare global within a module to introduce or augment a global declaration.
declare global {
  namespace jest {
    interface Matchers {
      myMatcher: typeof myMatcher;
    }
  }
}
export default function myMatcher<T>(this: jest.MatcherUtils, received: T, expected: T) {
  const pass = received === expected;
  return {
    pass,
    message: () => 'expected ${pass ? '!' : '='}=='
  };
}

Тем не менее, если у вас такая ситуация, рекомендуется выполнять глобальную мутацию и глобальное увеличение типов в одном файле. Учитывая это, я бы подумал переписать его следующим образом

myMatcher.ts

// ensure this is parsed as a module.
export {};

declare global {
  namespace jest {
    interface Matchers {
      myMatcher: typeof myMatcher;
    }
  }
}
function myMatcher<T>(this: jest.MatcherUtils, received: T, expected: T) {
  const pass = received === expected;
  return {
    pass,
    message: () => 'expected ${pass ? '!' : '='}=='
  };
}

expect.extend({
  myMatcher
});

someTest.ts

import './myMatcher';

it('should work', () => {
  expect('str').myMatcher('str');
});

Ответ 2

Простой способ это:

customMatchers.ts

declare global {
    namespace jest {
        interface Matchers<R> {
            // add any of your custom matchers here
            toBeDivisibleBy: (argument: number) => {};
        }
    }
}

// this will extend the expect with a custom matcher
expect.extend({
    toBeDivisibleBy(received: number, argument: number) {
        const pass = received % argument === 0;
        if (pass) {
            return {
                message: () => 'expected ${received} not to be divisible by ${argument}',
                pass: true
            };
        } else {
            return {
                message: () => 'expected ${received} to be divisible by ${argument}',
                pass: false
            };
        }
    }
});

my.spec.ts

import "path/to/customMatchers";

test('even and odd numbers', () => {
   expect(100).toBeDivisibleBy(2);
   expect(101).not.toBeDivisibleBy(2);
});