tape-testing / tape

tap-producing test harness for node and browsers
MIT License
5.77k stars 307 forks source link

Getting tape working with TypeScript and/or ESM #595

Closed EvHaus closed 6 months ago

EvHaus commented 1 year ago

I'm trying to add tape to https://github.com/EvHaus/test-runner-benchmarks as a follow up to @ljharb's Twitter post here, but I'm having a really hard time figuring out how to get it to work with a Typescript codebase.

My test file looks like this:

// Alert.test.tsx

import describe from 'tape-describe';
import Alert from '.';
import React from 'react';
import {render} from '@testing-library/react';

describe('<Alert />', (test) => {
    test('should render the given message', (t) => {
        const {getByText} = render(<Alert>Hello World</Alert>);
        t.ok(getByText('Hello World'));
    });
});

And my package.json has "type": "module" set.

1st Attempt (tape)

If I run:

tape tests/Alert.test.tsx

I get:

import describe from 'tape-describe';
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at internalCompileFunction (node:internal/vm:73:18)
    at wrapSafe (node:internal/modules/cjs/loader:1175:20)
    at Module._compile (node:internal/modules/cjs/loader:1219:27)
    at Module._extensions..js (node:internal/modules/cjs/loader:1309:10)
    at Module.load (node:internal/modules/cjs/loader:1113:32)
    at Module._load (node:internal/modules/cjs/loader:960:12)
    at Module.require (node:internal/modules/cjs/loader:1137:19)
    at require (node:internal/modules/helpers:121:18)
    at importOrRequire (/Users/evhaus/Git/jest-vs-jasmine/node_modules/tape/bin/import-or-require.js:14:2)
    at /Users/evhaus/Git/jest-vs-jasmine/node_modules/tape/bin/tape:96:8

2nd Attempt (ts-node)

If I switch to ts-node and use:

ts-node ./node_modules/tape/bin/tape tests/Alert.test.tsx

I get:

import describe from 'tape-describe';
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at internalCompileFunction (node:internal/vm:73:18)
    at wrapSafe (node:internal/modules/cjs/loader:1175:20)
    at Module._compile (node:internal/modules/cjs/loader:1219:27)
    at Module._extensions..js (node:internal/modules/cjs/loader:1309:10)
    at Object.require.extensions.<computed> [as .js] (/Users/evhaus/Git/jest-vs-jasmine/node_modules/ts-node/src/index.ts:1608:43)
    at Module.load (node:internal/modules/cjs/loader:1113:32)
    at Function.Module._load (node:internal/modules/cjs/loader:960:12)
    at Module.require (node:internal/modules/cjs/loader:1137:19)
    at require (node:internal/modules/helpers:121:18)
    at importOrRequire (/Users/evhaus/Git/jest-vs-jasmine/node_modules/tape/bin/import-or-require.js:14:2)

3rd Attempt (ts-node --esm)

If I try with ts-node --esm, ala:

ts-node --esm ./node_modules/tape/bin/tape tests/Alert.test.tsx

I get:

TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension "" for /Users/evhaus/Git/jest-vs-jasmine/node_modules/tape/bin/tape
    at new NodeError (node:internal/errors:399:5)
    at Object.getFileProtocolModuleFormat [as file:] (node:internal/modules/esm/get_format:99:9)
    at defaultGetFormat (node:internal/modules/esm/get_format:139:38)
    at defaultLoad (node:internal/modules/esm/load:83:20)
    at nextLoad (node:internal/modules/esm/hooks:735:28)
    at load (/Users/evhaus/Git/jest-vs-jasmine/node_modules/ts-node/dist/child/child-loader.js:19:122)
    at nextLoad (node:internal/modules/esm/hooks:735:28)
    at Hooks.load (node:internal/modules/esm/hooks:380:26)
    at handleMessage (node:internal/modules/esm/worker:165:24)
    at checkForMessages (node:internal/modules/esm/worker:114:28) {
  code: 'ERR_UNKNOWN_FILE_EXTENSION'
}

4th Attempt (tape-es)

If I try with tape-es:

tape-es ./node_modules/tape/bin/tape tests/Alert.test.tsx

I get:

node:internal/errors:490
    ErrorCaptureStackTrace(err);
    ^

TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".tsx" for /Users/evhaus/Git/jest-vs-jasmine/benchmarks/tape/tests/original/Alert/Alert.test.tsx
    at new NodeError (node:internal/errors:399:5)
    at Object.getFileProtocolModuleFormat [as file:] (node:internal/modules/esm/get_format:99:9)
    at defaultGetFormat (node:internal/modules/esm/get_format:139:38)
    at defaultLoad (node:internal/modules/esm/load:83:20)
    at DefaultModuleLoader.load (node:internal/modules/esm/loader:317:26)
    at DefaultModuleLoader.moduleProvider (node:internal/modules/esm/loader:195:22)
    at new ModuleJob (node:internal/modules/esm/module_job:63:26)
    at #createModuleJob (node:internal/modules/esm/loader:219:17)
    at DefaultModuleLoader.getJobFromResolveResult (node:internal/modules/esm/loader:172:34)
    at DefaultModuleLoader.getModuleJob (node:internal/modules/esm/loader:157:17) {
  code: 'ERR_UNKNOWN_FILE_EXTENSION'
}

