Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to import other types into my TypeScript custom declarations file?

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>;
like image 639
rfgamaral Avatar asked Jan 27 '17 14:01

rfgamaral


People also ask

How do I import all modules into a directory in TypeScript?

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 .


1 Answers

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:

  1. 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.

  2. 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.

like image 154
Aluan Haddad Avatar answered Oct 24 '22 05:10

Aluan Haddad