在笑话和打字稿中模拟交叉点观察者

Posted

技术标签:

【中文标题】在笑话和打字稿中模拟交叉点观察者【英文标题】:Mock for intersection observer in jest and typescript 【发布时间】:2020-12-19 06:03:51 【问题描述】:

如果我们将 jest 与 typescript 一起使用,其中使用了交叉点观察器,交叉点观察器的模拟将变得困难。到目前为止,我在:


beforeEach(() => 
  // IntersectionObserver isn't available in test environment
  const mockIntersectionObserver = class 
    observe() 
      console.log(this);
    

    unobserve() 
      console.log(this);
    

    disconnect() 
      console.log(this);
    

    root = null

    rootMargin = '0'

    thresholds=[1]

    takeRecords=() => ([
      isIntersecting: true,
      boundingClientRect: true,
      intersectionRatio: true,
      intersectionRect: true,
      rootBounds: true,
      target: true,
       time: true,
    ])
  ;
  window.IntersectionObserver = mockIntersectionObserver;
);

但这仍然会引发错误,例如:

Type 'typeof mockIntersectionObserver' is not assignable to type ' new (callback: IntersectionObserverCallback, options?: IntersectionObserverInit | undefined): IntersectionObserver; prototype: IntersectionObserver; '.
  The types returned by 'prototype.takeRecords()' are incompatible between these types.
    Type ' isIntersecting: boolean; boundingClientRect: boolean; intersectionRatio: boolean; intersectionRect: boolean; rootBounds: boolean; target: boolean; time: boolean; []' is not assignable to type 'IntersectionObserverEntry[]'.
      Type ' isIntersecting: boolean; boundingClientRect: boolean; intersectionRatio: boolean; intersectionRect: boolean; rootBounds: boolean; target: boolean; time: boolean; ' is not assignable to type 'IntersectionObserverEntry'.
        Types of property 'boundingClientRect' are incompatible.
          Type 'boolean' is not assignable to type 'DOMRectReadOnly'.ts(2322

我可以继续为每个元素添加正确的类型,但有更好的方法吗?

如何在 jest 环境中添加交叉点观察者?我觉得比这样嘲讽要好。

【问题讨论】:

如果这使事情变得更加复杂,您不一定需要在模拟中保持类型安全。但在这种情况下,它显示了模拟中的错误。检查developer.mozilla.org/en-US/docs/Web/API/…。 boundingClientRect 等一些属性不是布尔值。此外,将 IntersectionObserver 设为返回 spy 方法的 spy 比使用虚假方法实现的类更有意义。 能否请您详细说明一下作为答案,以便未来的开发人员可以轻松理解。请为TS新手写。 【参考方案1】:

一般来说,mock 应该紧跟IntersectionObserverIntersectionObserverEntry 规范,但是mock 可以根据用途进行剥离。

如果正确键入会使事情变得更加复杂,则可能没有必要在模拟中保持类型安全。在这种情况下,类型错误显示模拟中的错误。正如在IntersectionObserverEntry 参考中可以看到的那样,只有isIntersecting 属性应该是布尔值,而boundingClientRect 应该是对象,因此为其余部分提供布尔值并忽略类型问题可能会导致模拟实现无意中没有不行。

使用常规类进行模拟是不切实际的,因为它缺乏 Jest 间谍提供的功能,例如调用断言和由框架控制的模拟实现。

一个简单的实现是:

window.IntersectionObserver = jest.fn(() => (
  takeRecords: jest.fn(),
  ...
));

缺点是,当无法直接访问实例或需要在实例化后立即完成时,无法更改模拟类成员的实现。这需要在需要的地方替换整个类的实现。

出于这个原因,让 Jest 监视一个具有原型链的类是有益的,该原型链可以在实例化之前访问。可以为此目的利用 Jest 自动模拟,这允许为只读属性定义 get 访问器,这些属性可以像任何其他 Jest 间谍一样更改实现:

class IntersectionObserverStub 
  get root()  // read-only property 
  takeRecords()  /* implementation is ignored */  // stub method
  observe() 
  ...


jest.doMock('intersection-observer-mock', () => IntersectionObserverStub,  virtual: true );

window.IntersectionObserver = jest.requireMock('intersection-observer-mock');

jest.spyOn(IntersectionObserver.prototype, 'root', 'get').mockReturnValue(null);
// jest.spyOn(IntersectionObserver.prototype, 'takeRecords').mockImplementation(() => (...));

这导致生成模拟类实现,它是 Jest 间谍原型,其方法是无操作间谍。 get 访问器保持原样,但由于它们存在,以后可以使用 spyOn(..., 'get') 模拟它们。像 takeRecords 这样可能特定于实例的方法可以在没有默认实现的情况下保留并在原地模拟,当它被意外调用时,它返回 undefined 可能会导致比随机预定义值更清晰的错误输出。

jest.spyOn(IntersectionObserver.prototype, 'root', 'get').mockReturnValueOnce(someDocument);
const mockedEntries = [
  isIntersecting: true,
  boundingClientRect:  x: 10, y: 20, width: 30, height: 40, ... ,
  ...
];
IntersectionObserver.prototype.takeRecords.mockReturnValueOnce(mockedEntries );

// code that instantiates IntersectionObserver and possibly uses mocked values immediately

expect(IntersectionObserver.prototype.observe).toBeCalledWith(...);
expect(IntersectionObserver).toBeCalledWith(expect.any(Function)); // callback arg
let [callback] = IntersectionObserver.mock.calls[0]
callback(mockedEntries); // test a callback 

【讨论】:

我无法监视存根的任何方法。 “无法监视观察属性,因为它不是函数;而是给出了未定义。”当我这样做时:const observe = jest.spyOn(IntersectionObserver, 'observe').mockImplementation(() => ); 修复了代码,对于非静态方法,它应该是jest.spyOn(IntersectionObserver.prototype, ... 无处不在。【参考方案2】:

这对我们有用

/* eslint-disable class-methods-use-this */

export default class 
  readonly root: Element | null;

  readonly rootMargin: string;

  readonly thresholds: ReadonlyArray<number>;

  constructor() 
    this.root = null;
    this.rootMargin = '';
    this.thresholds = [];
  

  disconnect() 

  observe() 

  takeRecords(): IntersectionObserverEntry[] 
    return [];
  

  unobserve() 

然后安装然后做

import MockIntersectionObserver from './MockIntersectionObserver';
window.IntersectionObserver = MockIntersectionObserver;

【讨论】:

以上是关于在笑话和打字稿中模拟交叉点观察者的主要内容,如果未能解决你的问题,请参考以下文章

地图在打字稿中显示错误

如何处理模块和打字稿类型的笑话模拟功能

使用笑话模拟时的打字稿错误

同一页面上多个视频的交叉点观察者

带有反应的交叉口观察者 API

交叉点观察者 页面上已经存在的动画元素偶尔会起作用