Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v18 roadmap #821

Closed
26 tasks
isaacs opened this issue May 4, 2022 · 119 comments
Closed
26 tasks

v18 roadmap #821

isaacs opened this issue May 4, 2022 · 119 comments

Comments

@isaacs
Copy link
Member

isaacs commented May 4, 2022

This is the roadmap for node-tap v18.

Caveat: Tap is a side project, no one is paying me to do this. So this is not a timeline or schedule or itinerary, or even an estimate. It'll take as long as it takes, but this is what I'm thinking will go in it.

Might not all be in one v18, some of these might be pushed off until later. (Especially: the ESM/TS stuff is much more pressing, so Plugins might come in v19 or later.)

The roadmap for v17 is just that we'll start emitting TAP version 14. That's a breaking change, but not a big one.

Overall Codebase/Architecture/API

  • rewrite in typescript. Big task, but it'll force dogfooding a lot of persistent issues, and make it easier for people who rely heavily on editor affordances.
  • actual first class "just works" support for ESM, TypeScript, and "type":"module" projects:
    • use C8 instead of NYC
    • use @tapjs/processinfo for managing coverage, loaders, and which files were run by which tests. This presents some challenges, because it can only work if a loader is used, so what do we do to support node ./test.js? Maybe if the loader is needed and not present, we crash and tell the user to do node --loader=tap/load ./test.js? Automatically respawn if it's not present? Idk, but that'd be annoying to lose.
    • add t.mockESM(module, mocks)=>Promise to mock either ESM or CJS modules.
      • Alternatively, maybe just make t.mock() async? Bigger breaking change though, so idk.
  • Do not auto-bind any methods on Test objects except the top level TAP object, so that import { test } from 'tap' continues to work. If you want to do setTimeout(t.end), you'll have to use setTimeout(() => t.end()) moving forward.
  • Refactor t.mock, snapshots, t.before, t.beforeEach, t.afterEach, t.after, t.fixture, t.teardown, and most assertions (probably all except pass and fail?) to be Plugins (see below).
  • Add t.worker(...) method, modelled after t.spawn() Idea: t.worker() method #812
  • Set all configs in the environment, and read them all from the environment. Consider using http://npm.im/@isaacs/cli-env-config, maybe with a wrapper to do the usage printing? idk.

CLI

  • tap <noargs> - Run all tests matching the test regexp.
  • tap report - Run the coverage report, and do not run any tests. Respond to coverage-report option.
    • This means that tap --coverage-report=html is no longer a "run no tests and just run report" command.
  • tap <files...> - Run only the selected tests. If you have a test named 'report', then this is weird, you'll have to do tap ./report for that.

