如何在我的环境声明文件中声明全局 NodeJS 变量?
How to declare global NodeJS variables within my ambient declaration file?
这是 的后续问题。
鉴于该问题的答案,假设我有以下环境声明文件:
declare namespace Twilio {
interface IDevice {
ready(handler: Function): void;
}
let Device: IDevice;
}
这在我的应用程序 .ts
文件上运行良好,Twilio.Device.ready
被完全识别。但是我有我的单元测试 运行 Jest 和 AFAIK,它们 运行 在 NodeJS 环境中。
作为对我测试的过度简化的模拟,我有这个(也是 .ts
文件):
global.Twilio = {
Device: {
ready: () => {},
offline: () => {},
error: () => {},
}
};
但是这个Twilio
实例不被识别。我可以通过在 .d.ts
文件中添加如下内容来解决这个问题:
declare global {
namespace NodeJS {
interface Global {
Twilio: any;
}
}
}
export = {}; // This is needed otherwise `declare global` won't work
但是,我不能将这段代码与我第一次提到的 declare namespace Twilio
放在同一个文件中。它们需要在单独的文件中,否则我的测试中的 global.Twilio
将被识别,但我的代码中的 Twilio.Device
不会被识别。
所以我的问题是,如何让 Twilio
的两个实例在应用程序代码和测试代码中被识别?
作为一个额外的问题,如果我可以使用 Twilio
命名空间声明,以某种方式作为 NodeJS Twilio
全局类型而不是 any
的类型,那就太好了。
编辑:
在与理查德·塞维奥拉 (Richard Seviora) 愉快地交谈并讨论了我所有的问题之后,我最终为我的项目得到了以下 twilio.d.ts
文件:
/**
* Namespace declaration for the Twilio.js library global variable.
*/
declare namespace Twilio {
type DeviceCallback = (device : IDevice) => void;
type ConnectionCallback = (connection : IConnection) => void;
type ErrorCallback = (error : IError) => void;
interface IConnection {
parameters: IConnectionParameters;
}
interface IConnectionParameters {
From?: string;
To?: string;
}
interface IDevice {
cancel(handler: ConnectionCallback): void;
connect(params: IConnectionParameters): IConnection;
disconnect(handler: ConnectionCallback): void;
error(handler: ErrorCallback): void;
incoming(handler: ConnectionCallback): void;
offline(handler: DeviceCallback): void;
ready(handler: DeviceCallback): void;
setup(token: string, params: ISetupParameters): void;
}
interface IError {
message?: string;
code?: number;
connection?: IConnection;
}
interface ISetupParameters {
closeProtection: boolean;
}
let Device: IDevice;
}
/**
* Augment the Node.js namespace with a global declaration for the Twilio.js library.
*/
declare namespace NodeJS {
interface Global {
Twilio: {
// Partial type intended for test execution only!
Device: Partial<Twilio.IDevice>;
};
}
}
希望其他人发现这个问题和理查德在下面的回答很有见地(因为声明文档有点缺乏)。
再次感谢理查德的帮助。
这绝对是一个有趣的问题。问题是环境文件断言 Twilio.Device
存在,因此任何类似全局声明的东西都需要考虑到这一点。
事实证明,解决起来相当简单。无需扩展声明文件或创建另一个文件。鉴于您提供的声明文件,您需要做的就是在测试设置中包括以下内容:
namespace Twilio {
Device = {
setup: function () { },
ready: function () { },
offline: function () { },
incoming: function () { },
connect: function (params): Twilio.Connection { return null },
error: function () { }
}
}
因为我们声明了 let Twilio.Device : IDevice
,我们还允许消费者在以后分配 Twilio.Device
。如果我们声明 const Twilio.Device : IDevice
,这是不可能的。
但是,我们不能只说:
Twilio.Device = { ... }
因为这样做需要 Twilio
命名空间存在,这就是我们在说 declare namespace Twilio
时所断言的情况。这个 Typescript 可以编译成功,但是编译后的 JS 认为环境 Twilio
变量的存在是理所当然的,因此失败了。
TypeScript 也不允许您将值分配给现有的命名空间,因此我们不能这样做:
const Twilio = {}; // Or let for that matter.
Twilio.Device = {...}
但是,由于 TypeScript 命名空间声明合并到已编译的 JS 中,将赋值包装在 Twilio 命名空间中会实例化 Twilio
(如果它尚不存在)然后赋值 Device
,而都遵守环境文件中规定的类型规则。
namespace Twilio {
Device = {
// All properties need to be stubbed.
setup: function () { },
ready: function () { },
offline: function () { },
incoming: function () { },
connect: function (params): Twilio.Connection { return null },
error: function () { }
}
}
beforeEach(() => {
// Properties/mocks will need to be reset as the namespace declaration only runs once.
Twilio.Device.setup = () => {return null;};
})
test('adds stuff', () => {
expect(Twilio.Device.setup(null, null)).toBeNull()
})
这一切都假设您的测试文件可以访问声明文件。如果不是,您需要通过 tsconfig.json
包含它,或通过 /// <references path="wherever"/>
指令引用它。
编辑 1
已将上例中的 beforeEach
更正为 return 空值。否则测试会失败。
编辑 2 - 扩展 NodeJS 全局声明
我想我应该补充为什么不需要扩展 NodeJS.Global
接口。
当我们进行环境声明时,假定它作为全局上下文(以及任何子闭包)的一部分存在。因此,我们不需要用 Twilio
扩展 Global
,因为 Twilio
也被假定存在于直接上下文中。
但是,这种特殊的声明方法意味着 global.Twilio
不存在。我可以声明:
namespace NodeJS {
interface Global {
Twilio : {
Device : Twilio.IDevice;
};
}
}
这将为 NodeJS.Global.Twilio
对象的 Device 属性 提供类型推断,但无法以与类型相同的方式对 "share" 名称空间进行类型推断。
编辑 3 - 全球扩展毕竟是必要的
经过交谈,我们得出的结论是,扩展 NodeJS.Global
是必要的,因为 Jest 不会在测试和执行它们的 类 之间共享上下文。
为了允许我们修改 NodeJS.Global
界面,我们将以下内容附加到定义文件中:
declare namespace NodeJS {
interface Global {
Twilio: { Device: Partial<Twilio.IDevice> }
}
}
这将启用:
beforeEach(() => {
global.Twilio =
{
Device: {
setup: function () { return null },
ready: function () { },
connect: function (params): Twilio.Connection { return null },
error: function () { }
}
};
})
虽然这意味着 NodeJS.Global.Twilio
与 Twilio
命名空间不同,但它确实允许为了构建测试的目的而进行相同的操作。
这是
鉴于该问题的答案,假设我有以下环境声明文件:
declare namespace Twilio {
interface IDevice {
ready(handler: Function): void;
}
let Device: IDevice;
}
这在我的应用程序 .ts
文件上运行良好,Twilio.Device.ready
被完全识别。但是我有我的单元测试 运行 Jest 和 AFAIK,它们 运行 在 NodeJS 环境中。
作为对我测试的过度简化的模拟,我有这个(也是 .ts
文件):
global.Twilio = {
Device: {
ready: () => {},
offline: () => {},
error: () => {},
}
};
但是这个Twilio
实例不被识别。我可以通过在 .d.ts
文件中添加如下内容来解决这个问题:
declare global {
namespace NodeJS {
interface Global {
Twilio: any;
}
}
}
export = {}; // This is needed otherwise `declare global` won't work
但是,我不能将这段代码与我第一次提到的 declare namespace Twilio
放在同一个文件中。它们需要在单独的文件中,否则我的测试中的 global.Twilio
将被识别,但我的代码中的 Twilio.Device
不会被识别。
所以我的问题是,如何让 Twilio
的两个实例在应用程序代码和测试代码中被识别?
作为一个额外的问题,如果我可以使用 Twilio
命名空间声明,以某种方式作为 NodeJS Twilio
全局类型而不是 any
的类型,那就太好了。
编辑:
在与理查德·塞维奥拉 (Richard Seviora) 愉快地交谈并讨论了我所有的问题之后,我最终为我的项目得到了以下 twilio.d.ts
文件:
/**
* Namespace declaration for the Twilio.js library global variable.
*/
declare namespace Twilio {
type DeviceCallback = (device : IDevice) => void;
type ConnectionCallback = (connection : IConnection) => void;
type ErrorCallback = (error : IError) => void;
interface IConnection {
parameters: IConnectionParameters;
}
interface IConnectionParameters {
From?: string;
To?: string;
}
interface IDevice {
cancel(handler: ConnectionCallback): void;
connect(params: IConnectionParameters): IConnection;
disconnect(handler: ConnectionCallback): void;
error(handler: ErrorCallback): void;
incoming(handler: ConnectionCallback): void;
offline(handler: DeviceCallback): void;
ready(handler: DeviceCallback): void;
setup(token: string, params: ISetupParameters): void;
}
interface IError {
message?: string;
code?: number;
connection?: IConnection;
}
interface ISetupParameters {
closeProtection: boolean;
}
let Device: IDevice;
}
/**
* Augment the Node.js namespace with a global declaration for the Twilio.js library.
*/
declare namespace NodeJS {
interface Global {
Twilio: {
// Partial type intended for test execution only!
Device: Partial<Twilio.IDevice>;
};
}
}
希望其他人发现这个问题和理查德在下面的回答很有见地(因为声明文档有点缺乏)。
再次感谢理查德的帮助。
这绝对是一个有趣的问题。问题是环境文件断言 Twilio.Device
存在,因此任何类似全局声明的东西都需要考虑到这一点。
事实证明,解决起来相当简单。无需扩展声明文件或创建另一个文件。鉴于您提供的声明文件,您需要做的就是在测试设置中包括以下内容:
namespace Twilio {
Device = {
setup: function () { },
ready: function () { },
offline: function () { },
incoming: function () { },
connect: function (params): Twilio.Connection { return null },
error: function () { }
}
}
因为我们声明了 let Twilio.Device : IDevice
,我们还允许消费者在以后分配 Twilio.Device
。如果我们声明 const Twilio.Device : IDevice
,这是不可能的。
但是,我们不能只说:
Twilio.Device = { ... }
因为这样做需要 Twilio
命名空间存在,这就是我们在说 declare namespace Twilio
时所断言的情况。这个 Typescript 可以编译成功,但是编译后的 JS 认为环境 Twilio
变量的存在是理所当然的,因此失败了。
TypeScript 也不允许您将值分配给现有的命名空间,因此我们不能这样做:
const Twilio = {}; // Or let for that matter.
Twilio.Device = {...}
但是,由于 TypeScript 命名空间声明合并到已编译的 JS 中,将赋值包装在 Twilio 命名空间中会实例化 Twilio
(如果它尚不存在)然后赋值 Device
,而都遵守环境文件中规定的类型规则。
namespace Twilio {
Device = {
// All properties need to be stubbed.
setup: function () { },
ready: function () { },
offline: function () { },
incoming: function () { },
connect: function (params): Twilio.Connection { return null },
error: function () { }
}
}
beforeEach(() => {
// Properties/mocks will need to be reset as the namespace declaration only runs once.
Twilio.Device.setup = () => {return null;};
})
test('adds stuff', () => {
expect(Twilio.Device.setup(null, null)).toBeNull()
})
这一切都假设您的测试文件可以访问声明文件。如果不是,您需要通过 tsconfig.json
包含它,或通过 /// <references path="wherever"/>
指令引用它。
编辑 1
已将上例中的 beforeEach
更正为 return 空值。否则测试会失败。
编辑 2 - 扩展 NodeJS 全局声明
我想我应该补充为什么不需要扩展 NodeJS.Global
接口。
当我们进行环境声明时,假定它作为全局上下文(以及任何子闭包)的一部分存在。因此,我们不需要用 Twilio
扩展 Global
,因为 Twilio
也被假定存在于直接上下文中。
但是,这种特殊的声明方法意味着 global.Twilio
不存在。我可以声明:
namespace NodeJS {
interface Global {
Twilio : {
Device : Twilio.IDevice;
};
}
}
这将为 NodeJS.Global.Twilio
对象的 Device 属性 提供类型推断,但无法以与类型相同的方式对 "share" 名称空间进行类型推断。
编辑 3 - 全球扩展毕竟是必要的
经过交谈,我们得出的结论是,扩展 NodeJS.Global
是必要的,因为 Jest 不会在测试和执行它们的 类 之间共享上下文。
为了允许我们修改 NodeJS.Global
界面,我们将以下内容附加到定义文件中:
declare namespace NodeJS {
interface Global {
Twilio: { Device: Partial<Twilio.IDevice> }
}
}
这将启用:
beforeEach(() => {
global.Twilio =
{
Device: {
setup: function () { return null },
ready: function () { },
connect: function (params): Twilio.Connection { return null },
error: function () { }
}
};
})
虽然这意味着 NodeJS.Global.Twilio
与 Twilio
命名空间不同,但它确实允许为了构建测试的目的而进行相同的操作。