npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@notegen/plugin-test

v0.1.11

Published

In-process test host for NoteGen plugins.

Readme

@notegen/plugin-test

An in-process, deterministic test host for NoteGen plugins.

pnpm add -D @notegen/plugin-test @notegen/plugin-api
import { createPluginTestHost } from '@notegen/plugin-test'
import manifest from './plugin.json' with { type: 'json' }
import * as plugin from './src/index.js'

const host = createPluginTestHost({ manifest })

await host.activate(plugin)
await host.executeCommand('com.example.hello.open')

console.log(host.notices)
console.log(host.callHistory)

await host.deactivate()

The host implements the public PluginContext contract and provides:

  • activation, deactivation, command registration, and command execution;
  • manifest-aware permission grants and deterministic permission denial;
  • in-memory notes with production path, content-derived revision, 2 MiB content, and idempotency-key limits;
  • device/workspace storage with the combined 256-key and 1 MiB production quotas;
  • manifest-backed settings;
  • active-editor and content-change event controls;
  • note listing/writing/moving/deletion and change-event delivery;
  • revision-checked editor edits;
  • production-shaped validation for declarative view/dialog snapshots and injectable restricted-network responses;
  • notice, status-bar, and API call-history snapshots.

View snapshots are defensive deep copies and are recursively frozen, so test assertions cannot mutate the host's stored declarative UI state.

Writing an existing note (including with create: true) or deleting a note requires its last-read expectedRevision. Missing or stale revisions reject the mutation with StaleRevision. This host only deletes in-memory fixtures: it does not test OS trash, filesystem races, or mobile deletion support. The desktop app moves deleted notes to system trash; mobile deletion currently returns UnavailableOnPlatform.

Deactivation rejects pending activation, command execution, and injected network requests with Cancelled, even if their handlers have not finished. The underlying Node.js callback is not forcibly stopped; plugin code should observe ctx.signal before doing its own asynchronous side effects. Network permission is checked again before returning an injected response, so a grant revoked while the request is pending cannot deliver a successful response.

Notices are truncated to 500 UTF-16 code units. Status text fields are truncated to 160 code units. Repeated updates to one item within 100 ms are trailing-coalesced, so the final state is applied when the interval ends, like the production host. Supply a controllable now function when a test needs to advance that interval deterministically.

Permissions declared by the manifest are granted by default. Override a grant at construction time, or change it during a test:

const host = createPluginTestHost({
  manifest,
  permissions: { 'notes.read': false },
})

host.setPermission('notes.read', false)

A boolean true is an unrestricted test grant, retained for concise and backwards-compatible tests. Use a scoped grant when the test needs to reproduce the production host's workspace path checks:

const host = createPluginTestHost({
  manifest,
  permissions: {
    'notes.read': {
      granted: true,
      paths: ['Templates/daily.md'],
    },
    'notes.create': {
      granted: true,
      paths: ['Daily'],
    },
  },
})

host.setPermission('notes.read', {
  granted: true,
  paths: ['Templates/weekly.md'],
})

workspace-file and workspace-files grants match the listed files exactly. workspace-folder grants include the listed folders and their descendants; an empty string grants the whole workspace for that scope. A scoped grant with an empty paths array grants no path access. network-origins grants match exact origins such as https://api.example.com. The test host rejects credentials, fragments, IP literals, localhost names, forbidden request headers, and oversized bodies. It cannot reproduce the native host's DNS pinning check, so real-host testing is still required for hostnames that resolve to private or otherwise non-public addresses.

notes.list() is non-recursive by default and returns at most 200 entries, as the production host does. Seed empty folders with the folders option when a test needs to distinguish an existing empty directory from a missing one.

Seed openNotePaths with notes open in any tab, pane, or separate editor window to exercise the production EditorBusy check for notes.write, notes.move, and notes.delete. Call host.setOpenNotePaths(paths) when the simulated UI opens or closes notes. openOrCreate({ open: true, ... }) also marks its note as open. Use surface: 'editor-window' to model a separate window, where file mutations and opening another note are unavailable but the editor API remains available. The test host does not simulate real save queues or window races.

Commands and status-bar items must be declared in manifest.contributes. Settings begin with their manifest defaults and may be overridden through settings. Call clearCallHistory() after activation when a test only cares about calls made by a command.

Important limitation

Simulating stale form updates

After rendering a form in a view or dialog, call host.simulateFormChange(viewIdOrDialogId, formId) to advance its input revision and obtain an expectedForm token. A second call invalidates the first token. Pass a token to context.ui.views.update(id, { blocks, expectedForm }) or inside context.ui.updateDialog(id, { title, content: { blocks, expectedForm } }). Stale tokens reject with StaleRevision; resets, removal and closure invalidate the corresponding snapshots. This helper models revision changes only: it does not render inputs, change field values or dispatch debounced change commands.

Runtime boundary

This package is a test double, not QuickJS and not a security sandbox. Plugin code runs in the same Node.js process as the test, with the test process's full authority. The host does not reproduce worker isolation, timeouts, memory or CPU limits, signature checks, package integrity checks, or the production runtime's worker scheduling or native DNS resolver. JSON-compatible command and storage values are validated as finite, plain containers with enumerable data properties (including rejection of sparse arrays), but use this package for plugin behavior tests, not for executing untrusted plugins or proving runtime security.

Embedded view contexts

Use await host.setEmbeddedViewContext(viewId, "page-a") to simulate mounting an embedded view, pass a new token to simulate navigation, or pass null to unmount. ui.views.update requires the current token in expectedContextId; stale or absent contexts reject with StaleRevision.

Kanban and source handoff

Protocol 0.1.6 adds a host-rendered Kanban block and explicit source-to-plugin document handoff. See contract and integration notes. These changes are source-only and require a matching host.

Document adapters (protocol 0.1.7)

Pass renderDocument(options) and exportFile(options, document?) in createPluginTestHost options. File adapters receive the owned rendered document for document-ID exports and return { saved: boolean }. Rendering adapters return { html, text, warnings, title? }; the host allocates an owned ID. Missing adapters return UnavailableOnPlatform, since Node does not implement browser CSS or a save dialog. The shared input validator applies the same transport quotas. Use host.clipboard, host.editorStyles and callHistory to inspect effects. Invoke output through host.executeCommand; automatic listeners have no user-action scope. Browser CSS sanitization/layout and real WeChat pasting require host integration verification.