Plugins

  • Add async loadPlugin(specifier) to the Test prototype. If the module provides of specific named function exports (not sure what those are yet), then they'll be called and provided a Test object to work with. Special designator tapjs:... can be used to reference built-in plugins.
  • Add unloadPlugin(specifier) to remove plugins from the set. This can be used, for example, to disable mocks in a given test, etc.
  • Ideas for plugin hooks worth having: (see nlfspec)
    • load - Called one time to decorate the Test object initially receiving the plugin. All methods added with addMethod be inherited by all child tests (unless they call unloadPlugin). To add a property (eg, a t.nock object), it'll have to be set up in the before hook.
    • unload - Called once when the plugin is unloaded, with a reference to the test that is unloading it. (Either the initial Test passed to load, or one of its descendants.)
    • before - called one time at the start of the test object main function. If the Test object has no parent, this is called immediately after load.
    • after - called one time at the end of the test, but before processing the t.end(), so assertions are allowed. Can return a promise, in which case it will be awaited before moving on to the teardown phase.
    • teardown - after the end phase, but before moving on to the next test, after the test is already ended, so assertions not allowed. If it returns a promise, it's awaited before moving on to next test.
    • NB: There's no need for beforeEach or afterEach, because before and after already are called for each subsequent child test. These methods will be added by the default builtin tapjs:lifecycle plugin.
  • t.addMethod('name', (t, ...args) => {...}) to add a method to the relevant t object which is inherited by all its descendants.
    • addMethod throws if the property is already defined. It can only be removed by unloading the plugin that added it.
  • t.addProperty('name', descriptor) to add a property to the test object and all of its descendants. Simple values can be t.addProperty('foo', { value: 'bar' }), but can get fancy with t.addProperty('foo', { get () { return this.bar }}) or whatever.
    • addProperty throws if the property is already defined. It can only be removed by unloading the plugin that added it.
    • maybe t.addMethod(name, fn) is technically just t.addProperty(name, { value: function () { return fn(this) }})?
  • Add option to t.matchSnapshot() to specify a name which is appended to the t.snapshotFile, before the .test.cjs ext. (see nlfspec) So a plugin can easily create t.nock.snapshot() by having the nock object call t.matchSnapshot(data, { snapshotExt: 'nock' }) or something.
  • Add config array plugins, which takes a list of names that can be passed to require() or import(). (So either package names or module specifiers will work, as long as they're resolvable in the project.) Plugins named in the config are loaded on the root TAP object.
  • Add config array exclude-plugins, which takes a list of plugin specifiers to not load. (Can be handy if you want to limit how much of tap loads for your tests, if you know you're not using mocks or snapshots or something? idk.)
@isaacs isaacs pinned this issue May 4, 2022
@Stargator
Copy link

Thank you @isaacs . Tap is the only library I found I could use for my NodeJS-based TypeScript library. Though I am only using the mock method. Would you consider breaking tap up into different packages, like mocks, fixtures, assertions?

If you do consider it, I know that'll likely not happen anytime soon and it could very well make it more complicated for you to maintain consistency across multiple packages. So I can understand why it wouldn't be a consideration either.

@isaacs
Copy link
Member Author

isaacs commented Sep 1, 2022

I am planning to implement those things as plugins, so yes, they'll be separated out better than they are today.

@koshic
Copy link

koshic commented Oct 15, 2022

@isaacs , do you have any progress on that? There are too many empty checkboxes, and task count is really huge. I'm asking due to find options to leave Jest, because in ts/esm/esbuild/c8 world it looks a bit 'legacy'.

@isaacs
Copy link
Member Author

isaacs commented Oct 19, 2022

I haven't made much progress on it, unfortunately. Doing a new startup, and have been getting a bit into some other hobbies. I have a feeling I'll get to this soon, though. There's a rough proof of concept about how to do plugins in a way that preserves type information so that TS can grok how each plugin decorates the main tap object, and you can get all the autocorrect goodness in your editor. That was really the hardest part, the rest is mostly just typing.

The downside is that the plugin system won't be quite as flexible as I'd designed in the comment at the start of this issue, unfortunately. There's just no way to get static analysis to play nice with dynamically mutated objects. So, basically, it'll require a build step to create the plugin types. If you've ever used prisma, it'll be similar to that, where you run prisma generate to create the client based on your data model. So there'll be an explicit step you can run whenever, or we could make the tap runner generate the plugin types whenever the set of plugins changes (or if they've never been generated).

So that's big rock number 1. I have a proof of concept, but no working code for it yet.

Big rock number 2 is doing coverage and tracking process info like tap was using nyc for (which does more than just coverage, also used for --watch, --changed and so on). That's handled by the @tapjs/processinfo package, which is functionally about complete, but still lacking some test coverage.

Big rock number 3 is mocking. I have a proof of concept approach, but no fully working integration-ready code for it yet.

In the meantime, there are a few approaches you can use, here's what I've been relying on while using TypeScript:

In package.json, put this:

{
  "scripts": {
    "prepare": "tsc",
    "test": "c8 tap test/*.ts",
    "snap": "c8 tap test/*.ts",
    "pretest": "tsc"
  },
  "tap": {
    "coverage": false,
    "node-arg": [
      "--loader",
      "ts-node/esm"
    ],
    "ts": false
  }
}

Then you can do npm test, and it'll compile what it needs to, and run all your tests using the ts-node loader. Coverage in that case is handled by c8, which is a bit clunky in some ways (compared to the nice integration with nyc), but it does work, and should cover most if not all typescript and ESM cases.

@isaacs
Copy link
Member Author

isaacs commented Oct 19, 2022

("working", for me and for tap, means "has full test coverage and docs", along with the actual code that does the thing)

@koshic
Copy link

koshic commented Oct 20, 2022

Thx for such detailed answer!

Currently I work with esmock (looks similar to your PoC) author to integrate it in my Yarn PnP / TS / esbuild / jest / full ESM pipeline without ugly hacks, so only one missing point - more or less functional TAP formatter and / maybe some support in coverage collecting tasks (like coverage map) due to C8 is not so user friendly. In spite of Jest have all those things out of the box (not really, but most of them can be replaced / fine tuned), I don't like Jest concept with VM and 'let's repeat all things that node does in 2022'.

And node-tap may be the right choice... if it will be well maintained, you should know that pain. Thank you again, hope that rocks are marble not granite )

