Skip to content

test: run the jsx-runtime suite natively on tish test - #17

Merged
spacedevin merged 5 commits into
mainfrom
feat/tish-test
Aug 12, 2026
Merged

test: run the jsx-runtime suite natively on tish test#17
spacedevin merged 5 commits into
mainfrom
feat/tish-test

Conversation

@spacedevin

Copy link
Copy Markdown
Member

Adds the native tish test runner (tish 3.7.1) for the vnode factory, and keeps every existing leg. npm test is still test:dom && test:hmr — the DomHost reconciler needs a real DOM and the HMR test needs Vite. Neither can move to the VM, and neither should.

Unblocking the native suite

test/jsx-runtime.test.tish was prepared earlier but had never actually run: src/jsx-runtime.tish imports h/Fragment from Lattish.tish, which used JS undefined in twelve null-ish checks, and Tish has no undefined. Those become plain null checks — the emitted JS is unchanged in behaviour, since the JS target lowers x !== null to x != null, which still matches undefined.

Worth recording: typeof x === "undefined" is not a portable spelling, despite looking like one. Tish lowers typeof to a shim reporting "null" for both null and undefined, so the comparison can never be true on either target. I tried it first and it broke memo in the JS emit. The four genuine typeof window / typeof console host guards are untouched — those check for a host, not a value.

Duplicate-export fix

tish build --target js did not emit export lines up to 2.12; from 3.7 it does. Appending them unconditionally then produced:

SyntaxError: Duplicate export of 'exposeLattishHmrGlobals'

and the bundle failed to load outright. There were two independent copies of that workaround — scripts/append-exports.mjs and a hand-rolled one inside test/hmr-vite.test.mjs — so fixing only the first left the HMR leg red. Both now append only what the emitted output is missing, which works on either tish version rather than trading one breakage for another.

Anyone upgrading a project that post-processes tish JS output will hit this.

Also

  • test/lattish.test.tish -> test/lattish.jsemit.tish. It is a JS-emit suite, and .test.tish now means "native tish test suite" — leaving it would make a bare tish test try to run a jsdom suite on the VM. Matches the existing jsx-runtime.jsemit.tish.
  • test/run-tests.mjs gains a TISH_BIN override for running against a locally built tish; defaults to the installed package.
  • @tishlang/tish 2.12.0 -> 3.7.1.

Test plan

  • npm test — DOM (jsdom) + HMR (Vite), both green
  • tish test — 5/5 native
  • All four bundles rebuild and load cleanly (Lattish, lattishHmr, jsx-runtime, jsx-dev-runtime)

Adds the native `tish test` runner (tish 3.7.1) for the vnode factory, and keeps every existing
leg: `npm test` is still `test:dom && test:hmr`, because the DomHost reconciler needs a real DOM
and the HMR test needs Vite. Neither can move to the VM, and neither should.

`test/jsx-runtime.test.tish` was prepared earlier but had never run — `src/jsx-runtime.tish`
imports `h`/`Fragment` from `Lattish.tish`, which used JS `undefined` in twelve null-ish checks,
and Tish has no `undefined`. Those become plain `null` checks. The emitted JS is unchanged in
behaviour: the JS target lowers `x !== null` to `x != null`, which still matches `undefined`.

Note `typeof x === "undefined"` is NOT a portable spelling here, despite looking like one — Tish
lowers `typeof` to a shim that reports "null" for both null and undefined, so the comparison can
never be true on either target. The four genuine `typeof window`/`typeof console` host guards are
left alone; they are checking for a host, not for a value.

`test/lattish.test.tish` is renamed to `test/lattish.jsemit.tish`. It is a JS-emit suite, and
`.test.tish` now means "a native tish test suite" — leaving it would have made a bare `tish test`
try to run a jsdom suite on the VM and fail. Matches the existing `jsx-runtime.jsemit.tish`.

Two copies of the same obsolete workaround are made idempotent. `tish build --target js` did not
emit export lines up to 2.12; from 3.7 it does, so appending them unconditionally produced
`SyntaxError: Duplicate export of 'exposeLattishHmrGlobals'` and the bundle failed to load. Both
`scripts/append-exports.mjs` and the hand-rolled copy inside `test/hmr-vite.test.mjs` now append
only what the emitted output is actually missing, so this works on either tish.

`test/run-tests.mjs` also gains a `TISH_BIN` override, so the suite can run against a locally
built tish; it still defaults to the installed package.
@codacy-production

codacy-production Bot commented Aug 12, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 0 complexity · 0 duplication

Metric Results
Complexity 0
Duplication 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

Codacy flagged the export scanner I added. Most of the 28 findings are rules the file already
broke (`const`, `for-of`, arrow functions) re-surfaced because the lines changed — Codacy's
default ruleset bans ES2015 and wants type annotations in a `.mjs` file, neither of which fits an
ESM build script. Two were mine and worth acting on: both regexes use `\s+`, which backtracks
quadratically on a long whitespace run.

The scan is now plain string work — no regex, so nothing to backtrack. Same behaviour, verified
against all four bundles.

While here, the helper is exported and the HMR test imports it instead of carrying a second copy
of the same logic. That duplication is what made the first fix incomplete: `append-exports.mjs`
was corrected and the HMR leg stayed red, because it re-appended from its own copy. The build
step only runs when the module is invoked directly, so importing it has no side effects.
The build post-processed every bundle to append ESM export lines, because `tish build --target js`
did not emit them up to 2.12. From 3.7 it does, so the step was appending duplicates and the
module failed to load outright with `SyntaxError: Duplicate export of '<name>'`.

Making the append idempotent (the previous commit) fixed the symptom while leaving a build step
whose whole reason for existing was gone. It only had one remaining job: `jsx-runtime.tish` and
`jsx-dev-runtime.tish` import `Fragment` but never re-exported it, so the automatic JSX runtime
contract was being satisfied by a script bolting the name onto the emitted output rather than by
the source.

Both now `export { Fragment }` explicitly, which is where that contract belongs, and tish emits
every name for all four bundles. `scripts/append-exports.mjs` and its copy inside the HMR test
are deleted.

This also clears the Codacy findings on that file, which were unfixable in place: its ruleset
forbids `import`/`export`, `const`, `for-of`, `Set`, and `Array.prototype.includes`, none of
which an ESM build script targeting Node 22 can avoid. The pre-existing version violated exactly
the same rules — they only surfaced because the lines changed. Deleting the file is the honest
resolution rather than an exclude entry.
The previous commit deleted scripts/append-exports.mjs but the import in
test/hmr-vite.test.mjs was not included in it, so CI failed with
ERR_MODULE_NOT_FOUND while the working tree was fine.
Companion to the previous two commits: the `build` script still ran
`node scripts/append-exports.mjs` after the file was removed, so CI failed at
the Build step with MODULE_NOT_FOUND.
@spacedevin
spacedevin merged commit edd30bd into main Aug 12, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant