Agent skill

Writing Unit Tests

by TriliumNext in TriliumNext/Trilium

A skill your agent uses when writing, extending, or debugging Vitest unit tests anywhere in the Trilium monorepo — Preact components, jQuery widgets, client services, or the server/trilium-core…

AGPL-3.0Auto-check passedTesting & QA

Install Writing Unit Tests

skills CLI
$ npx skills add TriliumNext/Trilium --skill writing-unit-tests -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install TriliumNext/Trilium writing-unit-tests --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/TriliumNext/Trilium.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/writing-unit-tests .claude/skills/writing-unit-tests && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
writing-unit-tests
GitHub stars
38k
Token cost
~3.2k tokens
SKILL.md length
1,461 words
Files
4
Skills in repo
22
Repo updated
First seen
Licence
AGPL-3.0

At a glance

A skill your agent uses when writing, extending, or debugging Vitest unit tests anywhere in the Trilium monorepo — Preact components, jQuery widgets, client services, or the server/trilium-core…

  • Debugging Vitest unit tests anywhere in the Trilium monorepo — Preact components
  • SKILL.md covers First principle: prefer…, Which technique? (decision tree), Running tests and Coverage config rules (Vitest 4), plus 1 more section
  • Calls pnpm, tsc and npx
  • Client services

What it does

Writing Unit Tests is an agent skill from TriliumNext/Trilium. Use when writing, extending, or debugging Vitest unit tests anywhere in the Trilium monorepo — Preact components, jQuery widgets, client services, or the server/trilium-core backend. Covers how to render components (zero new deps), the easy-froca/becca fixtures, supertest API patterns, the honest coverage config, running a single test, and the known gotchas.

Its SKILL.md is about 3.2k tokens, which your agent loads only when the skill is triggered. The skill folder holds 3 other files (for example `client-components.md`, `client-logic-and-services.md` and `server-and-core.md`).

It sits in Testing & QA, covering Unit testing. It works with Vitest and Preact. The repository describes itself as: Build your personal knowledge base with Trilium Notes. The licence is AGPL-3.0.

When your agent uses it

  • Debugging Vitest unit tests anywhere in the Trilium monorepo — Preact components
  • Client services
  • The server/trilium-core backend

Example prompts

  • “/writing-unit-tests”

Requirements

  • Node.js

What it can do on your machine

Read from SKILL.md and the folder at commit 96bed2f. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • pnpm
    • tsc
    • npx
    • vitest
    • node

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md. Its commands use pnpm and npx, which can reach the network depending on how they are called.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Writing Unit Tests loads about 3.2k tokens when it runs. Until then it costs about 95 tokens; SKILL.md has 1,461 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~95
When it runs · the whole SKILL.md, loaded when a task matches
~3.2k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from TriliumNext/Trilium at commit 96bed2f, republished under its AGPL-3.0 licence (© TriliumNext). 1,461 words, ~3,158 tokens.