@SagnikPradhan
Copy link

There's just no way to get static analysis to play nice with dynamically mutated objects.

Can we do something like this?

@mithray
Copy link

mithray commented Nov 16, 2022

I tested all the common JS testing frameworks, and I hate all of them except tap because they all use implicit globals, which makes it difficult to understand how a program works, pollutes namespace, and prevents modular composition.

@isaacs
Copy link
Member Author

isaacs commented Mar 13, 2023

Progress continues on this, but it's been slow going.

  • steadily progressing through the various deps that I wanted updated for typescript and esm support (glob, rimraf, mkdirp, minipass, lru-cache, etc.)
  • Considering some way to add per-folder .taprc files.

I have a project now where I have a bunch of utils (testing with the c8/ts config described above) but then I also have a bunch of react components I want to test with react-testing-library and global-jsdom, and routes that I'll want to run end-to-end tests with puppeteer. So the components folder needs a different --loader node arg, and tests in the routes folder need a different --before module argument (to set up puppeteer, create a throwaway postgres db, etc.)

I hope to pull out of the depth-first traversal through lowlevel deps now, and get back to building up the @tapjs/core class hierarchy that'll support the plugin system.

The class hierarchy will look like this:

Base - streaming (extends Minipass), parsing, test status, config handling ({ bail: true }, etc)
+-- Spawn - run a child proc, consume output as tap stream
+-- Stdin - consume stdin as tap stream
+-- Worker - run a worker thread, consume output as tap stream
+-- PluginBase - generate tap stream, built-in set of assertions
  +-- (builtin plugins...) - add functions for spawn, assertions, snapshot, mock, etc
    +-- (user plugins...) - whatever the config says to load
      +-- Test - the test class exposed to user
        +-- TAP - the root test runner

The types are all working in my tests, with plugins like this:

import { TestBase, AssertionExtra } from '@tapjs/core'
export const plugin = <
  T extends new (...a: any[]) => TestBase
>(
  Base: T
) =>
  class IsString extends Base {
    isString(x: unknown, msg: string = 'expect string', extra: AssertionExtra = {}) {
      const e = { ...extra, found: typeof x, expect: 'string' }
      return typeof x === 'string' ? this.pass(msg, e) : this.fail(msg, e)
    }
  }

then you'd install the plugin with:

npm i tap-plugin-is-string

And load it with:

# .taprc
plugins:
  - 'tap-plugin-is-string'

And then the system can build up all the configured and builtin plugins into a single class defined in node_modules/.tapjs/core/test-base.{js,.d.ts} so everything Just Works.

In the fullness of time, I imagine that puppeteer and react-testing-library will be plugins, so nested configs would mean having separate plugin builds for each folder. Which... idk, seems tricky. But since every test is an isolated process, it could be done by walking the test folder tree for all the config files, and building the plugged-in client on demand with a sha of the plugins used. You'd still have to run tap build (or run tests) to have the types in your editor, of course, but that's a one-time thing.

