Testing Your Blocks
Your definitions and behaviors are code, so they deserve tests like any other code. Two checks cover most of it: do my definitions load, and does a program built from my blocks generate the code I expect. This guide sets both up with Vitest.
Morphic Blocks runs on Blockly, which needs a browser. In tests, jsdom stands in for one.
npm install --save-dev vitest jsdombun add -d vitest jsdompnpm add -D vitest jsdomTell Vitest to use jsdom and to load a small setup file:
import { defineConfig } from "vitest/config";
export default defineConfig({ test: { environment: "jsdom", setupFiles: ["./test/setup.ts"], },});jsdom does not lay anything out, so it cannot measure text. Blockly measures text while drawing blocks, so the setup file answers those calls with fixed sizes. Your tests check generated code, not how blocks look, so the sizes do not matter:
HTMLCanvasElement.prototype.getContext = (() => ({ font: "", measureText: (text: string) => ({ width: text.length * 8 }),})) as unknown as HTMLCanvasElement["getContext"];
Object.assign(SVGElement.prototype, { getBBox: () => ({ x: 0, y: 0, width: 0, height: 0 }), getComputedTextLength: () => 0,});Check that your definitions load
Section titled “Check that your definitions load”mount() validates your definitions and throws one error that lists every
problem it finds. A test that mounts an editor therefore catches broken
definitions before your users do:
import { expect, test } from "vitest";import { MorphicBlocks } from "morphic-blocks";import definitions from "../src/definitions.json";import { behaviors } from "../src/behaviors";
test("definitions load", () => { const engine = new MorphicBlocks(definitions, behaviors); expect(() => engine.mount({ workspaceContainer: document.createElement("div"), codespaceContainer: document.createElement("div"), }), ).not.toThrow();});Pass a codespaceContainer whenever one of your presets shows a codespace, just
as in your app.
Check what a program generates
Section titled “Check what a program generates”The easiest way to get a program into a test is to build it in your app and save it:
// in your app, e.g. from the browser consoleJSON.stringify(engine.serializeWorkspace());Store the result as a file, say test/programs/hello.json, and check what it
generates and prints:
import { expect, test } from "vitest";import { MorphicBlocks } from "morphic-blocks";import definitions from "../src/definitions.json";import { behaviors } from "../src/behaviors";import hello from "./programs/hello.json";
test("hello prints a greeting", () => { const engine = new MorphicBlocks(definitions, behaviors); engine.mount({ workspaceContainer: document.createElement("div"), codespaceContainer: document.createElement("div"), }); engine.loadWorkspace(hello);
expect(engine.generateJavaScript()).toContain("console.log"); expect(engine.runJavaScript().output).toEqual([{ level: "log", text: "Hello, world!" }]);});runJavaScript() collects every printed line with its level (see
Behaviors & Code Generation), so asserting
on output checks what the program actually does, not only the code it
produces.
- Keep one saved program per behavior you care about. When a behavior changes, the test tells you which programs now produce different code.
- A saved program carries only the blocks, not the modes, and the generated code does not depend on the mode either. One file is enough, whichever mode it was saved in. See Saving & Loading.
- Nothing is rendered on screen during tests, so they run in well under a second.