A little teaser for something I have in the works

Unit testing for Nova extension code, in the Nova runtime – all API features available. Current state: test running and live reload functional; sidebar needs some refinements; configuration still missing.

(sorry, @logan, if this abusing this forum, though I think a “community-announcements” tag would be helpful)


No worries. Talking about what you’re working on is definitely welcome.

1 Like

Well, I finally managed to get async tests working after banging my head against the wall for days, because anything using the clear|setTimeout functions would hang the whole runner. It turns out that:

  1. Nova functions behave like JS functions, but aren’t really Function objects. I knew they were missing the prototype from the inability to bind them, so, duh, I guess, but
  2. third party JS libraries assume otherwise and get up to all kinds of shenanigans that will dismally fail absent a real JS function,
  3. which means you have to replace the global instances of these functions by a JS function wrapper around them to unblock things. In this case, clear|setTimeout, for QUnit’s sake.
  4. To add assault to injury, Nova’s clearTimeout does not behave according to spec; specifically, it throws an error on a missing timeout ID where it should be a no-op, a possibility no code out there guards against.


1 Like

Thank you for the notes on clearTimeout and invalid arguments. I’ve made a note and we should be able to fix that for our next release.

As for the fact that they aren’t functions, this is likely a consequence of using JavaScriptCore’s Objective-C bridging. The only way I could think to get around it would be to do what you’ve done—wrap the current functions with new public versions that were pure JavaScript. That’d be easy enough for some small cases (like this), but would likely be unfeasible for all object types and methods in the runtime, unfortunately.

Cool; may I suggest you also have a look at clearInterval while you do? While the latter’s docs do not contain the same note, they do state both functions can be used interchangeably, so I’d assume the no-op thing applies to it, too.

Yeah, JavaScript for Automation works the same way. I meant the “duh”; it’s a limitation I am aware of at the conceptual level. Still, it’s easy to forget (my code is peppered with comments à la “can’t bind() Nova API functions” where I did) and it might be worth adding a caveat to the docs. All in all, JSC’s ObjC bridging works amazingly well, and Nova’s API is well designed (case in point, it is implementing globalThis, which allowed me to push my wrappers in the runtime environment painlessly), so I am not complaining.

Also, @logan, if I may bend your ear a little more, there is a kind of wish list that has developed from my experiment:

  1. Multi-line items in TreeViews. One line gets really short when you want to convey some information with the item, as happens with test results. Barring this, providing some alternative section view type with a flat, more textual structure would work too, as one could create a parent-child section relationship to provide more information for the selected TreeView item in that case (tooltips really do not cut it when it comes to that). People have been creating information type panels in sidebars, which are shallow (no child items); those would also greatly profit from that kind of capability, I think.
  2. Make FileSystem.listdir() accept a glob pattern like FileSystem.watch() does. Right now, I have to bridge between the pattern I need to provide to the latter and the literal path the former needs to get my test files, which is a bit of a pain. Not trivial, as accepting “**” means recursive listing, but that would be so very helpful.
  3. Document that FileSystemWatcher fires two events for changes, first an “old state” one, then a ”new state” one. Now, that is actually very cool, as it allows you to figure out what happened (one event, file exists: file addition to the watch path; the inverse means file removal; two events mean file modification when identical, file renaming when not), but should be documented. Of course, having the watcher itself do the figuring out and returning a matching event would be even better. I have a hand rolled version debouncing processing by 1 ms to wait for the second event, but that suffers from race conditions in mass changing situations.

From the department of “it might just be me, but”, we have:

  1. I had a hard time figuring out that TreeViews need to match a sidebar section ID, not a sidebar ID. The code provided by the extension generator names both the same, so the more dense readers (like me) can be led astray.
  2. I can’t for the life of me get TreeView.reveal() to expand parents of items to reveal, even while providing a “reveal” count vetted to match the hierarchy (and being below 3, I hasten to add). Selecting works, expanding does not; am I missing something?

Finally, fresh out of the department of “wouldn’t that be nice, probably not worth the effort, tho”, I have one item:

  1. Refreshing TreeViews via root level (argless / null) reload() flickers something fierce. While reloading an existing element is smooth as butter, root level reloads make the tree disappear, then rebuild, which is visually rather irritating. As noted, the internal diff of the source needed to not make this happen is probably not worth it, but, hey, I’m wishing for a pony already, might as well …

Not to toot my own horn (OK, I lie: totally to toot my own horn), I just found out about a non-trivial contract change in the Nova API because one of my extension tests abruptly failed right after updating, with no changes in any code of mine .

I wasn’t entirely sure how far I wanted to carry this thing, but I’ve pretty much set my mind to make this a kinda meta extension for extension development (tentatively named NovaNova) now. Talk of a motivator.


@kopischke I’m definitely interesting in seeing something like this, embedded in a stater template.