@koshic
Copy link

koshic commented Mar 13, 2023

@isaacs , looks promising, thx!

But what if config files... are "normal" programs too, like tests? )

What I mean - it's a bit wrong idea to import tap (and types, which is more important) from static package. Let's turn config (because you need config to specify plugins) into another entry point, like that:

import { config } from "./tap.js"; // my local Tap implementation in 11 lines + config function
import { TapPluginIsString } from "./tap-plugin-is-string.js"; // simple instanceof-based implementation

class TapPluginXxx {
  isXxx() {
    return true;
  }
}

export default config(new TapPluginIsString(), new TapPluginXxx());

Next, import tap from your config, not from original package:

import tap from "./config.js";

await tap.test("Some test", async () => {
  console.log("isString(1): ", tap.isString(1));
  console.log("isXxx(): ", tap.isXxx());
});

image

The magic is inside 'config' function - Proxy to add new methods to the Tap instance, and some types to merge plugin types together:

export const config = <T extends object[]>(
  ...plugins: [...T]
): Tap & Spread<T> => {
  const tap = new Tap();

  // @ts-ignore
  return new Proxy(tap, {
    get(target, p) {
      if (p in target) {
        // @ts-ignore
        return target[p];
      }

      for (const plugin of plugins) {
        if (p in plugin) {
          return (...args: any[]) =>
            // @ts-ignore
            Reflect.apply(plugin[p], plugin, args);
        }
      }
    },
  });
};

image

What do you think about this pattern?

@isaacs
Copy link
Member Author

isaacs commented Mar 13, 2023

That could definitely work, and actually isn't too far off from the build script I'm imagining.

But it's kind of annoying to have to manually load up your plugins in that way. What I want to do is this:

$ tap plugin add tap-plugin-is-string
# now the plug is loaded and `import t from 'tap'` Just Works
$ tap

It'll still be possible to configure plugins at run time if you like, but I just don't wanna do that in all my projects, too lazy 😅 Trying to reduce the boilerplate as much as possible.

@isaacs
Copy link
Member Author

isaacs commented Mar 13, 2023

Oh, actually, the mechanism you're proposing is actually pretty different. I'm just stacking mixins. But that also means I can detect if there are method incompatibilities.

The main drawback of using mixins in TypeScript is that you can't mark fields as protected/private/readonly. So if you want something to be private or readonly, you have to do it with javascript #private properties.

@koshic
Copy link

koshic commented Mar 13, 2023

In my solution you can build any object what you want, you free to use anything including mixins )

What about boilerplate - theoretically you are right, especially if you do not expect that user will not edit configuration manually. You can use some strange file format for configuration (or binary) and provide set of commands to work with it to hide details from users. Okay.

But what is the difference between autogenerated crappy .xxxxrc and autogenerated config.ts? No difference. Except ability to import config.ts and get all types without 'tap build'. Also, you can add config.ts to the 'imports' section of package.json (at plugin installation step), and write in readme: '...after adding plugins in TS project, you can use tap via "import tap from "#tap"'.

@isaacs
Copy link
Member Author

isaacs commented Mar 15, 2023

@koshic Running into some issues with my "mixins everywhere" approach, actually. TypeScript really does not give them first class service, it's a shame, because mixins really are "the pattern" for this kind of problem. But I'm already so tired of "Base constructor must have same return type" and useless (...a:any[]) constructor signatures.

I had previously explored a somewhat approach to what you suggested, but still using a build step that generates the types and Test class, just automatically instead of in a manually loaded config file. I'd abandoned that direction because Proxies are hella slow, compared to methods added to the prototype chain, and getting the plugged-in value type understood by tsc was kind of challenging, but maybe need to try something like that again.

@koshic
Copy link

koshic commented Mar 15, 2023

The proxy used in my example is intended only for simplification, in real code it can be replaced with a more or less complex object building. Any pattern what you want.

