Test RedHerb's Vue components - #1218
Conversation
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>
Harness approach: where this PR sits, and an axis the issue did not coverAgainst the plan in the issueThe issue proposed two harness options: A, a vitest workspace with a second project rooted at the This PR is closest to B, and further from A than B is: the specs live inside The alias list and the reuse of the existing A correction to one premiseThe issue notes that RedHerb's CommonJS Options API form is something "the vue plugin handles fine". It does not. The axis the issue did not coverA and B are both node-side. The third option is running the tests through ResourceLoader itself, as a MediaWiki
The tensionCatching NeoWiki breaking its own examples requires running against NeoWiki's real API. Tests somebody can run after Options that resolve it, rather than picking a side:
Worth noting that no node-side option can see the ResourceLoader wiring itself: a file missing from No decision taken here. Happy to reshape this PR toward A, split the harness out first as originally proposed, or
|
For #1390 Both RedHerb editors read with `getSubject()` and save the result through `nw.useSubjectStore()`. That was right while `getSubject()` meant the current revision; now it means the published one, so on an approval wiki the editor opens on approved values and the save writes them over the pending draft. RedHerb is what an extension author copies, and `extending.md` said only to read through the repositories, so it now names which read. `rest-api.md` spells `latest=1`: the REST validator reads a valueless `latest` as false, so the bare parameter the row showed asks for the published revision. RedHerb's Vue components have no test coverage (#1218), so these two call sites are unpinned. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VmaTULCniBvK8S53astfRX
For #1390 Both RedHerb editors read with `getSubject()` and save the result through `nw.useSubjectStore()`. That was right while `getSubject()` meant the current revision; now it means the published one, so on an approval wiki the editor opens on approved values and the save writes them over the pending draft. Neither is gated on the page being at its current revision, so both reach that state. RedHerb is what an extension author copies, and `extending.md` said only to read through the repositories, so it now names which read. `rest-api.md` spells `latest=1`: the REST validator reads a valueless `latest` as false, so the bare parameter the row showed asks for the published revision. RedHerb's Vue components have no test coverage (#1218), so these two call sites are unpinned. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VmaTULCniBvK8S53astfRX
For #1390 Both RedHerb editors read with `getSubject()` and save the result through `nw.useSubjectStore()`. That was right while `getSubject()` meant the current revision; now it means the published one, so on an approval wiki the editor opens on approved values and the save writes them over the pending draft. Neither is gated on the page being at its current revision, so both reach that state. RedHerb is what an extension author copies, and `extending.md` said only to read through the repositories, so it now names which read. `rest-api.md` spells `latest=1`: the REST validator reads a valueless `latest` as false, so the bare parameter the row showed asks for the published revision. RedHerb's Vue components have no test coverage (#1218), so these two call sites are unpinned. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VmaTULCniBvK8S53astfRX
* Serve the latest read from the revision its gate cleared For #1390 `GET /subject/{id}?latest=1` gated the page's latest revision and then had `MediaWikiSubjectRepository` go find that page and revision again, and `expand=relations` reached every relation target's page at its own latest revision. So an approval wiki served drafts of pages the caller never named, and a save landing between the gate and the read was served unchecked. `LatestRevisionSubjectLookup` answers from the revision the handler holds, and asks `PublishedSubjectLookup` only for the ids that revision does not hold. Subjects on the page the gate cleared therefore come from it, including a relation target on that same page; the pages the caller said nothing about answer with what they publish. `PointInTimeSubjectLookup` composes the same way for the `revisionId` read. One lookup serves both the requested Subject and the ones `expand=relations` reaches, which also keeps a Subject that targets itself answering as itself: `GetSubjectQuery` places the requested Subject and then the relation loop keys the same id, so two lookups there would have answered for it from the wrong one. Two behaviour changes fall out. `latest=1` on a page whose subject slot holds another content model answers not-found rather than 500, matching the published read; the write path keeps refusing such a slot, since it would save over it. And a page whose latest revision has suppressed text answers not-found rather than 500. The 500 survives elsewhere on all three reads: the Main-Subject lookup behind the display-name fallback still reads through the write repository, which throws for both. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VmaTULCniBvK8S53astfRX * Seed the Subject editor from the revision its save will target For #1390 `getSubjectForEditing()` sent `latest=1` only when the repository had no revision to pin, and `NeoWikiExtension.getRevisionId()` pins one whenever `wgRevisionId` differs from `wgCurRevisionId`. A write targets the page's current revision whatever revision was read, so an editor seeded from a pinned one saves those values over whatever the page holds now. `NeoWikiApp` gates `canEditSubject` on that same comparison, so no View offers editing on a pinned view and the two branches never both applied there. A host that mounts an editor itself has no such gate: RedHerb's main-subject dialog is one, and the BlueSpice connector another. On an approval wiki a pinned view is the ordinary article view of a page with a pending draft, so there an ungated editor loaded approved values and wrote them over that draft. An editing read is now never pinned. A display read still follows the revision the page is rendered at, which is what the reader came for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VmaTULCniBvK8S53astfRX * Point the extension example at the editing read For #1390 Both RedHerb editors read with `getSubject()` and save the result through `nw.useSubjectStore()`. That was right while `getSubject()` meant the current revision; now it means the published one, so on an approval wiki the editor opens on approved values and the save writes them over the pending draft. Neither is gated on the page being at its current revision, so both reach that state. RedHerb is what an extension author copies, and `extending.md` said only to read through the repositories, so it now names which read. `rest-api.md` spells `latest=1`: the REST validator reads a valueless `latest` as false, so the bare parameter the row showed asks for the published revision. RedHerb's Vue components have no test coverage (#1218), so these two call sites are unpinned. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VmaTULCniBvK8S53astfRX --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.neowikiAPI, asserting the behaviour an extension author copies it for. The specs sit withNeoWiki's own frontend tests, in
resources/ext.neowiki/tests/RedHerb/, alongside where the PHP examplesare already covered from
tests/phpunit/RedHerb/. The registrationpackage 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.neowikiandthe CodexModule-generated
codex.jsandicons.jsonin withrequire(). Vite only understands ES modules,so
resources/ext.neowiki/tests/RedHerb/resourceLoaderCommonJs.tsrewritesrequire()andmodule.exportsfor files under RedHerb's resources directory, and resolves the provided modules asext.neowikiwould. That last partmatters: RedHerb has no
node_modulesof its own, and resolving from its own directory would either failor 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-tstriggered onresources/**only, so editing a RedHerb component ran neither the new tests nor theexisting ones. It now also triggers on
tests/RedHerb/**.🤖 Generated with Claude Code