Plugins
Testing a plugin
Run a plugin test target with loam plugin test, write logic and UI tests with the harnesses, and check the plugin on a vault.
A generated plugin comes with one test. This page shows how to run the tests with loam plugin test, how to write a logic test and a UI test, how to check the plugin on a vault, and what to avoid.
Run the tests
loam plugin test <id> runs the package tests against the app SDK and prints JSON with ok and the test log. The first run also compiles the test target, so it takes a few minutes.
loam --vault ~/Notes plugin test word-countA logic test
A logic test builds a kernel on a temporary vault, activates the plugin, runs a command, and checks the result. PluginTestHarness comes from PluginKitTesting. This is the generated test:
Tests/WordCountTests/WordCountPluginTests.swift
import PluginKitTesting
import XCTest
@testable import WordCount
final class WordCountPluginTests: XCTestCase {
func testSaysHelloWithTheNoteCount() async throws {
let harness = try PluginTestHarness([WordCountPlugin.self], files: ["one.md": "# One", "two.md": "# Two"])
let output = try await harness.run("word-count.hello", ["name": "Ada"])
XCTAssertEqual(output, "Hello Ada. Notes in this vault: 2.")
}
}The files argument creates notes in the temporary vault before the test runs. harness.run runs a command by id. End each test with harness.host.deactivateAll().
A UI test
A UI test uses UIPluginHarness from PluginKitUITesting. It gives you the host services, a recording workspace and quick pick, and render(_:), which lays a view out offscreen. This is the generated UI test:
Tests/WordPanelTests/WordPanelPluginTests.swift
import PluginKitUITesting
import XCTest
@testable import WordPanel
@MainActor
final class WordPanelPluginTests: XCTestCase {
func testAddsItsPanelAndCommand() async throws {
let harness = try UIPluginHarness(uiPlugins: [WordPanelPlugin.self], files: ["one.md": "# One"])
defer { harness.close() }
XCTAssertTrue(harness.report.failed.isEmpty, "\(harness.report.failed)")
let panel = try XCTUnwrap(harness.panels.all.first { $0.id == "word-panel.panel" })
_ = harness.render(panel.makeView())
let output = try await harness.run("word-panel.hello")
XCTAssertEqual(output, "Hello from Word Panel. Notes in this vault: 1.")
}
}Check it on a vault
Build the plugin, turn it on, and run its command on a test vault:
loam --vault ~/Notes plugin build word-count
loam --vault ~/Notes plugin enable word-count
loam --vault ~/Notes word-count.helloUse a test note for your checks, and delete it afterwards. Keep your own notes unchanged.
Pitfalls
- In the CLI, each run loads the latest build. An open app keeps the old code until it restarts:
loam --vault ~/Notes app relaunch. - Read a note right before you write it. The editor may hold text that is not saved yet, and reading through
context.vaultsaves that text first. - When
okisfalse, the last lines oflogsay why. Fix the first error, then build again. - Finish each test with
harness.host.deactivateAll(), then check that nothing stays registered. - After an app update that changes the SDK major version, rebuild the plugin.
loam guide test has these steps. loam plugin test <id> prints JSON with ok and log.