扩展全球公开的第三方模块

Posted

技术标签:

【中文标题】扩展全球公开的第三方模块【英文标题】: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 效果很好,但对于快速变化的代码库和多方扩展的类来说,这不是一个好的解决方案。

【问题讨论】:

【参考方案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)。

【讨论】:

以上是关于扩展全球公开的第三方模块的主要内容,如果未能解决你的问题,请参考以下文章

NO.1 全球模块化数据中心市场排名稳居首位

odoo第三方市场 -- 模块推荐

php中添加新模块支持

Nginx 第三方模块使用与开发

Redis学习之外部扩展模块详解

Pyhon学习笔记3:模组(引用第三方模块)