Testing a cmssy app
The editor is the path your build cannot check. One smoke test covers it.
A site whose editor is dead still compiles, still serves, and still passes your unit tests. Nothing in a normal build touches the edit path, so nothing in a normal build tells you when it breaks.
checkCmssyEditMode is the check that does.
The smoke test
import { checkCmssyEditMode } from "@cmssy/next/testing";
const result = await checkCmssyEditMode({
baseUrl: "http://localhost:3000",
secret: process.env.CMSSY_DRAFT_SECRET!,
path: "/",
// Only if your URLs carry the language. The check reads <html lang> - a
// contract - rather than hunting for a word from your copy, which an editor
// can rewrite at any time.
localizedPath: "/pl",
});
expect(result.failures).toEqual([]);Run it against a started production build (next build && next start), not the dev server. Static rendering is the thing that made the edit route necessary in the first place, so the dev server tests the wrong app.
One more option exists for sites whose URL prefix is not the locale code. localizedLocale names the language localizedPath must render; it defaults to the first path segment, which is the language on a prefixed site. Set it explicitly when your prefix and your locale differ - /en-gb serving locale en, say - or the check compares against a language that does not exist and fails for the wrong reason.
What it asserts
- The public page returns 200 without the editor, with header and footer rendered server-side.
- A bare
?cmssyEdit=1with no secret does not enter edit mode. An unverified request must never open the door. - A verified
cmssyEdit=1pluscmssySecretrenders the editor and moves header and footer onto the edit bridge. - Optionally: the localized preview declares the language its URL asks for, via
<html lang>.
Why "no header in the SSR" means success
This is the assertion that reads backwards until you see why.
In edit mode the header and footer mount through the edit bridge, which renders on the client. So a header still present in the server-rendered HTML is a header the editor can select but cannot edit - the difference between an editable block and plain markup.
When it fails, the message names the cause rather than the symptom:
edit /shop: the header and footer are still server-rendered - they will be
selectable but have no fields (is CMSSY_EDIT_HEADER set on the rewrite?)In CI
- run: pnpm build
- run: |
pnpm start &
npx wait-on http://localhost:3000 --timeout 60000
- run: pnpm smoke:editSkip the job when the workspace secrets are absent. A green check that verifies nothing is worse than no check - it converts an unknown into a false assurance.
Wiring it as a script
The reference app keeps it as a standalone script rather than a test-runner case, so it can run in CI without a framework:
{
"scripts": {
"smoke:edit": "node smoke-edit.mjs"
}
}// smoke-edit.mjs
import { checkCmssyEditMode } from "@cmssy/next/testing";
const result = await checkCmssyEditMode({
baseUrl: "http://localhost:3000",
secret: process.env.CMSSY_DRAFT_SECRET,
localizedPath: "/pl",
});
console.log(result.ok ? "EDITOR OK" : "EDITOR BROKEN");
for (const f of result.failures) console.log(" -", f);
process.exit(result.ok ? 0 : 1);What else to test
Blocks are ordinary React components, so test them the ordinary way. Two cmssy-specific habits are worth keeping:
- Render each block with
data: undefined- that is what the editor passes, and a block that throws without its loader result is a block that breaks the editor. - Render with a missing locale - content may not be translated yet, and the fallback path is the one your visitors will hit first in a new language.
Next steps
- Server loaders - why
datais undefined in the editor. - Rendering - the routes the smoke test exercises.
- Page builder - what the editor does once it opens.