mcpField
E
electrobun-testing

electrobun-testing

github.com/dexploarerOther510 installsUpdated 34w ago
Repository

About electrobun-testing

Use this skill when the user asks to “write electrobun tests,” “add electrobun test coverage,” “electrobun testing,” use the “defineTest” pattern, generate new electrobun test suites, or reverse-engineer electrobun component behavior from test source.

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/dexploarer/plugin-electrobun-dev --skill electrobun-testing

After installing, type/electrobun-testingin Claude Code to confirm it loaded.

Details

From the project README

Electrobun Testing

Tests for Electrobun APIs live in the Kitchen Sink app (kitchen/src/tests/).

Test Definition Pattern

All tests use defineTest() from the test framework:

// kitchen/src/tests/window.test.ts
import { defineTest } from "../test-framework/defineTest";

export const windowTests = [
  defineTest({
    id: "window-creation-with-url",          // stable slug — must be unique
    title: "Window creation with URL",
    category: "BrowserWindow",
    description: "Verifies BrowserWindow can be created with a URL",
    interactive: false,                       // automated: no human needed
    apiSurface: ["BrowserWindow"],            // APIs exercised (for manifest)
    async run({ assert, log }) {
      const win = new BrowserWindow({
        title: "Test Window",
        url: "views://test-harness/index.html",
        hidden: true,
        rpc: testRpc,
      });
      log("Window created, id:", win.id);
      assert(win.id > 0, "Window should have valid id");
      await win.close();
    },
  }),
];
defineTest() Fields
FieldTypeRequiredDescription
idstring✅Stable slug (kebab-case, globally unique)
titlestring✅Human-readable display name
categorystring✅API grouping (e.g. "BrowserWindow", "Utils")
descriptionstring✅One-sentence description
interactiveboolean✅true if requires human input
apiSurfacestring[]✅Electrobun APIs exercised
runasync function✅Test body (see below)
playgroundRoutestring?—Playground HTML path if has UI
uiSelectorsstring[]—CSS selectors used in playground
platformCaveatsstring[]—e.g. ["macOS only"]
Test Body API
async run({ assert, log, skip, fail }) {
  // assert(condition, message) — fails test if false
  assert(value === expected, "description of what was expected");

  // log(message) — appears in test runner UI
  log("Window id:", win.id);

  // skip(reason) — marks test as skipped (not failed)
  if (process.platform === "win32") skip("Not supported on Windows");

  // fail(message) — immediately fail with message
  fail("Should not reach here");
}

Registering Test Suites

After writing a test file, add it to the aggregator:

// kitchen/src/tests/index.ts
import { windowTests } from "./window.test";
import { myNewTests } from "./my-new-feature.test";

export const allTests = [
  ...windowTests,
  ...myNewTests,   // ← add here
];

Then regenerate the manifest:

cd kitchen
npx tsx scripts/generate-manifest.ts

Writing Automated Tests

Automated tests (interactive: false) run entirely in code — no human in the loop.

Pattern: Test a BrowserWindow method
defineTest({
  id: "window-set-title",
  title: "BrowserWindow.setTitle()",
  category: "BrowserWindow",
  description: "Verifies setTitle changes the window title",
  interactive: false,
  apiSurface: ["BrowserWindow"],
  async run({ assert, log }) {
    const win = new BrowserWindow({
      title: "Original Title",
      url: "views://test-harness/index.html",
      hidden: true,
      rpc: testRpc,
    });
    win.setTitle("New Title");
    // Observe via evaluateJavascript or just test the API doesn't throw
    log("Title changed successfully");
    assert(true, "setTitle completed without error");
    await win.close();
  },
}),
Pattern: Test RPC round-trip
defineTest({
  id: "rpc-request-response",
  title: "RPC request-response round trip",
  category: "RPC",
  description: "Verifies bun-side request handler is callable from webview",
  interactive: false,
  apiSurface: ["BrowserView"],
  async run({ assert, log }) {
    const win = new BrowserWindow({
      title: "RPC Test",
      url: "views://test-harness/index.html",
      hidden: true,
      rpc: testRpc,
    });
    // Use evaluateJavascriptWithResponse to trigger webview-side RPC call
    const result = await win.webview.rpc.request.evaluateJavascriptWithResponse({
      script: `electrobun.rpc.request.someBunFunction({ a: 2, b: 3 })`,
    });
    assert(result === 5, `Expected 5, got ${result}`);
    log("RPC round trip passed");
    await win.close();
  },
}),
Pattern: Test Utils / system APIs
defineTest({
  id: "utils-clipboard-text",
  title: "Clipboard read/write text",
  category: "Utils",
  description: "Verifies clipboardWriteText and clipboardReadText round-trip",
  interactive: false,
  apiSurface: ["Utils"],
  async run({ assert, log }) {
    Utils.clipboardWriteText("electrobun-test-value");
    const text = Utils.clipboardReadText();
    assert(text === "electrobun-test-value", `Expected clipboard text, got: ${text}`);
    Utils.clipboardClear();
    log("Clipboard round-trip passed");
  },
}),
Pattern: Test navigation rules
defineTest({
  id: "navigation-block-rule",
  title: "setNavigationRules blocks disallowed URL",
  category: "Navigation",
  description: "Verifies last-match-wins navigation rules",
  interactive: false,
  apiSurface: ["BrowserView"],
  async run({ assert, log }) {
    const win = new BrowserWindow({ url: "views://test-harness/index.html", hidden: true, rpc: testRpc });
    win.webview.setNavigationRules([
      { match: "*", allow: false },           // block all
      { match: "views://*", allow: true },    // allow views:// (last match wins)
    ]);
    // Navigate to allowed URL — should succeed
    await win.webview.loadURL("views://test-harness/index.html");
    log("Navigation rules applied");
    await win.close();
  },
}),