I believe that 'classic' plugin-based architecture may be middle-term solution, a kind of step into ESM world. Time is running out - Node test-runner grown enough to be used as main solution for small projects.

@SagnikPradhan
Copy link

Running into some issues with my "mixins everywhere" approach, actually. TypeScript really does not give them first class service, it's a shame, because mixins really are "the pattern" for this kind of problem.

As I was reading the new messages was wondering when you'd run into this issue. It's the reason why my plugin system didn't use classes, which they were at first, but just couldn't get the types to work right.

Also soft agree with @koshic on having config as entry point rather than having a generation step. Though, if we do end up having a generation step, I hope it will be possible to generate the client outside node_modules?

@isaacs
Copy link
Member Author

isaacs commented Mar 15, 2023

The client can be anywhere really, but the point is that it should be returned when doing import t from 'tap'

I'm thinking of just going back to the "function that takes a Test object and decorates it" approach. Then I can specify the Test type as TestBase & ReturnType<typeof Plugin0> & ReturnType<typeof Plugin1> & ... which is much simpler, albeit less performant and powerful.

@isaacs
Copy link
Member Author

isaacs commented Mar 15, 2023

Time is running out - Node test-runner grown enough to be used as main solution for small projects.

I'm not worried. People still use mkdirp and rimraf very extensively. The built in methods are good enough for many cases, but I don't see node adding the entire feature set of tap or jest into core. When people need that, they look to user land projects. And I need that, so I'm building it. Already, even with the current inconveniences of using tap with typescript, I find its added features useful, and very much miss stuff like --watch and --changed, which don't work with the current state of things, since they rely on nyc.

@isaacs
Copy link
Member Author

isaacs commented Mar 15, 2023

The challenge seems to be getting t.test((t) => { ... }) to know that the t argument will be typeof this, whatever this happens to be. I had some proofs of concept where I had that working, but somewhere in the process of actually building up the TestBase class with all its flow control and options and whatnot, I guess that got lost somehow.

@isaacs
Copy link
Member Author

isaacs commented Mar 15, 2023

Ah, nevermind. I see what I was doing wrong. Need to make it less generic, and also explicitly declare the inheritance chain as a multiple-inheritance interface. This actually works fine. https://bit.ly/ts-mixin-plugins (shortened ts playground link)

@isaacs
Copy link
Member Author

isaacs commented Mar 15, 2023

Hmm. Actually that won't work unless I figure out a way to make Test objects not be Streams or EventEmitters, or else it blows up with a slew of Exported variable 'plugin' has or is using name '_DOMEventTarget' from external module "events" but cannot be named. (tsserver 4023).

@koshic
Copy link

koshic commented Mar 15, 2023

@isaacs looks a bit strange - how are DOM types related to tap? did you set 'lib' in tsconfig to use only 'esxxxx' instead of default one, includes DOM?

@isaacs
Copy link
Member Author

isaacs commented Mar 15, 2023

The issue is that the Base class derives from Minipass, which derives from node's Stream. Stream has a bunch of references to WebStreams (so you can do stuff like fs.createReadStream(path).asWebStream() or something), which are covered in all sorts of DOM Event junk. Just making the stream a property rather than a base class worked around that.

@isaacs
Copy link
Member Author

isaacs commented Mar 16, 2023

Ok, this is actually kinda working?

A t.beforeEach plugin: https://github.com/tapjs/core/blob/main/src/plugin/before-each.ts
A t.afterEach plugin: https://github.com/tapjs/core/blob/main/src/plugin/after-each.ts
Putting them to use (manually, but in the way that the generated client will as well) https://github.com/tapjs/core/blob/main/test/scratch.ts

Promise returns are broken for some reason, but it's kind of working with t.end() at least.

TAP version 14
first beforeEach
second beforeEach
# Subtest: hello
    ok 1 - this is fine
first beforeEach
second beforeEach
child beforeeach
    # Subtest: hello child
        ok 1 - also fine
        1..1
