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

dwc-plugin-test-kit

v0.2.6

Published

Reusable unit / component-mount test harness for DuetWebControl (Vue 3) plugins. Mocks the DWC plugin API surface (@/stores/*, @/plugins, @/i18n, @/utils/events) so plugin components can be mounted, fed a fake object model, and asserted on — plus CLI help

Readme

dwc-plugin-test-kit

A reusable test harness for DuetWebControl (Vue 3) plugins. It mocks the small, stable DWC API surface a plugin imports (@/stores/*, @/plugins, @/i18n, @/utils/events, plus vue-router, @duet3d/objectmodel, grid-layout-plus) so you can:

  • mount any plugin component under Vuetify + happy-dom and assert it renders without throwing,
  • feed it a fake object model and check what it displays,
  • drive interactions and assert the exact G-code it sends,
  • type-check and verify the installable build of the plugin against a DWC checkout,
  • run all of the above in CI.

It is plugin-agnostic — drop it into any DWC plugin repo (FlexibleLayouts, OmBrowser, Neopixel, Globals, …) the same way.

Why mount tests matter

Pure-logic unit tests miss the bugs that actually break plugins: a component that throws on mount (a TDZ in setup, a Vuetify component that crashes when rendered). A registry-driven "mount every widget" test catches those automatically — including for widgets you add later.


Add it to a plugin

1. Install (peer deps give you a single, deduped Vue/Vuetify copy):

// package.json
"devDependencies": {
  "dwc-plugin-test-kit": "github:jaysuk/dwc-plugin-test-kit",  // or "file:../dwc-plugin-test-kit"
  "@vitejs/plugin-vue": "^5.2.4",
  "@vue/test-utils": "^2.4.6",
  "happy-dom": "^15.11.7",
  "vitest": "^4.1.8",
  "vue": "^3.5.30",
  "vuetify": "^4.1.0"
},
"scripts": {
  "test": "vitest run",
  "typecheck": "dwc-plugin-typecheck",
  "verify-build": "dwc-plugin-verify-build"
}

2. Vitest configvitest.config.ts:

import vue from "@vitejs/plugin-vue";
import { dwcVitestConfig } from "dwc-plugin-test-kit/vitest";

export default dwcVitestConfig({ plugins: [vue()] });

That wires up the @/…→stub aliases, happy-dom, dep dedupe, Vuetify CSS handling and the setup polyfills. (Pass overrides like dwcVitestConfig({ plugins: [vue()], test: { coverage: {…} } }).)

3. Write tests under test/:

import { describe, expect, it } from "vitest";
import { mountInDwc, sentCodes } from "dwc-plugin-test-kit";
import MyButton from "../src/widgets/MyButton.vue";

it("sends G28 when clicked", async () => {
  const w = mountInDwc(MyButton, { props: { widget: { type: "codeButton", code: "G28" } } });
  await w.find("button").trigger("click");
  expect(sentCodes()).toContain("G28");
});

A self-maintaining smoke test (covers every component automatically):

import { mountInDwc } from "dwc-plugin-test-kit";
import { FREEFORM_WIDGETS } from "../src/widgets/registry";
import { createDefaultWidget } from "../src/model/document";
import WidgetView from "../src/widgets/WidgetView.vue";

for (const e of FREEFORM_WIDGETS) {
  it(`mounts: ${e.type}`, () => {
    expect(mountInDwc(WidgetView, { props: { widget: createDefaultWidget(e.type) } }).exists()).toBe(true);
  });
}

npm test runs them.


Harness API

import {
  mountInDwc,        // mount(component, { props, slots, global }) with Vuetify + $t installed
  setModel,          // replace the object model
  patchModel,        // shallow-merge into the object model
  setConnected,      // toggle machineStore.isConnected
  setUiFrozen,       // toggle uiStore.uiFrozen
  setFiles,          // seed machineStore.getFileList(dir)
  setGlobals,        // set the `global.*` map
  setMessageBox,     // set state.messageBox (M291)
  sentCodes,         // string[] of every G-code sent, in order
  lastCode,          // the most recent sent G-code
  notifications,     // uiStore notifications raised
  makeObjectModel,   // build/override a small programmatic OM fixture
  loadObjectModel,   // load a fuller realistic model — or your own M409 dump (see below)
  byTitle, byText,   // find a button by title= / text (3rd arg overrides the "button" selector)
  clickByTitle, clickByText,  // find + click; throw a helpful error if nothing matches
  expectCode, expectLastCode, expectNoCodes,  // Vitest assertions over sentCodes()
  dwc, resetDwc,     // the raw reactive state + manual reset (auto-reset runs before each test)
} from "dwc-plugin-test-kit";

The fake machineStore.sendCode records into sentCodes(); getFileList reads setFiles(); the object model defaults to makeObjectModel() (axes, heaters, fans, spindles, tools, a global Map, ledStrips, …). State resets before every test.

Realistic data: loadObjectModel

makeObjectModel() is a small programmatic fixture. For true-to-life data use loadObjectModel():

setModel(loadObjectModel());                 // bundled fuller standalone Duet 3 model
setModel(loadObjectModel(myDump));           // your captured machineStore.model
setModel(loadObjectModel(m409Response));     // an RRF `M409 K"" ` response ({ result }) — auto-unwrapped

It deep-clones (no cross-test leakage) and converts a plain-object global to a Map (matching @duet3d/objectmodel's ModelDictionary); pass { mapGlobals: false } to keep it, { overrides } to patch. Capturing a real model from your own machine and dropping it in is the cheapest way to catch field-shape assumptions the thin fixture hides.

CLI (build + type gates)

Both take a DuetWebControl checkout via DWC_DIR:

DWC_DIR=/path/to/DuetWebControl npm run typecheck      # vue-tsc the plugin against DWC's @/ types
DWC_DIR=/path/to/DuetWebControl npm run verify-build    # build the ZIP; assert dwcFiles + externals

verify-build fails if the bundle isn't correctly externalised (must reference window.DWC.*, must not bundle a second Vuetify copy) or the manifest's dwcFiles is empty.

CI

Add .github/workflows/ci.yml to your plugin:

name: CI
on: [push, pull_request]
jobs:
  test:
    uses: jaysuk/dwc-plugin-test-kit/.github/workflows/dwc-plugin-ci.yml@main
    # with: { dwc-ref: next }   # optional overrides

It runs the unit/mount tests, then (in a second job) checks out DWC and runs verify-build + typecheck. If your plugin defines a test:coverage script (see below) the unit job runs it instead of npm test, so coverage is collected in CI automatically.

Coverage — see what's left to test

dwcVitestConfig ships coverage defaults scoped to your plugin's src/** (skipping *.d.ts, generated model-data.js, configs and tests), so you only need to opt in:

// package.json
"devDependencies": { "@vitest/coverage-v8": "^4.1.8", /* … */ },
"scripts": { "test:coverage": "vitest run --coverage", /* … */ }

npm run test:coverage prints a per-file table of uncovered lines/branches and writes an HTML report to coverage/. That table is your "what do I still need to test?" list. Tune via test.coverage in vitest.config.ts (deep-merged over the defaults), e.g. add thresholds: { lines: 80 } to fail CI below a bar.

Vendoring instead of installing

Prefer to copy it in? Drop the src/ and bin/ folders into your repo (e.g. test-kit/), point your vitest.config.ts at ./test-kit/src/vitest.mjs, and run the bins with node ./test-kit/bin/…. Everything is dependency-light and self-contained.

What it does NOT cover

happy-dom mounting catches setup/TDZ errors and most render errors, but a few Vuetify behaviours only reproduce in a real browser (teleported overlays, layout measurement). For those — and for true integration (route registration, the custom shell, visual regression) — see the Playwright E2E template in e2e-template/. Its mock-Duet connector is a tested kit module (dwc-plugin-test-kit/mock-duet, exercised by test/mock-duet.test.ts — boot a server, drive the rr_* endpoints, assert recorded G-code), so that half runs in CI; the browser/screenshot half needs a built DWC serving your plugin and stays opt-in scaffolding you copy to e2e/ and adapt.

License

MIT