如何使用jsdom和typescript防止“类型'Global'上不存在属性'...'?


72

我尝试将现有项目转换为使用Typescript,但是在测试设置中却遇到了问题。

我有一个用于测试的安装文件,用于设置jsdom,以便我所有的DOM交互代码都可以在测试期间使用。使用Typescript(带有摩卡的ts节点),我总是会收到这样的错误:

Property 'window' does not exist on type 'Global'.

为了防止这种情况,我尝试像这样修补NodeJS.Global接口:

declare namespace NodeJS{
  interface Global {
    document: Document;
    window: Window;
    navigator: Navigator;
  }
}

但这并没有改变任何东西。

如何在NodeJS全局变量上启用这些浏览器属性?

附加功能:

这是我的摩卡咖啡setup.ts

import { jsdom, changeURL } from 'jsdom';

const exposedProperties = ['window', 'navigator', 'document'];

global.document = jsdom('');
global.window = global.document.defaultView;
Object.keys(global.document.defaultView).forEach((property) => {
  if (typeof global[property] === 'undefined') {
    exposedProperties.push(property);
    global[property] = global.document.defaultView[property];
  }
});

global.navigator = {
  userAgent: 'node.js',
};

changeURL(global.window, 'http://example.com/');

不能肯定这是正确的答案,但是经历了类似的痛苦之后,事实证明,扩展NodeJS.Global接口的文件必须全部小写,否则键入将忽略它。另外,它的顶部不能有任何导入或导出语句-否则它将被视为模块而不是类型文件。
Izhaki '16

4
这些答案似乎都不起作用,有人在2018年有答案吗?
亚历山大·米尔斯

Answers:


103

避免错误的原始答案

将其放在打字稿文件的顶部

const globalAny:any = global;

然后使用globalAny代替。

globalAny.document = jsdom('');
globalAny.window = global.document.defaultView;

更新答案以维护类型安全

如果要保持类型安全,则可以扩展现有的NodeJS.Global类型定义。

您需要将定义放入全局范围内 declare global {...}

请记住,打字稿global作用域与NodeJS接口Globalglobal property称为...global类型的节点不同Global

declare global {
  namespace NodeJS {
    interface Global {
       document: Document;
       window: Window;
       navigator: Navigator;
    } 
  }
}

10
通过强制转换删除类型any确实与为什么首先使用Typescript背道而驰。实际上,如果您要拉毛,TSLint可以提供专门的选项来防止这种情况:palantir.github.io/tslint/rules/no-any
Will Squire

1
@willsquire我使用类型安全选项更新了解决方案。这包括对OP不起作用的主要原因。
史蒂文·斯潘金

2
此声明与不使用名称空间的eslint规则不兼容。请看下面我的答案,找到一种更有趣的方法。
何塞擦玻璃

这个问题与陪同无关,规则是个人风格的问题。
史蒂文·斯潘金

2
ESLint规则是个人的,真实的,但如今每个人通常都会以所有默认规则为基础,并在不满意的情况下禁用或修改这些默认规则。我下面的解决方案涵盖了所有情况,而IMO则更具语义,因此使答案更加完整。
何塞·卡波

38

除了其他答案,您还可以直接在作业现场直接投放 global

(global as any).myvar = myvar;

2
这与打字稿用法矛盾,因为它是中性类型检查,这是打字稿的第一个原理用法。
Shahin Ghasemi

19

我通过这样做解决了这个问题...

export interface Global {
  document: Document;
  window: Window;
}

declare var global: Global;

7
export interface Global extends NodeJS.Global如果您想修改一些但保留其余Global的属性,例如fetch
casey

8

避免类型转换any,它消除了键入的目的。而是安装所需的类型定义(例如yarn add --dev @types/jsdom @types/node)并导入以使用:

import { DOMWindow, JSDOM } from 'jsdom'

interface Global extends NodeJS.Global {
  window: DOMWindow,
  document: Document,
  navigator: {
    userAgent: string
  }
}

const globalNode: Global = {
  window: window,
  document: window.document,
  navigator: {
    userAgent: 'node.js',
  },
  ...global
}

3

这是正确的解决方案,而不使用Typescript的名称空间。它还与所有eslint默认规则兼容:

// Declare a type.
interface CustomNodeJsGlobal extends NodeJS.Global {
    myExtraGlobalVariable: number;
    // You can declare anything you need.
}

用它:

// Tell Typescript to use this type on the globally scoped `global` variable.
declare const global: CustomNodeJsGlobal;

function doSomething() {
  // Use it freely
  global.myExtraGlobalVariable = 5;
}

doSomething();

使用最新推荐的@ typescript-eslint规则,eslint: error @typescript-eslint/no-unsafe-member-access - Unsafe member access .Global on an any value.可能会弹出。通过添加type NJSGlobal = NodeJS.Global;和扩展来解决NJSGlobal。除此之外,这种解决方案似乎是最好的方法。
Zweihänder

1
declare namespace NodeJS {
  export interface Global { window: any;
  }
}

24
仅代码的答案对教育SO读者几乎没有作用。请修改您的答案以包含一些说明。您的答案在审核队列中,因为它已被标记为低质量。
mickmackusa

尝试了您的答案,但global.window仍未解决
Mick
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.