child aftereach
parent aftereach
    ok 2 - hello child # time=887ms
    1..2
parent aftereach
ok 1 - hello # time=4573ms
1..1

@isaacs
Copy link
Member Author

isaacs commented Mar 16, 2023

Oh. That's annoying. TS adds a bunch of garbage properties if a mixin or its base class have anything private.

Screenshot 2023-03-15 at 17 57 31

The whole point of using private properties in the first place is to reduce noise! Jesus, TypeScript really does not want you to use mixins, this is ridiculous lol

@koshic
Copy link

koshic commented Mar 16, 2023

They also were against .ts extensions in imports. 5, 6, 7 years? Don't remember ) Now we have an ugly flag and mandatory 'noEmit' as a bonus.

@isaacs
Copy link
Member Author

isaacs commented Mar 16, 2023

Yeah... between the garbage properties and the completely unnecessary #private that TS sticks in the TestBase class specifically to make it more annoying to extend or pass around, I really think maybe I'm just going to have to ditch the entire idea of userland mixin extensions. Using proxies (or just Object.assign() stacking) is an option, but (a) I've been burned by Proxies before; they're great until they're really not, can be a nightmare to debug once the complexity gets too high, and have a nasty habit of tanking performance pretty badly. And (b) if it's going to require the user to do something special in their tests to enable it (like writing a config loader or something) then that pretty much loses all the magic anyway. At this point, I've explored pretty much every way there is to do this, and I'm at a loss. Dynamic types just go dramatically against the grain of TypeScript's ethos, sadly. Even after jumping through all the hoops to spell out exactly what the types will be, it's still just not a great experience.

So, I'm thinking, ditch the mixins and plugins entirely, and instead start from a clean slate with a dead simple module that just generates tap and can be extended classically in a plain old OOP kind of way. Then to add something like a plugin to add t.nock(), or load jsdom globals and integrate with @testing-library/react and @testing-library/user-event, or whatever else, it can either be a configured --require or --loader node arg, or pull in the Test class and extend it to provide its own t root test, or be a thing where you call const extended = extensionMethod(t) any time you want the added functionality.

There are some ways that TypeScript makes JS much more powerful, but this is one aspect where it really feels like it's holding the language back.

@isaacs
Copy link
Member Author

isaacs commented Aug 17, 2023

Oh yeah, I forgot to do t.error, whoops. Good catch.

The import error is weird. It seems like tap 18 is getting the older version of tap-yaml, even though it's dep should be pinned to the new version. I'll try to repro that as well.

@isaacs
Copy link
Member Author

isaacs commented Aug 17, 2023

@jsumners Oh, hey, this is a thing that's been an open question for years now, but I never changed it because it was annoying to do in the old codebase and would've been a breaking change (and I don't really use t.error() much, so never felt too motivated) but now that I'm looking at it, you get to decide ;)

If you have a failing t.error(er) (ie, it got an error) is it better to have the diagnostics point to the source of the error, ie, the stack that's on the error object, or the place where your assertion is failing? Either one is trivial to do.

Ie, if you have a test like this:

import t from 'tap'
t.test('t.error', t => {
  const er = new Error('this could be anywhere')
  t.pass('this is fine')
  t.error(er)
  t.end()
})

Is it more useful to see this:

          source: |
            import t from 'tap'
            t.test('t.error', t => {
              const er = new Error('this could be anywhere')
            -------------^
              t.pass('this is fine')
              t.error(er)

or this

          source: |2
              const er = new Error('this could be anywhere')
              t.pass('this is fine')
              t.error(er)
            ----^
              t.end()
            })

@isaacs
Copy link
Member Author

isaacs commented Aug 17, 2023

I guess, could also just do both?

          errorSource: |
            import t from 'tap'
            t.test('t.error', t => {
              const er = new Error('this could be anywhere')
            -------------^
              t.pass('this is fine')
              t.error(er)
          source: |2
              const er = new Error('this could be anywhere')
              t.pass('this is fine')
              t.error(er)
            ----^
              t.end()
            })

