Skip to content

Test RedHerb's Vue components - #1218

Draft
malberts wants to merge 3 commits into
masterfrom
feature/redherb-vue-component-tests
Draft

Test RedHerb's Vue components#1218
malberts wants to merge 3 commits into
masterfrom
feature/redherb-vue-component-tests

Conversation

@malberts

@malberts malberts commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Fixes #799

RedHerb is the live reference for extending NeoWiki, but nothing exercised its frontend, so a change to
NeoWiki's public API could rot the examples unnoticed. Each of its Vue components is now mounted against
the real ext.neowiki API, asserting the behaviour an extension author copies it for. The specs sit with
NeoWiki's own frontend tests, in resources/ext.neowiki/tests/RedHerb/, alongside where the PHP examples
are already covered from tests/phpunit/RedHerb/. The registration
package file is driven through mw.hook( 'neowiki.registration' ) the way NeoWiki drives it in production,
so the specs cover the wiring as well as the components.

Loading ResourceLoader package files in vitest

RedHerb's resources are ResourceLoader package files: CommonJS closures that pull vue, ext.neowiki and
the CodexModule-generated codex.js and icons.json in with require(). Vite only understands ES modules,
so resources/ext.neowiki/tests/RedHerb/resourceLoaderCommonJs.ts rewrites require() and
module.exports for files under RedHerb's resources directory, and resolves the provided modules as
ext.neowiki would. That last part
matters: RedHerb has no node_modules of its own, and resolving from its own directory would either fail
or hand the examples a second copy of vue and Codex.

Keeping the interop in test-only config leaves RedHerb itself untouched, so it stays copyable into a real
extension. The alternative, giving RedHerb its own vitest setup, would have had to reach into
ext.neowiki's TypeScript sources or its build output, which a real extension cannot do either.

Test collaborators

The specs use the real composables, registries and value components rather than mocking them, since the
point is to catch NeoWiki changes reaching the examples. Only the store methods that would hit the network
and the sortable drag mechanism are stubbed.

CI

ci-ts triggered on resources/** only, so editing a RedHerb component ran neither the new tests nor the
existing ones. It now also triggers on tests/RedHerb/**.

🤖 Generated with Claude Code

malberts and others added 3 commits July 30, 2026 10:22
RedHerb is the live reference for extending NeoWiki, but nothing exercised
its frontend, so a change to NeoWiki's public API could rot the examples
unnoticed. Mount each of its Vue components against the real ext.neowiki
API and assert the behaviour an extension author copies them for.

The components are ResourceLoader package files: CommonJS closures that
pull vue, ext.neowiki and the CodexModule-generated codex.js and icons.json
in with require(). A test-only Vite plugin rewrites those two constructs to
ES modules and resolves the provided modules as ext.neowiki would, so the
examples share the vue and Codex instances the rest of the suite uses and
stay copyable into a real extension.

ci-ts also runs on tests/RedHerb changes now, so editing an example runs
the tests covering it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closing a dialog through Escape or its close button goes through a
different handler than its cancel action, and has to clear the shared
state the mount point holds rather than only hide the dialog.

Assert the optional state of the color field through CdxField rather
than the Codex class name that renders it, and stop pinning the whole
style attribute of a swatch when only the color it paints matters.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Building the transformed single-file component with String.replace gave
$& and its siblings their substitution meaning, so a script containing
one would have been rewritten into something that no longer parses.

Narrow the directory check to the contents of the source directory
rather than any path starting with its name.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@malberts

malberts commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

Harness approach: where this PR sits, and an axis the issue did not cover

Against the plan in the issue

The issue proposed two harness options: A, a vitest workspace with a second project rooted at the
extension root, and B, extending the existing project. A was preferred, for mirroring that RedHerb is
conceptually a separate extension.

This PR is closest to B, and further from A than B is: the specs live inside resources/ext.neowiki/tests/RedHerb/,
so the existing project discovers them by default, and the aliases and transform live in the existing
vitest.config.ts. It also went past the proposed smoke-test scope (mount without throwing, assert props passed to a
stubbed nw.SubjectEditor, one interaction) into behavioural tests against real collaborators, and it combines the
harness with the tests rather than landing the harness first.

The alias list and the reuse of the existing setupMwMock follow the issue as written. hexRegex.js has no spec of
its own here; it is covered only through the components that use it.

A correction to one premise

The issue notes that RedHerb's CommonJS Options API form is something "the vue plugin handles fine". It does not.
Loading a RedHerb component in vitest without a transform fails with Cannot find module 'vue', because require()
falls through to Node's resolver rather than Vite's, and RedHerb has no node_modules of its own. Separately, a
<script> block that assigns module.exports compiles to a component with no default export. Both had to be
rewritten, which is what resourceLoaderCommonJs.ts does. Any option on the vitest axis needs this.

The axis the issue did not cover

A and B are both node-side. The third option is running the tests through ResourceLoader itself, as a MediaWiki
QUnit test module served by Special:JavaScriptTest.

vitest (A or B) MediaWiki QUnit
Module loading Needs a CommonJS to ESM transform to stand in for ResourceLoader ResourceLoader is the loader, so no transform
ext.neowiki Aliased to the TypeScript source, or to the built bundle The real module the wiki serves
Assertion tooling @vue/test-utils Manual mount and DOM inspection
Infrastructure Already present, runs in about 80 seconds No browser test job exists in this repo today
Copyable with RedHerb No, needs this repo's node toolchain Yes, the test module travels in extension.json

The tension

Catching NeoWiki breaking its own examples requires running against NeoWiki's real API. Tests somebody can run after
copying RedHerb into their own extension must not depend on this repo. Those pull opposite ways: stubbing
ext.neowiki to make the specs portable would mean an API change no longer breaks them, which is the thing the issue
asks to catch.

Options that resolve it, rather than picking a side:

  1. Option A with ext.neowiki resolved to the built bundle rather than to src/public-api.ts. The transform
    ships inside RedHerb, so it becomes part of the example rather than hidden config, and a copier repoints one alias
    at their own NeoWiki install. Costs: RedHerb gains vitest, @vue/test-utils, vue and Codex as devDependencies
    plus a lockfile update, and the tests depend on a prior build. The bundle is not committed today.
  2. Keep the behavioural specs on the node side and add a small QUnit module to RedHerb as the copyable example.
    Costs a browser job in CI, which does not exist yet, though ci-php already stands up MediaWiki and Neo4j.

Worth noting that no node-side option can see the ResourceLoader wiring itself: a file missing from packageFiles, a
missing codexComponents entry, an unregistered message. A PHPUnit assertion that every relative require() in
RedHerb's resources appears in its packageFiles would cover most of that far more cheaply than a browser job.

No decision taken here. Happy to reshape this PR toward A, split the harness out first as originally proposed, or
narrow the tests back to the smoke-test scope.

AI-authored, Claude Code Opus 5 (1M context); written at @malberts' request to summarise a design discussion held in chat; not yet reviewed by a human. Repository claims above (absence of a browser test job, uncommitted bundle) were checked in the working tree; the loader failures were reproduced.

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.

Add tests for Vue components in RedHerb

1 participant