Download SKILL.mdSave it as .claude/skills/writing-unit-tests/SKILL.md (or your agent's skills folder). This skill also uses 3 other files; get the full folder from GitHub.
name
writing-unit-tests
description
Use when writing, extending, or debugging Vitest unit tests anywhere in the Trilium monorepo — Preact components, jQuery widgets, client services, or the server/trilium-core backend. Covers how to render components (zero new deps), the easy-froca/becca fixtures, supertest API patterns, the honest coverage config, running a single test, and the known gotchas.

Writing unit tests in Trilium

Trilium is a pnpm monorepo tested with Vitest (v8 coverage). This skill captures the patterns that actually work here, plus the footguns that waste time. Read the per-layer reference file for the area you're touching.

First principle: prefer extracting pure logic

The dominant, lowest-risk pattern across this repo is extract the decision/transform logic out of a component/widget/route into a top-level export function that takes plain inputs and returns a plain value, then test that function. Rendering and side effects stay thin; the logic gets covered cheaply. apps/client/src/widgets/ribbon/FormattingToolbar.tsx (getFormattingToolbarState, tested in FormattingToolbar.spec.ts) is the canonical example. Reach for rendering/integration only when the behavior is the DOM/HTTP.

Also follow CLAUDE.md: write concise tests (group related assertions in one it, don't make one test per trivial passthrough), and when you add pure business logic, extract + unit-test it.

Which technique? (decision tree)

You're testing…TechniqueReference
A reusable Preact component (apps/client/src/widgets/react/)Render with raw preact render() into a happy-dom divclient-components.md
A jQuery widget / type widgetExtract logic → test fn; or instantiate + assert on $widgetclient-logic-and-services.md
A client service (apps/client/src/services/)easy-froca + override server.*; or pure logicclient-logic-and-services.md
A server service (apps/server/src or packages/trilium-core/src)Real in-memory DB (sql_init + cls.init) or mocked beccaserver-and-core.md
A shared core API route (packages/trilium-core/src/routes/api/*)CoreApiTester — in-process, cross-runtime, real services (incl. zip export/import/multipart), minimal mocksserver-and-core.md Pattern 0
An internal REST API route's Express transport (CSRF/auth/wiring)supertest agent + /login + /bootstrap CSRFserver-and-core.md Pattern 1
An ETAPI endpointsupertest + basic-auth via spec/etapi/utils.tsserver-and-core.md
Pure logic (parsers, formatters, math, data maps)Plain Vitest, no harnessany reference
Specialized harnesses (owned by sibling skills)

Some layers have a purpose-built spec harness documented in the skill that owns the feature — route there instead of re-deriving it:

You're testing…Harness shapeSkill
A DB migration (packages/trilium-core/src/migrations/*)getSql() captured in beforeEach (not at describe time — core isn't initialized yet) + sql.rebuildFromBuffer(fixtureDb) per test, mutated inside cls.getContext().initevolving-the-data-model
An Electron preload bridge method (apps/desktop/src/preload.ts)vi.mock("electron", …) mirroring the IPC channels into in-memory maps; assert the exposed window.electronApi shape (apps/desktop/src/preload.spec.ts)developing-electron-desktop
A CKEditor 5 plugin in Trilium's own bundle (packages/ckeditor5)Browser-mode headless Chrome: ClassicEditor.create + licenseKey: "GPL" + _setModelData, co-located src/**/*.spec.tsckeditor5-testing

Running tests

Run the narrowest thing that covers the change, and never the full suite. pnpm test:all, test:parallel, test:sequential and pnpm coverage take minutes and are CI's job — reach for one only if the user asks. Never run ESLint either (pnpm dev:linter-check/-fix, npx eslint): it currently dies out-of-memory, so a run costs minutes and tells you nothing. Typecheck with pnpm typecheck (never a raw tsc -p/tsc -b, which misses the project references) — cheaper than a suite, but not instant, so run it once a change is finished rather than after every edit.

  • Filtered (preferred): pnpm --filter <pkg> test <path-or-pattern> — the trailing argument is a substring filter over spec paths, so pnpm --filter server test special_notes runs every match.
  • Whole package: pnpm --filter <pkg> test (e.g. @triliumnext/client, @triliumnext/server, @triliumnext/commons).
  • Single file (server): pnpm --filter server test spec/etapi/search.spec.ts
  • Single file (client): pnpm --filter @triliumnext/client exec vitest run src/widgets/react/Button.spec.tsx
  • Coverage: append --coverage.
  • Server tests run sequentially (shared DB, pool: "forks", fork isolation is per file). Client/package tests run in parallel.

Windows/sandbox note: any pnpm --filter … invocation — exec vitest and the package's own test script alike — can trigger a pnpm auto-install that hits EPERM/Access is denied on a node_modules directory VS Code holds open (it rolls back, but the run is lost). If so, run the hoisted binary directly (it lives in the repo-root node_modules): CI=true node node_modules/vitest/vitest.mjs run <spec> --root apps/client, or node_modules/.bin/vitest.CMD run <spec> --root apps/<app>.

Coverage config rules (Vitest 4)

Each project's test config (vite.config.* / vitest.config.*) measures coverage honestly via:

ts
coverage: {
    provider: "v8" as const,
    include: ["src/**/*.{ts,tsx}"],            // makes UNTESTED files count too
    exclude: ["**/*.{test,spec}.{ts,mts,cts,tsx,js,jsx}", "**/*.d.ts"],
    reporter: ["text", "lcov"]
}
  • Do NOT use all: true — it was removed in Vitest 4 and is a type error; include already pulls in untested files.
  • If a config sets Vite root: "src" (e.g. apps/standalone), coverage include globs resolve relative to src, so use ["**/*.{ts,tsx}"], not ["src/**/…"].
  • Files outside the project root need coverage.allowExternal: true. v8 defaults it to false, which silently drops every out-of-root file — so an include glob alone (e.g. ../../packages/trilium-core/src/**) is ignored and contributes nothing. trilium-core has no runner of its own; its coverage is measured through apps/server and apps/standalone, and both must set allowExternal: true plus a core glob in coverage.include whose ../ depth matches that suite's root: ../../packages/trilium-core/src/** for server (root apps/server), ../../../packages/trilium-core/src/** for standalone (root apps/standalone/src). Without allowExternal core never reaches the lcov or Codecov. The lcov writes these as ../…/packages/… paths; codecov.yml's fixes: entries strip the ../ so they map onto the repo tree.
  • For provably-unreachable defensive branches, mark them with /* v8 ignore next */ / /* v8 ignore start */…/* v8 ignore stop */ and a one-line reason — don't delete the guard or write a fake test. /* v8 ignore next */ does not reliably suppress a branch sitting on a } else if (…) { line — use the start/stop form there. Better still, check whether the arm is dead because the code can be simplified: a trailing else if (name) { … } return [] where name is provably truthy collapses to a direct return, removing the branch honestly instead of hiding it. Project style is a plain /* v8 ignore next -- reason */ — no @preserve.
  • Checking one file's coverage: the v8 text reporter crashes (PARSE_ERROR while remapping unrelated uncovered core files) on single-spec --coverage runs. Produce lcov/json/json-summary instead and parse it with the analyzing-coverage skill's coverage.mjs (… summary for pct/aggregate, … gaps --filter <file> for the uncovered line list). The full-suite text report (run over a directory) is fine. Don't hand-roll a coverage parser — that script already handles all three formats and the Windows footguns.
Show full SKILL.md (547 more words)Show less

Universal gotchas

  • A green Vitest run is not proof the spec compiles. Vitest strips types with esbuild, so specs are never type-checked by the run itself — and apps/client/tsconfig.app.json deliberately excludes src/**/*.spec.ts(x). They are checked by tsconfig.spec.json, which pnpm typecheck reaches through the project references, so pnpm typecheck is the only thing that catches a broken spec — but run it once, after the whole batch of specs is written, never per file or per edit. It walks the project references and costs real time; spending that repeatedly is the single easiest way to make a test-writing session slow. Passing tests routinely hide mechanical type errors that then fail CI. The recurring ones: act(() => someExpression) returning a value where a void callback is expected, Component | undefined passed where the context wants | null, and mock-froca helpers whose default getNote = vi.fn(async () => undefined) infers Promise<undefined> and rejects a caller's vi.fn(async () => someNote) — type such params loosely ((...args: unknown[]) => Promise<unknown>).

  • No non-null assertions (!) — never use the TypeScript postfix ! operator, even in tests. Narrow instead: becca.getNoteOrThrow(id)/getAttachmentOrThrow(id) instead of becca.getNote(id)!; value?.prop ?? fallback then assert; or capture into a const after an expect(x).toBeDefined()/null check. (Project rule — see CLAUDE.md Code Style.)

  • vi.mock is hoisted above imports. Put component/module imports after the vi.mock(...) calls; mock factories can't reference outer non-hoisted variables. Partial-mock with async (importOriginal) => ({ ...(await importOriginal()), onlyThis: vi.fn() }).

  • vi.resetModules() drops importOriginal-based partial mocks. The next dynamic import of the module gets the real dependency back, not your stub, and the symptom reads as "the mock isn't applying" rather than as a reset problem. Prefer registering singletons and IPC handlers once and ordering the tests instead — put the one that latches module-level state last. Where a reset is genuinely needed (a value cached on first call), mock with a full factory rather than an importOriginal partial.

  • Don't assert on translated (i18n) strings — assert structure/keys/behavior (classes, counts, ids), not human-readable English.

  • happy-dom is not a browser: getBoundingClientRect() returns zeros, ResizeObserver/layout/visibility are stubs. Anything pixel/size/scroll-based needs @vitest/browser, not happy-dom.

  • @vitest/browser real-browser mode IS configured — the packages/ckeditor5, -mermaid and -math bundles run their co-located src/**/*.spec.ts in headless Chromium (@vitest/browser-playwright; see packages/ckeditor5/vitest.config.ts). These are the browser-mode test:sequential suites. Reserve real-browser mode for genuine layout/integration needs (CKEditor, Excalidraw, Modal transitions, size measurement); normal unit tests stay on happy-dom.

  • WASM scrypt is ~10× slower under the standalone suite (pure-JS scrypt-js under V8 coverage instrumentation, vs Node's native scryptSync) — enough to blow the 5s default. A core spec that hashes a password bumps the timeout for the standalone runtime only; copy the guard from packages/trilium-core/src/routes/api/login.spec.ts:13:

    ts
    const isBrowserRuntime = typeof window !== "undefined";
    if (isBrowserRuntime) {
        vi.setConfig({ testTimeout: 60000, hookTimeout: 60000 });
    }
  • A spec that hangs at exit takes the whole suite down with it. An unclosed timer, ResizeObserver, event listener or in-flight fetch can let every test pass and then stop Vitest from exiting — which hangs the full pnpm test run, and CI with it. Because the failure is at teardown, the spec looks green in isolation. Diagnose by sweeping the specs one at a time under a hard timeout and looking for exit code 124:

    bash
    find <dir> -name '*.spec.tsx' | xargs -P6 -I{} timeout 70 <vitest> run {}

    Use absolute paths in that command — a background shell starts at the repo root, not wherever you last cd'd. Clean up the handle in the spec rather than raising the timeout.

© TriliumNext, AGPL-3.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 3 other files in .claude/skills/writing-unit-tests of TriliumNext/Trilium.

  • SKILL.md
  • client-components.md
  • client-logic-and-services.md
  • server-and-core.md

Open the folder on GitHubat commit 96bed2f

Compare with similar skills

Writing Unit Tests next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Writing Unit Tests compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Writing Unit Tests this skillTriliumNext/Trilium38k—~3.2kAutomated safety check: PassAGPL-3.0
Test Writing WorkflowiOfficeAI/AionUi33k1 repos~1.2kAutomated safety check: PassApache-2.0
Vitestsupabase/supabase111k12 repos~1.1kAutomated safety check: PassApache-2.0
Test GuardamElnagdy/guard-skills1.3k2 repos~2.1kAutomated safety check: PassMIT
Concept Page Test Writerleonardomso/33-js-concepts67k—~5.5kAutomated safety check: PassMIT
Archestra Dev Testingarchestra-ai/archestra4.4k—~3.2kAutomated safety check: PassCustom licence

Similar skills

  • Test Writing Workflow

    iOfficeAI/AionUi

    Sets the test-writing workflow for the repository: risk-first scenario lists, behavior-focused Vitest tests, a full run before each commit and a coverage target.

    33k GitHub starsUsed in 1 repo~1.2k tokens
    Testing & QAAuto-check passed
  • Vitest

    supabase/supabase

    Official

    Vitest API and config reference (Jest-compatible) — mocking with vi., spies, fake timers, coverage configuration, fixtures, snapshots, and test filtering.

    111k GitHub starsUsed in 12 repos~1.1k tokens
    Testing & QAAuto-check passed
  • Test Guard

    amElnagdy/guard-skills

    Reviews newly written or edited tests against nine rules that cut test bloat, such as mock-heavy checks and near-duplicate cases, before they are committed.

    1.3k GitHub starsUsed in 2 repos~2.1k tokens
    Testing & QAAuto-check passed
  • Concept Page Test Writer

    leonardomso/33-js-concepts

    Generates Vitest tests for every runnable code example on a JavaScript concept documentation page, following a four-phase extraction and conversion process.

    67k GitHub stars~5.5k tokensUpdated 28 days ago
    Testing & QAAuto-check passed
  • Archestra Dev Testing

    archestra-ai/archestra

    A skill your agent uses for test selection and quality across backend, frontend, and e2e; load its backend reference for Vitest projects, mocking, DB fixtures, and performance.

    4.4k GitHub stars~3.2k tokensUpdated today
    Testing & QAAuto-check passed
  • Control UI E2E

    openclaw/openclaw

    A skill your agent uses when designing, testing, fixing, or extending the OpenClaw Control UI GUI, including UI stress-test galleries with feedback inputs, Vitest + Playwright end-to-end checks…

    392k GitHub stars~2.9k tokensUpdated today
    Testing & QAAuto-check passed

More from TriliumNext/Trilium

All 22 skills in this repo
  • Cutting A Release

    TriliumNext/Trilium

    A skill your agent uses when cutting, preparing, or debugging a Trilium release — bumping the monorepo version, tagging, or diagnosing a failed "Release" workflow run.

    38k GitHub stars~3.2k tokensUpdated today
    Auto-check passed
  • Developing Electron Desktop

    TriliumNext/Trilium

    A skill your agent uses when working on the Trilium Electron desktop app (apps/desktop) — adding or changing an electronApi method / IPC channel, touching preload.ts, main.ts, services/window.ts or…

    38k GitHub stars~5.7k tokensUpdated today
    Auto-check passed
  • Evolving The Data Model

    TriliumNext/Trilium

    A skill your agent uses when adding a DB migration or a new column/field to a Becca entity in Trilium ("add a migration", "new column on notes/attributes", "ALTER TABLE", "add a field to…

    38k GitHub stars~2.1k tokensUpdated today
    Auto-check passed
  • Adding Internal API Route

    TriliumNext/Trilium

    A skill your agent uses when adding, moving, or wiring an internal REST endpoint in Trilium (a new /api/ route) — choosing between a core-shared handler (packages/trilium-core/src/routes/index.ts…

    38k GitHub stars~3.3k tokensUpdated today
    Auto-check passed
  • Adding LLM MCP Tools

    TriliumNext/Trilium

    A skill your agent uses when adding, changing, or reviewing an LLM/MCP tool in Trilium (the defineTools definitions under packages/trilium-core/src/services/llm/tools/ —…

    38k GitHub stars~2.5k tokensUpdated today
    Auto-check passed
  • Ckeditor5 Plugin Development

    TriliumNext/Trilium

    Write, extend, and review CKEditor 5 plugins in the Trilium (TriliumNext Notes) monorepo — the rich-text-note editor under packages/ckeditor5, whose plugins live in src/plugins/.

    38k GitHub stars~4.9k tokensUpdated today
    Auto-check passed

Works with

Categories

Questions about Writing Unit Tests

What does Writing Unit Tests do?

A skill your agent uses when writing, extending, or debugging Vitest unit tests anywhere in the Trilium monorepo — Preact components, jQuery widgets, client services, or the server/trilium-core…. Writing Unit Tests is an agent skill from TriliumNext/Trilium. Use when writing, extending, or debugging Vitest unit tests anywhere in the Trilium monorepo — Preact components, jQuery widgets, client services, or the server/trilium-core backend.

When should I use Writing Unit Tests?

Writing Unit Tests fits situations like: debugging Vitest unit tests anywhere in the Trilium monorepo — Preact components; client services; the server/trilium-core backend.

How do I install Writing Unit Tests in Claude Code?

Run `npx skills add TriliumNext/Trilium --skill writing-unit-tests -a claude-code`. Or copy the skill folder (.claude/skills/writing-unit-tests in TriliumNext/Trilium) into .claude/skills/writing-unit-tests in your project. Claude Code loads it when a task matches its description.

How do I install Writing Unit Tests in Codex?

Run `npx skills add TriliumNext/Trilium --skill writing-unit-tests -a codex`. Or copy the skill folder (.claude/skills/writing-unit-tests in TriliumNext/Trilium) into .agents/skills/writing-unit-tests in your project. Codex loads it when a task matches its description.

Can I use Writing Unit Tests in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add TriliumNext/Trilium --skill writing-unit-tests -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/writing-unit-tests, .gemini/skills/writing-unit-tests, .github/skills/writing-unit-tests and .opencode/skills/writing-unit-tests in your project.

What does Writing Unit Tests need to run?

Going by SKILL.md and its folder, Writing Unit Tests needs the command-line tools its instructions call (pnpm, tsc, npx, vitest and node). Our summary lists: Node.js.

Does Writing Unit Tests access the network?

SKILL.md contains no URLs. Its commands use npx, which can reach the network depending on how they are called. This is read from the text; nothing was executed.

Is Writing Unit Tests safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Writing Unit Tests use?

Writing Unit Tests is published under the AGPL-3.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Writing Unit Tests use?

About 3.2k tokens (SKILL.md is roughly 13k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Writing Unit Tests?

Skills that share tags, products or a category with Writing Unit Tests: Test Writing Workflow (iOfficeAI/AionUi, 33k stars), Vitest (supabase/supabase, 111k stars), Test Guard (amElnagdy/guard-skills, 1.3k stars) and Concept Page Test Writer (leonardomso/33-js-concepts, 67k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Writing Unit Tests?

TriliumNext (a GitHub organization) maintains it in TriliumNext/Trilium, which has 38,256 GitHub stars. The repository holds 22 skills in this directory. The repository was last updated on October 9, 2026.

Source: TriliumNext/Trilium on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.