@isaacs
Copy link
Member Author

isaacs commented Aug 17, 2023

Yeah, both is better.

Screenshot 2023-08-17 at 11 16 23

@jsumners
Copy link
Contributor

/me gets back in from doing yard work...

Happy to have helped 🤣

Seriously though, I don't have a strong opinion. If I had to choose, I'd probably choose showing the t.error(err) line so that I know where in my test file to trace back from.

@isaacs
Copy link
Member Author

isaacs commented Aug 17, 2023

@jsumners Ok, tap@18.0.0-16 published with t.error(), and errorOrigin reporting for all the things where that makes sense.

@jsumners
Copy link
Contributor

@isaacs thanks. In order to keep this thread on topic, I have moved my issues list to #14

@isaacs
Copy link
Member Author

isaacs commented Aug 20, 2023

In order to keep this thread on topic,

"keep"? 😂

@isaacs
Copy link
Member Author

isaacs commented Aug 22, 2023

Added a tap replay command, that's handy. Also fixed a bunch of things that @jsumners found in play testing, and improved bailout reporting.

Remaining blockers:

  • docs. Making steady progress, but ughhhhh there's so muchhhhhh
  • website. Though, I could just ship plain old html, how bad would it really be? It loads fast and it's classic lol
  • ts-node. Right now, it's pulling a git dep because I'm waiting for a new release with some fixes. The git install is super painfully slow. It's not really a blocker, but if ts-node isn't shipped soon-ish, then I will have to bundle it somehow, which would be a tiny bit of a chore. Really, what I probably ought to do is spend some time trying to help get it over the finish line with node 20 support.

@piotr-cz
Copy link

  • website: I think the option of plain old html is not that bad for the moment
  • ts-node: I have the feeling that the maintainer is aiming to publish fixes in v11

@isaacs
Copy link
Member Author

isaacs commented Aug 23, 2023

Website deployed, such as it is: https://node-tap.surge.sh

This will live on https://node-tap.org once it's finished, of course.

@SagnikPradhan
Copy link

Website deployed, such as it is: https://node-tap.surge.sh/

I like the style

@isaacs
Copy link
Member Author

isaacs commented Aug 29, 2023

Docs are now fully styled.

I found a weird issue with how tap plugin add tries to pull the wrong version of a plugin that's incompatible with the current @tapjs/core in use. Once that's fixed, the only item left to do is production deploy, and it's done!

@alexgoldstone
Copy link

alexgoldstone commented Aug 31, 2023

I don't know if this is me not understanding something or if there is something missing from the docs but I've upgraded to try the 18.0.0-21 pre-release and I can see the Typescript plugin is active by running npx tap plugin add @tapjs/typescript and get:

nothing to do, all plugins already installed

However .ts extensions do not seem to be supported as the output I get when running tap is:

2> src/test/users.test.ts
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for
/fastify-test/src/test/users.test.ts
    at __node_internal_captureLargerStackTrace (node:internal/errors:464:5)
    at new NodeError (node:internal/errors:371:5)
    at Object.file: (node:internal/modules/esm/get_format:72:15)
    at defaultGetFormat (node:internal/modules/esm/get_format:85:38)
    at defaultLoad (node:internal/modules/esm/load:13:42)
    at null.load (/fastify-test/node_modules/@tapjs/processinfo/lib/esm.mts:86:21)
    at ESMLoader.load (node:internal/modules/esm/loader:303:26)
    at ESMLoader.moduleProvider (node:internal/modules/esm/loader:230:58)
    at new ModuleJob (node:internal/modules/esm/module_job:63:26)
    at ESMLoader.getModuleJob (node:internal/modules/esm/loader:244:11)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:281:24)
    at async loadESM (node:internal/process/esm_loader:88:5)
    at async handleMainPromise (node:internal/modules/run_main:65:12) {
  code: 'ERR_UNKNOWN_FILE_EXTENSION'
}

 SKIP  src/test/users.test.ts 0 OK 341ms
 ~ no tests found

