【问题标题】:Extending third party module that is globally exposed扩展全球公开的第三方模块
【发布时间】:2017-09-25 19:09:01
【问题描述】:

我正在尝试在 Typescript 中向 Jest 添加自定义匹配器。这很好用,但我无法让 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 文档,但不知道如何将命名空间与全局可用类型(未导出)合并。

将它从 jest 放入 index.d.ts 效果很好,但对于快速变化的代码库和多方扩展的类来说,这不是一个好的解决方案。

【问题讨论】:

    标签: typescript jestjs


    【解决方案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 ? '!' : '='}==`
      };
    }
    

    也就是说,如果您遇到这种情况,最好在同一个文件中执行全局mutation 和全局类型augmentation。鉴于此,我会考虑重写如下

    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');
    });
    

    【讨论】:

    • 在这里我认为可以将类型声明从 .ts 文件移到 .d.ts 文件中以进行组织。但实际上,.d.ts 文件只是纯 JavaScript 的 TS 外观。那里的普通进口也很好。有趣的是,如果我将声明语句放在 someTest.ts 文件中,我会丢失其余的 Matchers 声明信息。所以 TS 编译器必须对那里的声明顺序做一些奇怪的事情,我将不得不研究更多。
    • @DylanStewart 声明可以从.ts 文件中分解到.d.ts 文件中,但前提是它们具有不同的模块说明符。例如。 my-module.tsmy-module-declarations.d.ts 没问题,但 my-module.tsmy-module.d.ts 没问题。 .tsx 文件也是如此。
    • @AluanHaddad 我对您提出的解决方案有疑问。如果我要使用声明全局方法,它可以工作,但是如果我将声明命名空间玩笑分解到一个单独的模块中,然后导入该模块,它似乎不起作用。这是一个屏幕截图。你有什么想法? d2ffutrenqvap3.cloudfront.net/items/2A1H3v0v2B3l3e1a3q06/…
    • @Tony 一个文件with 顶级importexport 是一个模块。要影响模块内的全局声明空间,您需要使用declare global。你的custom-matchers.ts 有一个顶级import 这意味着它是一个模块并且需要blockdeclare global
    • @Tony 您的代码所做的是声明一个模块范围的命名空间,它与jest 全局无关。
    【解决方案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);
    });
    

    【讨论】:

    • 通过setupFilesAfterEnv配置选项,jest可以在测试前自动导入customMathers.ts文件一次
    【解决方案3】:

    tsconfig.json 中添加"@types/testing-library__jest-dom"types 为我解决了这个问题

    // tsconfig.json
    
    "types": [
          "node",
          "jest",
          "@types/testing-library__jest-dom"
        ],
    

    也看到这个答案Property 'toBeInTheDocument' does not exist on type 'Matchers<any>'

    【讨论】:

      【解决方案4】:

      @AluanHaddad 的回答几乎是正确的,没有几种类型。 这个有效:

      export {};
      
      declare global {
        namespace jest {
          interface Matchers<R> {
            myMatcher: (received: string) => R;
          }
        }
      }
      
      function myMatcher<T>(this: jest.MatcherUtils, received: string, expected: string): jest.CustomMatcherResult {
        const pass = received === expected;
        return {
          pass,
          message: (): string => `expected ${received} to be ${expected}`,
        }
      }
      
      expect.extend({
        myMatcher,
      });
      

      有关真实示例,请参阅https://github.com/Quantum-Game/quantum-tensors/blob/master/tests/customMatchers.ts(测试实际上通过了:https://travis-ci.com/Quantum-Game/quantum-tensors)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-05-20
        • 2015-09-20
        • 1970-01-01
        • 2018-06-21
        • 1970-01-01
        • 1970-01-01
        • 2018-01-12
        • 1970-01-01
        相关资源
        最近更新 更多