I have this TypeScript + React + Webpack + Jest + Enzyme fully working.
I feel the need to have some global function accessible in my test specs. I can do that by pointing the setupTestFrameworkScriptFile
option in Jest configuration to a .js
file and I have something like this:
const Enzyme = require("enzyme");
const React = require("react");
const getMuiTheme = require("material-ui/styles/getMuiTheme").default;
global.mountWithContext = (node) => {
return Enzyme.mount(node, {
context: {
muiTheme: getMuiTheme()
},
childContextTypes: {
muiTheme: React.PropTypes.object
}
});
};
On my spec files I can call mountWithContext()
and it will work just fine when I run the tests. But my editor does not recognize the function.
To work around that, I've created a /typings/declarations.d.ts
file with this:
declare function mountWithContext(node: any): any;
Now my spec files recognize the mountWithContext
function, but the types are "wrong". The correct definition would be more something like this:
declare function mountWithContext(node: React.ReactElement<any>): Enzyme.ReactWrapper<P, S>;
The React.ReactElement
is correctly identified because the @types/react/index.d.ts
file includes this:
export = React;
export as namespace React;
The same isn't true for Enzyme. There's no global Enzyme
exported. Looking at @types/enzyme/index.d.ts
there's something like this:
export interface ReactWrapper<P, S> extends CommonWrapper<P, S> {
(...)
}
But even if I do this:
declare function mountWithContext(node: React.ReactElement<any>): ReactWrapper<P, S>;
It doesn't work. ReactWrapper
is still not recognized.
I tried to import inside this declarations.d.ts
file and that will allow me to use the type correctly, but my function declaration will stop being recognized in my spec files. I've also tried to add a triple-slash directive like this:
/// <reference path="../node_modules/@types/enzyme/index.d.ts" />
And the file is recognized, but the ReactWrapper
type still isn't.
So... How can I have a TypeScript function declaration for my custom mountWithContext
global function with all the proper types, like this:
declare function mountWithContext(node: React.ReactElement<any>): ReactWrapper<P, S>;
To import all modules from a directory in TypeScript, we can create a module that reexports all modules that were imported. export { default as A } from "./a"; export { default as B } from "./b"; to import the default exports from modules a and b .
If we would like to declare that the global.mountWithContext
function exists and has the indicated type we can add the following to an empty declaration file in our project.
import React from 'react';
import enzyme from 'enzyme';
declare global {
namespace NodeJS {
interface Global {
mountWithContext<P, S>(node: React.ReactElement<any>): enzyme.ReactWrapper<P, S>;
}
}
}
Details
Here is a break down each of the constructs in the above:
The declare global
block: The purpose of this construct is to modify, to augment if you will, the global scope, either by introducing new declarations or by modifying existing ones, from within a non global context such as a module. Our declaration file is a module because it contains a top-level import
clause that imports ReactWrapper
from Enzyme.
Such a block is called a "Global Augmentation" and may appear in implementation files as well.
The interface Global
wrapped in the namespace NodeJS
structuring: In our tests we are accessing the mountWithContext
function as a member of the global variable, incidentally named global
, that is provided by the Node environment. This variable is already declared by the type declarations for Node at the same level as other environmental globals such as process
. However, we need to augment its type, by adding a new member. The type of this global global
is the interface
Global
already declared inside the namespace
NodeJS
, by the official declarations for Node. We accomplish this by declaring a new member of the interface
Global
. Our augmentation needs to be wrapped in the namespace
containing Global
or else it would be considered a separate interface
. Roughly speaking, lexical scoping applies. (recall that TypeScript interfaces can be declared multiple times in the same scope and that these declarations will be merged)
Notes
Where do we place this declaration? Anywhere that will cause TypeScript to pick it up and include it in our compilation context. By convention we will place it in a file named globals.d.ts in the root directory of our project. (Technically it can be named something else and go in a lower directory).
It is important to note that this declaration file is part of our application code and should be checked into source control. It must not be placed alongside third party declarations in directories such as typings, node_modules/@types, or jspm_packages/npm/@types.
It is also important to note that this augmentation affects TypeScript files (.ts
, .tsx
, .d.ts
) only. If we were rather declaring this function to be picked up by the TypeScript language service simply to provide intellisense when working in .js
or .jsx,
files, this approach will not work.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With