@isaacs
Copy link
Member Author

isaacs commented Sep 1, 2023

@alexgoldstone Can you post this issue over at https://github.com/tapjs/tapjs/issues? Please include the way that you're running your tests. If you run them just as node blah.ts, then yeah, that won't work. You'd need to tell node the --loader to use, like node --loader=ts-node/esm blah.ts, or let the tap CLI do that for you, by running tap blah.ts.

@isaacs
Copy link
Member Author

isaacs commented Sep 1, 2023

Ok, issue with tap plugin add <pkgname> fixed, now it'll actually allow plugin@<version>, and properly select a version that doesn't have a peerDep on a conflicting version of @tapjs/core.

All that's left now is production deploy, probably going to ship tomorrow or Monday.

  • website CNAME to node-tap.org
  • update all the versions of things so that they're not 0.0.0-123

@isaacs
Copy link
Member Author

isaacs commented Sep 10, 2023

Only thing remaining is for ts-node to land TypeStrong/ts-node#2009, and update to that (probably SemVer-major) release.

Going to look into forking it to @tapjs/ts-node if that doesn't happen soon, tap 18 is pretty much ready to go.

@alexgoldstone
Copy link

@alexgoldstone Can you post this issue over at https://github.com/tapjs/tapjs/issues? Please include the way that you're running your tests. If you run them just as node blah.ts, then yeah, that won't work. You'd need to tell node the --loader to use, like node --loader=ts-node/esm blah.ts, or let the tap CLI do that for you, by running tap blah.ts.

Just to confirm I didn't raise an issue as it was resolved by updating to the latest Node LTS (I was still on the previous).

@isaacs
Copy link
Member Author

isaacs commented Sep 13, 2023

Got everything working on node 20 and typescript 5.2, and swapped to using a forked ts-node so the install is now much much faster. In the process, made a new build tool.

The only task at this point is to get the tests for tshy completed, so that I can remove the dependency on an untested prerelease dep, but getting tap onto it was a great smoke-test, and it needed tap to work with 5.2 in order to use tap for its tests.

That should be done today or tomorrow, and then release!

@isaacs
Copy link
Member Author

isaacs commented Sep 14, 2023

Tshy is out, tap release tomorrow!

@isaacs
Copy link
Member Author

isaacs commented Sep 15, 2023

$ npm view tap version
18.0.0

Thanks for all the feedback and input in this super long mega-issue. I'll write a blog post this weekend and publish that Monday or Tuesday.

In the meantime, npm i tap@latest -D and go write some tests 😄 🎉

@isaacs isaacs closed this as completed Sep 15, 2023
@jsumners
Copy link
Contributor

@isaacs Are you willing to dual license all of the tap modules to MIT? It seems that OpenJSF projects are unable to include BlueOak licensed modules. I think that is silly since it's a clarified MIT, but 🤷‍♂️ what do I know about lawyering?

@isaacs
Copy link
Member Author

isaacs commented Sep 15, 2023

@jsumners Not willing, no, sorry. OpenJSF can either accept BlueOak, buy a custom license from me, or stop using my code. glob is really the much bigger issue there.

@ValchanOficial
Copy link

I'm getting this error

tap: 18.6.1

.taprc

check-coverage: true
...

.error

..../node_modules/jackspeak/src/index.ts:780
        throw new Error(`Unknown config option: ${field}`)
              ^
Error: Unknown config option: check-coverage
...

@isaacs
Copy link
Member Author

isaacs commented Jan 18, 2024

Please open new issues for new problems, rather than comment on closed issues.

check-coverage is no longer a thing. Tap always checks coverage now. You have to explicitly tell it to be ok with lacking coverage, by doing --allow-incomplete-coverage or --allow-empty-coverage. Full coverage ftw!

@tapjs tapjs locked as resolved and limited conversation to collaborators Jan 18, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests