Tests
What devpage's test suite covers, module by module, how the tests are written, and what has no test. It is a reference page under Developing devpage, for someone about to change a module or wondering whether a behaviour is pinned.
$ cargo test # 222 tests in the four crates; needs no network and no Chrome
$ cargo test -- --ignored # the 53 tests that drive Chrome or call GPT-Live
How they are written
cargo test runs every crate in the workspace: 148 in devpage, 66 in the
engine devpage-design, 6 in devpage-design-wasm, whose tests run
natively, and 2 in devpage-design-module. Tests are written as
#[cfg(test)] modules beside the code they test — the canvas's Chrome tests
in src/designs/canvas_tests.rs, a child module of designs; the tests/
folder holds only fixtures — tests/fixtures/sync-design, a doc of three
chapters that the doc and PDF tests load, and tests/fixtures/narrated-talk,
a deck of three slides with notes that the live video test records. Route tests start the real router on
a free port on 127.0.0.1 and talk to it with reqwest; external-link tests
point the checker at a local web server, so the suite needs no network.
Of the 53 tests marked #[ignore], all drive Chrome, and one of them also
calls GPT-Live.
All but the two video tests drive chrome-headless-shell through
chromiumoxide — the copy
devpage pdf downloads
into devpage's cache folder, fetched by the first test that needs it — never
a Chrome installed on the machine, so a run puts nothing in the macOS Dock.
They run in parallel. Run them with --ignored after changing the page
script, the design script, the module, the stylesheet, a widget, print mode,
devpage pdf or a slide's PNG. The two video tests drive Chrome for
Testing instead — the copy devpage video downloads,
fetched the same way; run them after changing devpage video or its
recorder, assets/video.js. The live one records
tests/fixtures/narrated-talk with GPT-Live, which bills per minute, and
passes without doing anything unless DEVPAGE_OPENAI_API_KEY is set. The canvas tests and a slide's PNG start it
with --enable-unsafe-webgpu and --enable-gpu: the shell keeps its GPU process off without the second, and
then a screenshot holds black where a WebGPU canvas is; with it, WebGPU gets
the machine's real adapter and the screenshot is the canvas as a reader sees
it.
Tests that need files write them into folders under the system temp directory, named with the test process's id, and don't all remove them.
The watcher's tests need file-system events
Nine tests save a file and wait for devpage to hear of it through notify:
- six
watchtests —a_saved_page_refreshes_open_pages,a_comment_thread_always_reaches_its_page,the_watch_list_follows_the_entry_file,a_sites_watch_list_narrows_only_its_own_folder,the_entrys_watch_list_does_not_quiet_another_folderandwidget_files_refresh_whatever_watch_says(of the other two,ignored_files_and_files_outside_watch_do_not_refreshexpects no event anda_changed_file_reaches_the_design_storewaits for none, so both pass either way); server::the_entry_document_follows_its_file_without_a_restart;- two Chrome tests in
comments::browser—selected_text_becomes_a_thread_in_the_margin_and_in_the_storeanda_live_update_with_the_comments_shown_swaps_the_page_in_place.
Where the platform delivers no events to the test process — notify itself
receives nothing, as can happen in some sandboxed or restricted sessions on
macOS — those tests fail while everything else passes. That says nothing
about the change under test; run them where file events work before calling
the suite green.
By module
check
17 tests.
<Notes>on a page that isn't a slide, a second one on a slide, and one without its</Notes>, as problems — indevpage check, however a slide is reached, and in the preview.- A site's navbar reported once and its links followed.
- A missing logo reported once.
- Widget problems — an unknown widget tag, a project widget with a system widget's name, a widget or a helper that doesn't compile, a widgets folder's problems reported once however many pages.
- Missing files and headings and links climbing out of the served folder.
- A slide number the deck doesn't have.
- A heading in a doc checked against the ids the doc renders.
- Topics checked against the page's own site, and a page in no site checked as a doc.
- A broken linked document named by its address, not an absolute path.
- Each external address asked once and every link to it reported.
- An unreachable address asked again before
devpage checkreports it. - The preview's background check refreshing open pages.
- An unreachable address shown only after three failures in a row and cleared when it answers.
- The fixture doc, the devpage talk and devpage's own docs free of broken internal links.
comments
4 tests, 6 in Chrome.
- A thread's whole life leaving
git statusclean. - A thread file that doesn't parse named and left alone.
- The local author without a
.gitfolder. - Timestamps.
In Chrome:
- Commenting off until comment mode is on, a comment made, the switch remembered after a reload.
- Selected text becoming a thread in the margin and the store, then detaching.
- A paragraph comment on a nested list anchoring again after a reload.
- The comment icon and a wide table staying inside 390 px and 820 px windows.
- C on a deck.
- A live update with the comments shown swapping the page in place rather than reloading it.
designs
19 tests, 28 in Chrome.
- The store announcing only the designs made from a changed file, and resolving a design again when a missing import appears; a save landing while a design resolves never leaving a stale scene kept.
- An image that doesn't decode a problem at its
srcin whatdevpage checkreads and in the PNG. - A drawing too big to paint a problem in the store and in what
devpage checkreads; images too many for the GPU's atlas a problem at thesrcof the first that doesn't fit. - An image file decoded once per read of it: a save of the design showing it doesn't decode it again, a save of the file does.
- A real file named like a design's PNG served as it is; a link to a design's PNG checked as the design.
- No design file — a pasted zero-width space in a colour, deep nesting, a huge number, a truncated file — stopping the store, which still sees saves afterwards.
- Nothing outside the served folder read.
- A PNG, or
422and the problems, and404for a design outside the served folder or missing. - A design as the entry document; the canvas page titled without
.design. - The design service answering only
Watch. - The watch stream over gRPC-Web sending the scene, a patch of only what changed, nothing for a no-op, problems, and a deleted design coming back when written again.
- One stream over several designs sending each scene once, patches naming their design, going on after one is deleted, and bringing it back when it is written again.
devpage checknaming a broken embedded design'sfile:line:col.
In Chrome:
- Embeds inside widgets on a page in a subfolder — in a
Callout, aTab, aStepsitem and aCalloutinside aStepsitem — each finding its design beside the page. - The canvas on the GPU and on the CPU each matching the PNG by the engine's comparison, over the whole drawing and a margin of plain page around it.
- On the GPU, as in the PNG: a file wider than the GPU's atlas, twelve distinct 2000-pixel files together, and a save swapping six of them for new ones; shadows drawn in pieces — square, sampled and disc corners, a pill, a thin box, ellipses in wedges.
- A navigation from a design whose four 4,096-pixel images fill the GPU's atlas to one with an image of its own drawing the second as its PNG, on a canvas page and inside an embed.
- A click navigating, back returning, an overlay opening and closing; an interaction inside an instance from another folder opening its own target.
- The wheel zooming about the pointer and panning.
- An undone typo clearing the problems.
- A deleted design, on its canvas page and embedded, coming back when written again — after a page refetch too — without reopening its stream meanwhile.
- A canvas saying so when the engine doesn't load.
- An embedded design drawing at its own size and surviving a page edit; centred at its own size and only ever scaled down, to the page's width or the window's height.
- The backdrop's link opening the design it shows on its canvas page; a small design not enlarged on its canvas page.
- A doc printing its embedded design; a doc with eight embeds printing as fast as one with one.
- Eight embedded designs painting through one stream while the page still loads; leaving the last design in place closing the stream.
- An embed with problems surviving a page refetch.
- The canvas page's light/dark switch repainting what a design leaves transparent and remembered across a reload, and leaving the problems bar whole in light and dark; an embedded design painting again when the page switches theme.
- A design inside a project widget on a slide not yet shown drawing once shown, inside the slide, and printing promptly.
- An embed inside a widget keeping the design it navigated to, and its way back, across a page refetch.
- A design on a slide scaled down to a 1280 × 720 or 1600 × 900 window painted whole on the GPU and on the CPU, each corner its own color, and a click landing where the reader sees it.
document
9 tests.
- Each kind parsed.
- A deck's audience, goal, tone and pronounce, each slide's notes and delivery; those fields refused on the other kinds, and
contextrefused. - A navbar read in order with its icons and dropdowns,
sidebarandtheme-selector, and its bad shapes refused. - Bad kinds, fields and missing files rejected.
- A Markdown file as a one-chapter doc.
- The served folder as boundary and address root.
- A document outside it refused.
- A site's
watchlist. - Only YAML with a devpage
kindis a document file.
icons
1 test.
- Lucide icons and GitHub's mark drawn.
- Unknown names known as such.
index
4 tests.
- Document files found outside what
.gitignoreexcludes. - The nearest site above a page.
- Nested
.gitignorefiles and.gitfolders out of sight. - Changes keeping it current.
main
1 test.
- Taking the next port when the first is busy.
navbar
6 tests.
- Links, dropdowns, icons and icon-only links rendered in order.
- The link holding the page shown highlighted, a dropdown holding it too.
- Missing files and unknown icons as problems.
- No navbar rendering nothing, the comment-mode switch in the corner, and the theme selector off with
theme-selector: falsewhile the switch stays; with comments off, no switch. - A logo before the title and as the tab's icon, for a doc and a deck too.
- A missing logo as a problem and the page shown without it.
nav
3 tests.
- Links and
###groups read from the Topics section only. - The tree stopping at a cycle.
- A Topics entry outside the site or to a missing file is a problem.
pdf
2 tests, 16 in Chrome.
- The default file name made safe.
- A site refused.
In Chrome:
- The logo loaded at 22px before the navbar's title, centred on it, and the tab's icon.
- The navbar's dropdown by click and keyboard, its highlight following an in-place link.
- The theme selector following the system until clicked, then switching every token and remembering the theme across a reload.
- The navbar folding into a menu on a narrow screen.
- The fixture doc on A4 with its header and footer.
- The devpage talk one 16:9 page per slide.
- A deck's keys.
- A deck served with comments off showing no comment UI, even with comment mode remembered on, and C doing nothing.
- A doc's outline above the text on a narrow screen.
- Every system widget rendering, long lines scrolling, task lists without bullets and an inline widget keeping its paragraph.
- Widgets in a doc and a deck, a page loading only the widgets it uses.
- Widgets mounting again after a live update and an edited widget or helper reaching open pages.
- A comment anchored inside a widget.
- A printed doc showing each widget's first render, terminal lines wrapping on paper.
- A broken helper's message and an unknown tag's error shown in place, and a project
Callout.tsxleaving the systemCalloutrendering.
render
16 tests.
- An image of a design becoming its canvas, and a raw
<img>found whole with or without its closing slash, its attributes read however they are quoted, spaced or capitalised. - Headings never taking an id the frame uses.
- Every frame carrying the logo as its tab's icon, before the sidebar's and the outline's title.
- Raw HTML kept and Mermaid fences marked.
- Highlighting without a language label.
- Unknown languages left plain.
- Tables and heading ids.
- A doc's chapters with its outline.
- A deck's slides, never showing their notes.
- Links kept for chapters and slides from other folders.
- A one-file doc's title.
- A page's title.
- With comments off, every frame's page script carrying
data-no-commentsand no comment-mode switch; print mode staying print mode.
server
14 tests.
- A slide's PNG address answering
404with the reason for a slide the deck doesn't have, a number written otherwise than plainly, and a file that isn't a deck. - Widgets served compiled, for the document they belong to, with versioned helper imports.
- A page naming its widgets and carrying their placeholders, code left alone.
- A site's page printing as a doc of one chapter.
/and the site's pages following the entry file's changes without a restart, through an invalid state and back.- Comment threads over HTTP.
- Deployed writes needing the header and stamped with it, over HTTP and in the identity rule alone.
- A thread on a page outside the served folder refused.
- With comments off, every comment request answering
404, no.devpage/created, and the page carrying the setting. - Serving a folder with no
.gitby what each file is. - Nothing outside the served folder served.
- A served folder reached through a symbolic link.
- Each site's title linking to its own root page.
slide_png
1 test in Chrome.
- A slide served as a 1920 × 1080 PNG, each slide a different picture, and
404for a slide the deck doesn't have.
video
19 tests, 2 in Chrome.
- The silence rules: a pause inside a slide capped at a second and one under it kept whole; silence held until what follows is known; a slide's end keeping its share of the gap and the next slide starting on speech; leading silence dropped; whatever arrived before a slide was asked for left out; a fade at every cut.
- The narrator's settings: none set naming the variables, blank counting as
unset and
OPENAI_API_KEYnever read; OpenAI's address and bearer header with the default model; the Azure endpoint selecting Azure over an OpenAI key, with its address,api-keyheader and deployment name; an endpoint with a path or as a bare host; Azure without its key. - What the narrator is told: the audience, goal and default tone in its instructions and each slide's request with its delivery; the making-a-video page showing the template and request exactly as the code sends them.
- The default file name; every slide needing notes, the error naming the ones
without; a deck without
audienceorgoalrefused, naming them; a site and a doc refused.
In Chrome:
- A two-slide deck recorded in Chrome for Testing with a synthetic voice fed through the same player — no GPT-Live — to an mp4 with a 1920 × 1080 video track and a sound track, the deck moving to slide 2 on the way.
tests/fixtures/narrated-talkrecorded end to end with GPT-Live on OpenAI, whenDEVPAGE_OPENAI_API_KEYis set. The Azure path has no live test.
watch
8 tests.
- A saved page refreshing open pages.
- Ignored files and files outside
watchnot refreshing. - A comment thread always reaching its page.
- The
watchlist following the entry file. - A site's
watchnarrowing only its own folder, whichever document devpage was started with. - Widget files refreshing whatever
watchsays. - A changed file reaching the design store, and none under
.devpage/, handed over directly, without waiting on file-system events.
widgets
25 tests.
<Notes>taken out of the page with its text kept, each one found with its line, none found in code or comments, and a projectNotes.tsxa clash; an unclosed one a problem, running to the end of the page so none of it is shown; itsdeliverykept and never shown; a<Notes>whose attributes can't be read, or whose delivery isn't a quoted string, a problem and never shown.- Widget tags expanded with Markdown content, expression, bare and self-closing props, code left alone — fenced, indented and inline code and HTML comments, but not indented list content.
- An unknown tag replaced by an error, not passed through.
- A
Taboutside aTabsreported at its page line. - Nested and inline widgets, an inline widget at the start of a line staying in its paragraph.
Example's highlighted source.- TSX compiled with the automatic runtime, widget shims,
__devpageUsesand versioned relative imports. - A compile error's line.
- Compiles remembered until the source or version changes.
- A folder's version moving when a helper changes.
- Lowercase files as helpers and a system widget's name as a problem.
- A
Cardsicon drawn on the server and an unknown icon name as a problem.
The design engine's crates
devpage-design
66 tests.
parse, 8: imports, attributes and text with props; JSX's strings without escapes, bare attributes and names with-; nesting and numbers kept within their limits, arrays and objects counting as levels and numbers too small named so; JSX whitespace in text; every syntax error, and each problem, at its line and column.resolve, 24: props and defaults; an import name unique and never a built-in element's; sizes and spacing never negative;justifynaming all it takes;xandyin a frame with auto layout, a negative size, an attribute given twice and an import of something other than a design each a problem;propsonly on the root, and noxoryon it;"fill"only under auto layout; a colour with anything but hex digits in it a problem, not a panic; a tree nested too deep through instances; an instance taking its props and the attributes every element takes; values parsed into their types; opacity kept between 0 and 1; every problem reported at once and each at its place; a missing import a problem at its line, used or not; a design that uses itself; paths kept inside the served folder; an image that doesn't decode — a broken PNG, a truncated JPEG — a problem at itssrc, and a good one's pixels going with the scene; a file larger than 4,096 a side resampled once to fit, by area, a transparent pixel lending no color;cliponly on a frame; numbers inside a gradient, aviewBoxand adheld to the same limits as any other; an interaction relative to the file that wrote it, one to nowhere a problem, and one only ever to a design; a valid design having none.render, 30: auto layout, hug, fill, a root that fills on its own hugging, a drawing laid out once, align and justify, and absolute placement; a hugging frame with no layout taking its children's extent, nested ones too; the drawing being the root plus what is painted outside it, cut by clipping, and a drawing too big being a problem; no input — hostile values for every attribute of every element, a file cut at every character — making the engine panic; text hugging and centered; fills, gradients along their angle, strokes inside the box, per-corner radius, a shadow following each corner's radius and the spread, sharp and blurred, and its edge the Gaussian at every box size from 100 to 16,000 — fading out with no step, rising 10–90% over 2.563 σ; clip and opacity as one group; vectors scaled into and hugging their view box; images covering their box, PNG and JPEG decoding; images too many for the GPU's atlas a problem at thesrcof the first that doesn't fit, one file shown again taking no more room; a drawing painting the same images as the last painting the last one's blobs, and one whose files, order or shadows differ, or made after none, fresh ones; the example designs painting as their reference PNGs; the comparison forgiving anti-aliased edges only, and catching a missing divider or icon on the checkout design.wire, 2: a patch carrying only the nodes that changed; an image file sent once however many nodes show it, a patch carrying only the files the browser lacks, and one whose nodes show a file never sent refused.atlas, 1: four 4,096-pixel squares filling the atlas at their places, a fifth not fitting.shadow, 1: a shadow in pieces matching it as one image, with no seam and within the bound for how wide its blur is on the screen, at fractional scales, for every kind of piece, a box thinner than its blur, and a dozen random outlines.
devpage-design-wasm
6 tests.
- A scene sized, painted and hit-tested.
- The engine's atlas model and vello's own resolver, as the module ships it, agreeing on 4,000 random sets of images: whether they fit, the first left out, and each one's place.
- The two agreeing on a real drawing's image files and shadows, replayed as the canvas replays it, and on the same drawing with too many files.
- A patch before any scene, or garbage, doing nothing.
- Problems keeping the scene until a patch clears them.
- A click landing only on what a clip shows.
devpage-design-module
2 tests.
- The vendored module built from the sources as they are.
- The fingerprint changing with an engine source, the
wasmprofile and a locked version in the module's tree, but not with a package outside it.
What has no test
syntax highlighting except through the render tests; a canvas reopening
its stream after devpage restarts, closing streams in a hidden tab, and
drag-to-pan and pinch on the canvas; chrome-headless-shell's and Chrome for
Testing's first download (it needs an empty cache folder); GPT-Live on Azure;
devpage video stopping on a signal; fullscreen on a deck (headless Chrome refuses it without a real user
gesture); and how the light and dark themes look, which is checked by eye.