Writing Interactive Tests

Interactive tests (interactive: true) open a playground window and walk the user through a verification flow.

defineTest({
  id: "dialog-open-file",
  title: "Open file dialog",
  category: "Dialogs",
  description: "Opens the native file picker and verifies a path is returned",
  interactive: true,
  apiSurface: ["Utils"],
  playgroundRoute: "playgrounds/file-dialog/index.html",
  uiSelectors: ["#openDialogBtn", "#result"],
  async run({ assert, log, waitForReady, waitForVerify }) {
    // Open the playground window
    const playground = new BrowserWindow({
      title: "File Dialog Playground",
      url: "views://playgrounds/file-dialog/index.html",
      rpc: playgroundRpc,
    });

    // Wait for user to click Start in the modal
    await waitForReady();

    // Instructions have been shown; user now interacts with playground window
    // Wait for user to click Pass/Fail/Retest
    const { action, notes } = await waitForVerify();

    assert(action === "pass", `User marked ${action}: ${notes}`);
    await playground.close();
  },
}),

Full API Surface Tested by Kitchen Sink

BrowserWindow

new BrowserWindow(options), getById(id), webview, setTitle, close, focus, show, minimize, unminimize, isMinimized, maximize, unmaximize, isMaximized, setFullScreen, isFullScreen, setAlwaysOnTop, isAlwaysOnTop, setVisibleOnAllWorkspaces, isVisibleOnAllWorkspaces, setPosition, setSize, setFrame, getFrame, getPosition, getSize, setPageZoom, getPageZoom, on('close'|'move'|'resize'|'blur'|'focus')

BrowserView

BrowserView.defineRPC(schema), new BrowserView(options), executeJavascript, loadURL, loadHTML, setNavigationRules, stopFindInPage, openDevTools, closeDevTools, toggleDevTools, setPageZoom, getPageZoom, remove, getById, getAll, on('will-navigate'|'did-navigate'|'dom-ready'), rpc.request.evaluateJavascriptWithResponse

Tray

new Tray(options), setTitle, setImage, setMenu, setVisible, getBounds, remove, on('tray-clicked'), getById, getAll, removeById

ApplicationMenu / ContextMenu

ApplicationMenu.setApplicationMenu(menu), ContextMenu.showContextMenu(menu), Electrobun.events.on('application-menu-clicked'), Electrobun.events.on('context-menu-clicked')

Utils

moveToTrash, showItemInFolder, openExternal, openPath, setDockIconVisible, isDockIconVisible, showNotification, quit, openFileDialog, showMessageBox, clipboardReadText, clipboardWriteText, clipboardReadImage, clipboardWriteImage, clipboardClear, clipboardAvailableFormats, paths.*

Session

Session.fromPartition(name), Session.defaultSession, session.cookies.set/get/remove/clear

Screen

Screen.getPrimaryDisplay(), Screen.getAllDisplays(), Screen.getCursorScreenPoint()

GlobalShortcut

GlobalShortcut.register(accelerator, handler), GlobalShortcut.unregister(accelerator), GlobalShortcut.unregisterAll()

Updater

Updater.getLocal, Updater.onStatusChange, Updater.checkForUpdate, Updater.downloadUpdate, Updater.updateInfo, Updater.applyUpdate, Updater.getStatusHistory, Updater.clearStatusHistory

Updater status values: checking, update-available, downloading, update-ready, no-update, error

View-side (electrobun/view)

Electroview.defineRPC(schema), new Electroview({ rpc }), electrobun.rpc.request.*, electrobun.rpc.send.*, evaluateJavascriptWithResponse({ script })

Platform Caveats to Document in Tests

Always add a platformCaveats array when a test is platform-specific:

platformCaveats: ["macOS only"],
platformCaveats: ["requires CEF renderer"],
platformCaveats: ["unreliable on Linux window managers"],
platformCaveats: ["ARM Windows: commented out due to VM crashes"],

Generating and Validating After Changes

cd kitchen

# After adding new defineTest() calls:
npx tsx scripts/generate-manifest.ts

# Verify everything is consistent:
npx tsx scripts/validate-manifest.ts
# Checks:
# 1. Every test in index.ts has manifest entries
# 2. Every playground route with a test is represented
# 3. All entries have required fields
# 4. No duplicate IDs

Comments

Markdown supported