5th Attempt (ts-node & tape-es)

If I combine ts-node and tape-es, ala:

ts-node --esm ./node_modules/.bin/tape-es tests/Alert.test.tsx

I get:

node:internal/errors:490
    ErrorCaptureStackTrace(err);
    ^

TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".tsx" for /Users/evhaus/Git/jest-vs-jasmine/benchmarks/tape/tests/original/Alert/Alert.test.tsx
    at new NodeError (node:internal/errors:399:5)
    at Object.getFileProtocolModuleFormat [as file:] (node:internal/modules/esm/get_format:99:9)
    at defaultGetFormat (node:internal/modules/esm/get_format:139:38)
    at defaultLoad (node:internal/modules/esm/load:83:20)
    at DefaultModuleLoader.load (node:internal/modules/esm/loader:317:26)
    at DefaultModuleLoader.moduleProvider (node:internal/modules/esm/loader:195:22)
    at new ModuleJob (node:internal/modules/esm/module_job:63:26)
    at #createModuleJob (node:internal/modules/esm/loader:219:17)
    at DefaultModuleLoader.getJobFromResolveResult (node:internal/modules/esm/loader:172:34)
    at DefaultModuleLoader.getModuleJob (node:internal/modules/esm/loader:157:17) {
  code: 'ERR_UNKNOWN_FILE_EXTENSION'
}

I know how much you hate the ESM drama, but I'm out of ideas and I'd really love to add tape to the benchmarks (so it can also serve as possible documentation for those who want to try it). Any pointers?

ljharb commented 1 year ago

tape just runs node, so the only extensions it understands by default are js, cjs, or mjs - so if you want jsx/tsx/ts to work directly, you'll have to --require or --import something that can handle those, like babel-node or ts-node.

ljharb commented 7 months ago

@EvHaus have you had any progress on using a loader here, or transpiling beforehand?

EvHaus commented 7 months ago

I haven't spent any more time with it. Would you recommend the transpiling route or the ts-node route? I'm guessing ts-node would be a more realistic workflow from a DX perspective.

ljharb commented 7 months ago

For performance, I'd recommend pre-transpiling; for DX, ts-node.

It might be good to do both, because the delta wouldn't be due to tape.

fregante commented 6 months ago

I just turned my tests into TypeScript and in my case it was as easy as:

  1. include index.test.ts in tsconfig.json
  2. running tsc && tape index.test.js

Not great because it requires running TypeScript first, but in my case I was already doing it: https://github.com/fregante/text-field-edit/pull/29

EvHaus commented 6 months ago

Got it working. The trick was to:

ljharb commented 6 months ago

Glad you got it working! type module just makes things worse anyways :-)

fregante commented 6 months ago

Got it working. Remove "type": "module" from package.json

You got it working by not doing what the issue suggested. Now it's not ESM anymore. It'd be good to keep the issue open. Likewise in my case it worked by not having TS anymore.

ljharb commented 6 months ago

Removing type module means that only .mjs is ESM (as it should be).

@fregante maybe i'm confused, help me understand why this should be open and what we need to fix?

fregante commented 6 months ago

what we need to fix?

Documenting how to deal with ESM/TS given that all the tries with ts-node above failed

ljharb commented 6 months ago

@fregante if you know the answer to that, a PR to the docs would be most helpful :-)

I prefer to avoid native ESM or native TS, so I'm not the expert here.

darcyrush commented 4 months ago

tape just runs node, so the only extensions it understands by default are js, cjs, or mjs - so if you want jsx/tsx/ts to work directly, you'll have to --require or --import something that can handle those, like babel-node or ts-node.

@ljharb Would you be able to provide a (non working) example of running tape using node and passing nodes --import flag? I tried running node_modules/tape/bin/tape --help and node_modules/.bin/tape --help but there is no help output and I have no idea in what order I am meant to pass arguments or flags.

The documentation mentions the --require flag and since you mention import, I tried --import but I'm not sure I am even running tape correctly because running tape as above with an empty --require or --import flag doesn't return an error.

Tape: 5.7.5
Nodejs: 22
OS: Linux
ljharb commented 4 months ago

@darcyrush that sounds like a great idea to add to https://github.com/tape-testing/tape/tree/HEAD/example !

Specifically, it's something like NODE_OPTIONS='--import=ts-node' tape '**/*.ts', for example.

darcyrush commented 3 months ago

@ljharb Thank you for the example. I tried to get ts-node working with a work-around I stumbled across in a ts-node issue, but I still wasn't successful.

I tried the following;

NODE_OPTIONS='--import ./ts-node.register.mjs' node_modules/tape/bin/tape 'test/**/*.test.ts'`

With ts-node.register.mjs being

import { pathToFileURL } from "node:url";
import { register } from "node:module";

register("ts-node/esm", pathToFileURL("./"));

I thought that would transpile to JS before reaching tape, but I'm still not really sure about how the 'piping' works.

binarykitchen commented 4 weeks ago

Managed it to work with this package.json entry for npm run test:

"test": "cross-env NODE_OPTIONS='--import=tsx' tape test/*.ts",