Search code examples
typescriptbabeljscreate-react-appjestjspreact

Using Jest with Typescript + preact


Problem with transpiling JSX into h() in tests. All config files is similar to create-react-app, exclude changes for TypeScript and preact

I create app via create-react-app my-app --script=react-scripts-ts - for TypeScript project. Then do eject and change react to preact (don't use preact-compat).

For migrating to preact I'm add to package.json into babel.plugins section new plugin ["babel-plugin-transform-react-jsx", { pragma: "h"}] - for transpiling <JSX /> to h(JSX) function calls, instead for default React.createElement(JSX) (migration guide https://preactjs.com/guide/switching-to-preact).

And this works fine.

But test has a different configuration of transpiling <JSX />: it's transpile to React.createElement(JSX) be default. And in test I take an error ReferenceError: React is not defined at Object.<anonymous> (src/Linkify/Linkify.test.tsx:39:21). If I manually change <JSX /> to h(SomeComponent) in test and tested file - it work.

How to transpile <JSX /> into h(JSX) for tests?

// typescriptTransform.js
// Copyright 2004-present Facebook. All Rights Reserved.

'use strict';

const fs = require('fs');
const crypto = require('crypto');
const tsc = require('typescript');
const tsconfigPath = require('app-root-path').resolve('/tsconfig.json');
const THIS_FILE = fs.readFileSync(__filename);

let compilerConfig = {
  module: tsc.ModuleKind.CommonJS,
  jsx: tsc.JsxEmit.React,
};

if (fs.existsSync(tsconfigPath)) {
  try {
    const tsconfig = tsc.readConfigFile(tsconfigPath).config;

    if (tsconfig && tsconfig.compilerOptions) {
      compilerConfig = tsconfig.compilerOptions;
    }
  } catch (e) {
    /* Do nothing - default is set */
  }
}

module.exports = {
  process(src, path, config, options) {
    if (path.endsWith('.ts') || path.endsWith('.tsx')) {
      let compilerOptions = compilerConfig;
      if (options.instrument) {
        // inline source with source map for remapping coverage
        compilerOptions = Object.assign({}, compilerConfig);
        delete compilerOptions.sourceMap;
        compilerOptions.inlineSourceMap = true;
        compilerOptions.inlineSources = true;
        // fix broken paths in coverage report if `.outDir` is set
        delete compilerOptions.outDir;
      }

      const tsTranspiled = tsc.transpileModule(src, {
        compilerOptions: compilerOptions,
        fileName: path,
      });
      return tsTranspiled.outputText;
    }
    return src;
  },
  getCacheKey(fileData, filePath, configStr, options) {
    return crypto
      .createHash('md5')
      .update(THIS_FILE)
      .update('\0', 'utf8')
      .update(fileData)
      .update('\0', 'utf8')
      .update(filePath)
      .update('\0', 'utf8')
      .update(configStr)
      .update('\0', 'utf8')
      .update(JSON.stringify(compilerConfig))
      .update('\0', 'utf8')
      .update(options.instrument ? 'instrument' : '')
      .digest('hex');
  },
};

Test sample:

import { h, render } from 'preact';
import Linkify from './Linkify';

it('renders without crashing', () => {
  const div = document.createElement('div');
  render(<Linkify children={'text'} />, div);
});

Solution

  • I found solution.

    I was wrong in babel.plugins section new plugin ["babel-plugin-transform-react-jsx", { pragma: "h"}] - it's not using. Really this pragma uses from tsconfig.json - "jsxFactory": "h"

    However this directive don't uses in typescriptTransform.js.

    I'm extend compiler options

    let compilerConfig = {
      module: tsc.ModuleKind.CommonJS,
      jsx: tsc.JsxEmit.React,
      jsxFactory: "h" // <-- duplicate option in jest transform config file
    };
    

    Hope it would be useful.