obsidian-e2e 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -237,8 +237,7 @@ Fixture summary:
237
237
  - `sandbox`
238
238
  - a per-test disposable directory under `sandboxRoot`; automatically cleaned
239
239
  up after each test
240
- - exposes note helpers such as `writeNote()`, `readNote()`, `frontmatter()`,
241
- `waitForFrontmatter()`, and `waitForMetadata()`
240
+ - exposes note helpers such as `writeNote()`, `readNote()`, and `path()`
242
241
 
243
242
  Plugin data mutations are snapshotted on first write and restored automatically
244
243
  after each test. Sandbox files are also cleaned up automatically.
@@ -266,14 +265,14 @@ await expect(sandbox.readNote("Inbox/Today.md")).resolves.toMatchObject({
266
265
  },
267
266
  });
268
267
 
269
- await sandbox.waitForFrontmatter("Inbox/Today.md", (frontmatter) =>
268
+ await obsidian.metadata.waitForFrontmatter(sandbox.path("Inbox/Today.md"), (frontmatter) =>
270
269
  frontmatter.tags.includes("daily"),
271
270
  );
272
271
  ```
273
272
 
274
- `readNote()` is file-derived. `sandbox.frontmatter()` and
275
- `sandbox.waitForMetadata()` are Obsidian metadata-cache reads, so tests can
276
- distinguish raw file content from “Obsidian has indexed this note”.
273
+ `readNote()` is file-derived. Metadata-cache reads stay under
274
+ `obsidian.metadata.*`, so tests can distinguish raw file content from
275
+ “Obsidian has indexed this note”.
277
276
 
278
277
  Outside Vitest fixtures, use the public lifecycle wrapper:
279
278
 
@@ -559,6 +558,7 @@ to the real `obsidian` CLI:
559
558
  - `obsidian.command(id).run()`
560
559
  - `obsidian.dev.dom({ ... })`
561
560
  - `obsidian.dev.eval(code)`
561
+ - `obsidian.dev.evalJson(code)`
562
562
  - `obsidian.dev.evalRaw(code)`
563
563
  - `obsidian.dev.diagnostics()`
564
564
  - `obsidian.dev.resetDiagnostics()`
@@ -626,7 +626,7 @@ test("waits for generated content and plugin state", async ({ obsidian, sandbox,
626
626
  },
627
627
  body: "# Today\n",
628
628
  });
629
- await sandbox.waitForFrontmatter("Inbox/Today.md", (frontmatter) =>
629
+ await obsidian.metadata.waitForFrontmatter(sandbox.path("Inbox/Today.md"), (frontmatter) =>
630
630
  frontmatter.tags.includes("daily"),
631
631
  );
632
632
 
@@ -678,9 +678,10 @@ test("inspects live UI state", async ({ obsidian }) => {
678
678
  });
679
679
  ```
680
680
 
681
- `obsidian.dev.eval()` now uses a structured JSON envelope and throws remote
682
- stack information when the evaluated code fails. Use `obsidian.dev.evalRaw()`
683
- only when you intentionally need the unstructured CLI output. `dev.dom()` and
681
+ `obsidian.dev.eval()` remains the low-level escape hatch and preserves the raw
682
+ CLI parsing behavior. Use `obsidian.dev.evalJson()` when you want JSON-safe
683
+ typed results and remote error details, and `obsidian.dev.evalRaw()` when you
684
+ intentionally need the unstructured CLI output. `dev.dom()` and
684
685
  `dev.screenshot()` remain the safer wrappers around the built-in developer CLI
685
686
  commands. Screenshot behavior depends on the active desktop environment, so
686
687
  start by validating it locally before relying on it in automation.
@@ -691,17 +692,19 @@ start by validating it locally before relying on it in automation.
691
692
  workspace state, it does not belong there.
692
693
  - `sandbox.readNote()` parses file content only. It does not imply that
693
694
  Obsidian has indexed the note.
694
- - `sandbox.frontmatter()` and `obsidian.metadata.*` read metadata-cache state,
695
- which is the right layer for frontmatter synchronization and race-sensitive
696
- tests.
697
- - `obsidian.dev.eval()` is structured but still low-level. Prefer the
698
- higher-level metadata, sandbox, wait, plugin, and matcher helpers first.
695
+ - `obsidian.metadata.*` reads metadata-cache state, which is the right layer
696
+ for frontmatter synchronization and race-sensitive tests.
697
+ - `obsidian.dev.eval()` is the escape hatch. Prefer the higher-level metadata,
698
+ sandbox, wait, plugin, and matcher helpers first, and use
699
+ `obsidian.dev.evalJson()` when you need structured JSON-safe results.
699
700
 
700
701
  ## Migration Notes
701
702
 
702
- - If you relied on raw `obsidian.dev.eval()` output strings, switch those calls
703
- to `obsidian.dev.evalRaw()`. The structured `eval()` path now expects JSON-safe
704
- values and throws `DevEvalError` with the remote stack.
703
+ - Keep using `obsidian.dev.eval()` for the raw escape hatch semantics.
704
+ - Use `obsidian.dev.evalJson()` when you want JSON-safe typed results and
705
+ `DevEvalError` stack details.
706
+ - Use `obsidian.metadata.*` for metadata-cache synchronization, including notes
707
+ created under `sandbox.path(...)`.
705
708
  - Prefer `sandbox.writeNote()` over hand-built YAML strings when the test is
706
709
  describing note content rather than string formatting.
707
710
  - Prefer `plugin.updateDataAndReload()` or `plugin.withPatchedData()` over open-
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as PluginHandle, B as VaultApi, C as ObsidianClient, D as OpenFileOptions, E as ObsidianMetadataHandle, F as PluginWaitUntilReadyOptions, G as WorkspaceNode, H as VaultWaitForContentOptions, I as PluginWithPatchedDataOptions, K as WorkspaceOptions, L as RestartAppOptions, N as PluginUpdateDataOptions, O as OpenTabOptions, P as PluginWaitForDataOptions, R as SandboxApi, S as ObsidianArg, T as ObsidianDevHandle, U as VaultWriteOptions, V as VaultContentPredicate, W as WaitForOptions, _ as NoteDocument, a as DevDiagnostics, b as NoteMatcherOptions, c as DevNoticeEvent, d as ExecResult, f as JsonFile, g as MetadataWaitOptions, h as MetadataPredicate, i as DevConsoleMessage, j as PluginReloadOptions, k as PluginDataPredicate, l as DevRuntimeError, m as MetadataFileCache, n as CommandTransport, o as DevDomQueryOptions, p as JsonFileUpdater, q as WorkspaceTab, r as CreateObsidianClientOptions, s as DevDomResult, t as CommandListOptions, u as ExecOptions, v as NoteFrontmatter, w as ObsidianCommandHandle, x as ObsidianAppHandle, y as NoteInput, z as TabsOptions } from "./types-C4cj443K.mjs";
2
- import { C as FailureArtifactConfig, D as captureFailureArtifacts, E as FailureArtifactTask, S as DEFAULT_FAILURE_ARTIFACTS_DIR, T as FailureArtifactRegistrationOptions, _ as TestContext, a as acquireVaultRunLock, b as VaultSeedEntry, c as readVaultRunLockMarker, d as ObsidianFixtures, f as ObsidianTest, g as SharedVaultLockOptions, h as PluginTest, i as VaultRunLockState, l as CreateObsidianTestOptions, m as PluginSessionOptions, n as VaultRunLock, o as clearVaultRunLockMarker, p as PluginFixtures, r as VaultRunLockMetadata, s as inspectVaultRunLock, t as AcquireVaultRunLockOptions, u as CreatePluginTestOptions, v as TestContextCleanupOptions, w as FailureArtifactOptions, x as CaptureFailureArtifactsOptions, y as VaultSeed } from "./vault-lock-CYyOdRP1.mjs";
1
+ import { A as PluginHandle, B as VaultApi, C as ObsidianClient, D as OpenFileOptions, E as ObsidianMetadataHandle, F as PluginWaitUntilReadyOptions, G as WorkspaceNode, H as VaultWaitForContentOptions, I as PluginWithPatchedDataOptions, K as WorkspaceOptions, L as RestartAppOptions, N as PluginUpdateDataOptions, O as OpenTabOptions, P as PluginWaitForDataOptions, R as SandboxApi, S as ObsidianArg, T as ObsidianDevHandle, U as VaultWriteOptions, V as VaultContentPredicate, W as WaitForOptions, _ as NoteDocument, a as DevDiagnostics, b as NoteMatcherOptions, c as DevNoticeEvent, d as ExecResult, f as JsonFile, g as MetadataWaitOptions, h as MetadataPredicate, i as DevConsoleMessage, j as PluginReloadOptions, k as PluginDataPredicate, l as DevRuntimeError, m as MetadataFileCache, n as CommandTransport, o as DevDomQueryOptions, p as JsonFileUpdater, q as WorkspaceTab, r as CreateObsidianClientOptions, s as DevDomResult, t as CommandListOptions, u as ExecOptions, v as NoteFrontmatter, w as ObsidianCommandHandle, x as ObsidianAppHandle, y as NoteInput, z as TabsOptions } from "./types-BUXaueDI.mjs";
2
+ import { C as FailureArtifactConfig, D as captureFailureArtifacts, E as FailureArtifactTask, S as DEFAULT_FAILURE_ARTIFACTS_DIR, T as FailureArtifactRegistrationOptions, _ as TestContext, a as acquireVaultRunLock, b as VaultSeedEntry, c as readVaultRunLockMarker, d as ObsidianFixtures, f as ObsidianTest, g as SharedVaultLockOptions, h as PluginTest, i as VaultRunLockState, l as CreateObsidianTestOptions, m as PluginSessionOptions, n as VaultRunLock, o as clearVaultRunLockMarker, p as PluginFixtures, r as VaultRunLockMetadata, s as inspectVaultRunLock, t as AcquireVaultRunLockOptions, u as CreatePluginTestOptions, v as TestContextCleanupOptions, w as FailureArtifactOptions, x as CaptureFailureArtifactsOptions, y as VaultSeed } from "./vault-lock-XcBHtwm9.mjs";
3
3
 
4
4
  //#region src/core/client.d.ts
5
5
  declare function createObsidianClient(options: CreateObsidianClientOptions): ObsidianClient;
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { a as clearVaultRunLockMarker, c as createSandboxApi, d as createVaultApi, i as acquireVaultRunLock, l as DEFAULT_FAILURE_ARTIFACTS_DIR, n as withVaultSandbox, o as inspectVaultRunLock, p as createObsidianClient, s as readVaultRunLockMarker, t as createTestContext, u as captureFailureArtifacts } from "./test-context-BprSx6U1.mjs";
1
+ import { a as clearVaultRunLockMarker, c as createSandboxApi, d as createVaultApi, i as acquireVaultRunLock, l as DEFAULT_FAILURE_ARTIFACTS_DIR, n as withVaultSandbox, o as inspectVaultRunLock, p as createObsidianClient, s as readVaultRunLockMarker, t as createTestContext, u as captureFailureArtifacts } from "./test-context-Bl-e-83H.mjs";
2
2
  import { n as parseNoteDocument, r as stringifyNoteDocument, t as createNoteDocument } from "./document-DunL2Moz.mjs";
3
3
  export { DEFAULT_FAILURE_ARTIFACTS_DIR, acquireVaultRunLock, captureFailureArtifacts, clearVaultRunLockMarker, createNoteDocument, createObsidianClient, createSandboxApi, createTestContext, createVaultApi, inspectVaultRunLock, parseNoteDocument, readVaultRunLockMarker, stringifyNoteDocument, withVaultSandbox };
@@ -1,4 +1,4 @@
1
- import { b as NoteMatcherOptions, v as NoteFrontmatter } from "./types-C4cj443K.mjs";
1
+ import { b as NoteMatcherOptions, v as NoteFrontmatter } from "./types-BUXaueDI.mjs";
2
2
 
3
3
  //#region src/matchers.d.ts
4
4
  declare module "vite-plus/test" {
package/dist/matchers.mjs CHANGED
@@ -37,7 +37,11 @@ expect.extend({
37
37
  };
38
38
  },
39
39
  async toHaveFrontmatter(target, targetPath, expected) {
40
- const actual = await target.frontmatter(targetPath);
40
+ if (!await target.exists(targetPath)) return {
41
+ message: () => `Expected vault path to exist: ${targetPath}`,
42
+ pass: false
43
+ };
44
+ const actual = parseNoteDocument(await target.read(targetPath)).frontmatter;
41
45
  const pass = isDeepStrictEqual(actual, expected);
42
46
  return {
43
47
  message: () => pass ? `Expected frontmatter for "${targetPath}" not to equal ${JSON.stringify(expected)}` : `Expected frontmatter for "${targetPath}" to equal ${JSON.stringify(expected)}, received ${JSON.stringify(actual)}`,
@@ -1 +1 @@
1
- {"version":3,"file":"matchers.mjs","names":[],"sources":["../src/matchers.ts"],"sourcesContent":["import { isDeepStrictEqual } from \"node:util\";\n\nimport { expect } from \"vite-plus/test\";\n\nimport { parseNoteDocument } from \"./note/document\";\nimport type {\n NoteMatcherOptions,\n NoteFrontmatter,\n ObsidianClient,\n PluginHandle,\n SandboxApi,\n VaultApi,\n} from \"./core/types\";\n\ntype FileMatcherTarget = SandboxApi | VaultApi;\n\nexpect.extend({\n async toHaveActiveFile(target: ObsidianClient, targetPath: string) {\n const actual = await target.dev.activeFilePath();\n const pass = actual === targetPath;\n\n return {\n message: () =>\n pass\n ? `Expected active file not to be \"${targetPath}\"`\n : `Expected active file to be \"${targetPath}\", received ${JSON.stringify(actual)}`,\n pass,\n };\n },\n async toHaveCommand(target: ObsidianClient, commandId: string) {\n const pass = await target.command(commandId).exists();\n\n return {\n message: () =>\n pass\n ? `Expected Obsidian command not to exist: ${commandId}`\n : `Expected Obsidian command to exist: ${commandId}`,\n pass,\n };\n },\n async toHaveFile(target: FileMatcherTarget, targetPath: string) {\n const pass = await target.exists(targetPath);\n\n return {\n message: () =>\n pass\n ? `Expected vault path not to exist: ${targetPath}`\n : `Expected vault path to exist: ${targetPath}`,\n pass,\n };\n },\n async toHaveFileContaining(target: FileMatcherTarget, targetPath: string, needle: string) {\n const exists = await target.exists(targetPath);\n\n if (!exists) {\n return {\n message: () => `Expected vault path to exist: ${targetPath}`,\n pass: false,\n };\n }\n\n const content = await target.read(targetPath);\n const pass = content.includes(needle);\n\n return {\n message: () =>\n pass\n ? `Expected vault path \"${targetPath}\" not to contain \"${needle}\"`\n : `Expected vault path \"${targetPath}\" to contain \"${needle}\"`,\n pass,\n };\n },\n async toHaveFrontmatter(target: SandboxApi, targetPath: string, expected: NoteFrontmatter) {\n const actual = await target.frontmatter(targetPath);\n const pass = isDeepStrictEqual(actual, expected);\n\n return {\n message: () =>\n pass\n ? `Expected frontmatter for \"${targetPath}\" not to equal ${JSON.stringify(expected)}`\n : `Expected frontmatter for \"${targetPath}\" to equal ${JSON.stringify(\n expected,\n )}, received ${JSON.stringify(actual)}`,\n pass,\n };\n },\n async toHaveJsonFile(target: FileMatcherTarget, targetPath: string) {\n const exists = await target.exists(targetPath);\n\n if (!exists) {\n return {\n message: () => `Expected JSON file to exist: ${targetPath}`,\n pass: false,\n };\n }\n\n try {\n await target.json(targetPath).read();\n return {\n message: () => `Expected JSON file \"${targetPath}\" not to be valid JSON`,\n pass: true,\n };\n } catch (error) {\n return {\n message: () =>\n `Expected JSON file \"${targetPath}\" to be valid JSON, but parsing failed: ${\n error instanceof Error ? error.message : String(error)\n }`,\n pass: false,\n };\n }\n },\n async toHaveNote(target: FileMatcherTarget, targetPath: string, expected: NoteMatcherOptions) {\n let actual: ReturnType<typeof parseNoteDocument>;\n\n try {\n actual = parseNoteDocument(await target.read(targetPath));\n } catch (error) {\n return {\n message: () =>\n `Expected vault path \"${targetPath}\" to be readable as a note, but reading failed: ${\n error instanceof Error ? error.message : String(error)\n }`,\n pass: false,\n };\n }\n\n const matchesFrontmatter =\n expected.frontmatter === undefined ||\n isDeepStrictEqual(actual.frontmatter, expected.frontmatter);\n const matchesBody = expected.body === undefined || actual.body === expected.body;\n const matchesBodyIncludes =\n expected.bodyIncludes === undefined || actual.body.includes(expected.bodyIncludes);\n const pass = matchesFrontmatter && matchesBody && matchesBodyIncludes;\n\n return {\n message: () =>\n pass\n ? `Expected vault path \"${targetPath}\" not to match the expected note shape`\n : `Expected vault path \"${targetPath}\" to match the expected note shape, received ${JSON.stringify(\n actual,\n )}`,\n pass,\n };\n },\n async toHaveOpenTab(target: ObsidianClient, title: string, viewType?: string) {\n const tabs = await target.tabs();\n const pass = tabs.some(\n (tab) => tab.title === title && (viewType === undefined || tab.viewType === viewType),\n );\n\n return {\n message: () =>\n pass\n ? `Expected no open tab matching \"${title}\"${\n viewType ? ` with view type \"${viewType}\"` : \"\"\n }`\n : `Expected an open tab matching \"${title}\"${\n viewType ? ` with view type \"${viewType}\"` : \"\"\n }`,\n pass,\n };\n },\n async toHavePluginData(target: PluginHandle, expected: unknown) {\n const actual = await target.data().read();\n const pass = isDeepStrictEqual(actual, expected);\n\n return {\n message: () =>\n pass\n ? `Expected plugin data not to equal ${JSON.stringify(expected)}`\n : `Expected plugin data to equal ${JSON.stringify(expected)}, received ${JSON.stringify(\n actual,\n )}`,\n pass,\n };\n },\n async toHaveEditorTextContaining(target: ObsidianClient, needle: string) {\n const actual = await target.dev.editorText();\n const pass = typeof actual === \"string\" && actual.includes(needle);\n\n return {\n message: () =>\n pass\n ? `Expected editor text not to contain \"${needle}\"`\n : `Expected editor text to contain \"${needle}\", received ${JSON.stringify(actual)}`,\n pass,\n };\n },\n async toHaveWorkspaceNode(target: ObsidianClient, label: string) {\n const pass = hasWorkspaceNode(await target.workspace(), label);\n\n return {\n message: () =>\n pass\n ? `Expected workspace not to contain node \"${label}\"`\n : `Expected workspace to contain node \"${label}\"`,\n pass,\n };\n },\n});\n\ndeclare module \"vite-plus/test\" {\n interface Assertion<T = any> {\n toHaveActiveFile(path: string): Promise<T>;\n toHaveCommand(commandId: string): Promise<T>;\n toHaveEditorTextContaining(needle: string): Promise<T>;\n toHaveFile(path: string): Promise<T>;\n toHaveFileContaining(path: string, needle: string): Promise<T>;\n toHaveFrontmatter(path: string, expected: NoteFrontmatter): Promise<T>;\n toHaveJsonFile(path: string): Promise<T>;\n toHaveNote(path: string, expected: NoteMatcherOptions): Promise<T>;\n toHaveOpenTab(title: string, viewType?: string): Promise<T>;\n toHavePluginData(expected: unknown): Promise<T>;\n toHaveWorkspaceNode(label: string): Promise<T>;\n }\n\n interface AsymmetricMatchersContaining {\n toHaveActiveFile(path: string): void;\n toHaveCommand(commandId: string): void;\n toHaveEditorTextContaining(needle: string): void;\n toHaveFile(path: string): void;\n toHaveFileContaining(path: string, needle: string): void;\n toHaveFrontmatter(path: string, expected: NoteFrontmatter): void;\n toHaveJsonFile(path: string): void;\n toHaveNote(path: string, expected: NoteMatcherOptions): void;\n toHaveOpenTab(title: string, viewType?: string): void;\n toHavePluginData(expected: unknown): void;\n toHaveWorkspaceNode(label: string): void;\n }\n}\n\nexport {};\n\nfunction hasWorkspaceNode(\n nodes: Awaited<ReturnType<ObsidianClient[\"workspace\"]>>,\n label: string,\n): boolean {\n for (const node of nodes) {\n if (node.label === label || hasWorkspaceNode(node.children, label)) {\n return true;\n }\n }\n\n return false;\n}\n"],"mappings":";;;;AAgBA,OAAO,OAAO;CACZ,MAAM,iBAAiB,QAAwB,YAAoB;EACjE,MAAM,SAAS,MAAM,OAAO,IAAI,gBAAgB;EAChD,MAAM,OAAO,WAAW;AAExB,SAAO;GACL,eACE,OACI,mCAAmC,WAAW,KAC9C,+BAA+B,WAAW,cAAc,KAAK,UAAU,OAAO;GACpF;GACD;;CAEH,MAAM,cAAc,QAAwB,WAAmB;EAC7D,MAAM,OAAO,MAAM,OAAO,QAAQ,UAAU,CAAC,QAAQ;AAErD,SAAO;GACL,eACE,OACI,2CAA2C,cAC3C,uCAAuC;GAC7C;GACD;;CAEH,MAAM,WAAW,QAA2B,YAAoB;EAC9D,MAAM,OAAO,MAAM,OAAO,OAAO,WAAW;AAE5C,SAAO;GACL,eACE,OACI,qCAAqC,eACrC,iCAAiC;GACvC;GACD;;CAEH,MAAM,qBAAqB,QAA2B,YAAoB,QAAgB;AAGxF,MAAI,CAFW,MAAM,OAAO,OAAO,WAAW,CAG5C,QAAO;GACL,eAAe,iCAAiC;GAChD,MAAM;GACP;EAIH,MAAM,QADU,MAAM,OAAO,KAAK,WAAW,EACxB,SAAS,OAAO;AAErC,SAAO;GACL,eACE,OACI,wBAAwB,WAAW,oBAAoB,OAAO,KAC9D,wBAAwB,WAAW,gBAAgB,OAAO;GAChE;GACD;;CAEH,MAAM,kBAAkB,QAAoB,YAAoB,UAA2B;EACzF,MAAM,SAAS,MAAM,OAAO,YAAY,WAAW;EACnD,MAAM,OAAO,kBAAkB,QAAQ,SAAS;AAEhD,SAAO;GACL,eACE,OACI,6BAA6B,WAAW,iBAAiB,KAAK,UAAU,SAAS,KACjF,6BAA6B,WAAW,aAAa,KAAK,UACxD,SACD,CAAC,aAAa,KAAK,UAAU,OAAO;GAC3C;GACD;;CAEH,MAAM,eAAe,QAA2B,YAAoB;AAGlE,MAAI,CAFW,MAAM,OAAO,OAAO,WAAW,CAG5C,QAAO;GACL,eAAe,gCAAgC;GAC/C,MAAM;GACP;AAGH,MAAI;AACF,SAAM,OAAO,KAAK,WAAW,CAAC,MAAM;AACpC,UAAO;IACL,eAAe,uBAAuB,WAAW;IACjD,MAAM;IACP;WACM,OAAO;AACd,UAAO;IACL,eACE,uBAAuB,WAAW,0CAChC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;IAE1D,MAAM;IACP;;;CAGL,MAAM,WAAW,QAA2B,YAAoB,UAA8B;EAC5F,IAAI;AAEJ,MAAI;AACF,YAAS,kBAAkB,MAAM,OAAO,KAAK,WAAW,CAAC;WAClD,OAAO;AACd,UAAO;IACL,eACE,wBAAwB,WAAW,kDACjC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;IAE1D,MAAM;IACP;;EAGH,MAAM,qBACJ,SAAS,gBAAgB,KAAA,KACzB,kBAAkB,OAAO,aAAa,SAAS,YAAY;EAC7D,MAAM,cAAc,SAAS,SAAS,KAAA,KAAa,OAAO,SAAS,SAAS;EAC5E,MAAM,sBACJ,SAAS,iBAAiB,KAAA,KAAa,OAAO,KAAK,SAAS,SAAS,aAAa;EACpF,MAAM,OAAO,sBAAsB,eAAe;AAElD,SAAO;GACL,eACE,OACI,wBAAwB,WAAW,0CACnC,wBAAwB,WAAW,+CAA+C,KAAK,UACrF,OACD;GACP;GACD;;CAEH,MAAM,cAAc,QAAwB,OAAe,UAAmB;EAE5E,MAAM,QADO,MAAM,OAAO,MAAM,EACd,MACf,QAAQ,IAAI,UAAU,UAAU,aAAa,KAAA,KAAa,IAAI,aAAa,UAC7E;AAED,SAAO;GACL,eACE,OACI,kCAAkC,MAAM,GACtC,WAAW,oBAAoB,SAAS,KAAK,OAE/C,kCAAkC,MAAM,GACtC,WAAW,oBAAoB,SAAS,KAAK;GAErD;GACD;;CAEH,MAAM,iBAAiB,QAAsB,UAAmB;EAC9D,MAAM,SAAS,MAAM,OAAO,MAAM,CAAC,MAAM;EACzC,MAAM,OAAO,kBAAkB,QAAQ,SAAS;AAEhD,SAAO;GACL,eACE,OACI,qCAAqC,KAAK,UAAU,SAAS,KAC7D,iCAAiC,KAAK,UAAU,SAAS,CAAC,aAAa,KAAK,UAC1E,OACD;GACP;GACD;;CAEH,MAAM,2BAA2B,QAAwB,QAAgB;EACvE,MAAM,SAAS,MAAM,OAAO,IAAI,YAAY;EAC5C,MAAM,OAAO,OAAO,WAAW,YAAY,OAAO,SAAS,OAAO;AAElE,SAAO;GACL,eACE,OACI,wCAAwC,OAAO,KAC/C,oCAAoC,OAAO,cAAc,KAAK,UAAU,OAAO;GACrF;GACD;;CAEH,MAAM,oBAAoB,QAAwB,OAAe;EAC/D,MAAM,OAAO,iBAAiB,MAAM,OAAO,WAAW,EAAE,MAAM;AAE9D,SAAO;GACL,eACE,OACI,2CAA2C,MAAM,KACjD,uCAAuC,MAAM;GACnD;GACD;;CAEJ,CAAC;AAkCF,SAAS,iBACP,OACA,OACS;AACT,MAAK,MAAM,QAAQ,MACjB,KAAI,KAAK,UAAU,SAAS,iBAAiB,KAAK,UAAU,MAAM,CAChE,QAAO;AAIX,QAAO"}
1
+ {"version":3,"file":"matchers.mjs","names":[],"sources":["../src/matchers.ts"],"sourcesContent":["import { isDeepStrictEqual } from \"node:util\";\n\nimport { expect } from \"vite-plus/test\";\n\nimport { parseNoteDocument } from \"./note/document\";\nimport type {\n NoteMatcherOptions,\n NoteFrontmatter,\n ObsidianClient,\n PluginHandle,\n SandboxApi,\n VaultApi,\n} from \"./core/types\";\n\ntype FileMatcherTarget = SandboxApi | VaultApi;\n\nexpect.extend({\n async toHaveActiveFile(target: ObsidianClient, targetPath: string) {\n const actual = await target.dev.activeFilePath();\n const pass = actual === targetPath;\n\n return {\n message: () =>\n pass\n ? `Expected active file not to be \"${targetPath}\"`\n : `Expected active file to be \"${targetPath}\", received ${JSON.stringify(actual)}`,\n pass,\n };\n },\n async toHaveCommand(target: ObsidianClient, commandId: string) {\n const pass = await target.command(commandId).exists();\n\n return {\n message: () =>\n pass\n ? `Expected Obsidian command not to exist: ${commandId}`\n : `Expected Obsidian command to exist: ${commandId}`,\n pass,\n };\n },\n async toHaveFile(target: FileMatcherTarget, targetPath: string) {\n const pass = await target.exists(targetPath);\n\n return {\n message: () =>\n pass\n ? `Expected vault path not to exist: ${targetPath}`\n : `Expected vault path to exist: ${targetPath}`,\n pass,\n };\n },\n async toHaveFileContaining(target: FileMatcherTarget, targetPath: string, needle: string) {\n const exists = await target.exists(targetPath);\n\n if (!exists) {\n return {\n message: () => `Expected vault path to exist: ${targetPath}`,\n pass: false,\n };\n }\n\n const content = await target.read(targetPath);\n const pass = content.includes(needle);\n\n return {\n message: () =>\n pass\n ? `Expected vault path \"${targetPath}\" not to contain \"${needle}\"`\n : `Expected vault path \"${targetPath}\" to contain \"${needle}\"`,\n pass,\n };\n },\n async toHaveFrontmatter(\n target: FileMatcherTarget,\n targetPath: string,\n expected: NoteFrontmatter,\n ) {\n const exists = await target.exists(targetPath);\n\n if (!exists) {\n return {\n message: () => `Expected vault path to exist: ${targetPath}`,\n pass: false,\n };\n }\n\n const actual = parseNoteDocument(await target.read(targetPath)).frontmatter;\n const pass = isDeepStrictEqual(actual, expected);\n\n return {\n message: () =>\n pass\n ? `Expected frontmatter for \"${targetPath}\" not to equal ${JSON.stringify(expected)}`\n : `Expected frontmatter for \"${targetPath}\" to equal ${JSON.stringify(\n expected,\n )}, received ${JSON.stringify(actual)}`,\n pass,\n };\n },\n async toHaveJsonFile(target: FileMatcherTarget, targetPath: string) {\n const exists = await target.exists(targetPath);\n\n if (!exists) {\n return {\n message: () => `Expected JSON file to exist: ${targetPath}`,\n pass: false,\n };\n }\n\n try {\n await target.json(targetPath).read();\n return {\n message: () => `Expected JSON file \"${targetPath}\" not to be valid JSON`,\n pass: true,\n };\n } catch (error) {\n return {\n message: () =>\n `Expected JSON file \"${targetPath}\" to be valid JSON, but parsing failed: ${\n error instanceof Error ? error.message : String(error)\n }`,\n pass: false,\n };\n }\n },\n async toHaveNote(target: FileMatcherTarget, targetPath: string, expected: NoteMatcherOptions) {\n let actual: ReturnType<typeof parseNoteDocument>;\n\n try {\n actual = parseNoteDocument(await target.read(targetPath));\n } catch (error) {\n return {\n message: () =>\n `Expected vault path \"${targetPath}\" to be readable as a note, but reading failed: ${\n error instanceof Error ? error.message : String(error)\n }`,\n pass: false,\n };\n }\n\n const matchesFrontmatter =\n expected.frontmatter === undefined ||\n isDeepStrictEqual(actual.frontmatter, expected.frontmatter);\n const matchesBody = expected.body === undefined || actual.body === expected.body;\n const matchesBodyIncludes =\n expected.bodyIncludes === undefined || actual.body.includes(expected.bodyIncludes);\n const pass = matchesFrontmatter && matchesBody && matchesBodyIncludes;\n\n return {\n message: () =>\n pass\n ? `Expected vault path \"${targetPath}\" not to match the expected note shape`\n : `Expected vault path \"${targetPath}\" to match the expected note shape, received ${JSON.stringify(\n actual,\n )}`,\n pass,\n };\n },\n async toHaveOpenTab(target: ObsidianClient, title: string, viewType?: string) {\n const tabs = await target.tabs();\n const pass = tabs.some(\n (tab) => tab.title === title && (viewType === undefined || tab.viewType === viewType),\n );\n\n return {\n message: () =>\n pass\n ? `Expected no open tab matching \"${title}\"${\n viewType ? ` with view type \"${viewType}\"` : \"\"\n }`\n : `Expected an open tab matching \"${title}\"${\n viewType ? ` with view type \"${viewType}\"` : \"\"\n }`,\n pass,\n };\n },\n async toHavePluginData(target: PluginHandle, expected: unknown) {\n const actual = await target.data().read();\n const pass = isDeepStrictEqual(actual, expected);\n\n return {\n message: () =>\n pass\n ? `Expected plugin data not to equal ${JSON.stringify(expected)}`\n : `Expected plugin data to equal ${JSON.stringify(expected)}, received ${JSON.stringify(\n actual,\n )}`,\n pass,\n };\n },\n async toHaveEditorTextContaining(target: ObsidianClient, needle: string) {\n const actual = await target.dev.editorText();\n const pass = typeof actual === \"string\" && actual.includes(needle);\n\n return {\n message: () =>\n pass\n ? `Expected editor text not to contain \"${needle}\"`\n : `Expected editor text to contain \"${needle}\", received ${JSON.stringify(actual)}`,\n pass,\n };\n },\n async toHaveWorkspaceNode(target: ObsidianClient, label: string) {\n const pass = hasWorkspaceNode(await target.workspace(), label);\n\n return {\n message: () =>\n pass\n ? `Expected workspace not to contain node \"${label}\"`\n : `Expected workspace to contain node \"${label}\"`,\n pass,\n };\n },\n});\n\ndeclare module \"vite-plus/test\" {\n interface Assertion<T = any> {\n toHaveActiveFile(path: string): Promise<T>;\n toHaveCommand(commandId: string): Promise<T>;\n toHaveEditorTextContaining(needle: string): Promise<T>;\n toHaveFile(path: string): Promise<T>;\n toHaveFileContaining(path: string, needle: string): Promise<T>;\n toHaveFrontmatter(path: string, expected: NoteFrontmatter): Promise<T>;\n toHaveJsonFile(path: string): Promise<T>;\n toHaveNote(path: string, expected: NoteMatcherOptions): Promise<T>;\n toHaveOpenTab(title: string, viewType?: string): Promise<T>;\n toHavePluginData(expected: unknown): Promise<T>;\n toHaveWorkspaceNode(label: string): Promise<T>;\n }\n\n interface AsymmetricMatchersContaining {\n toHaveActiveFile(path: string): void;\n toHaveCommand(commandId: string): void;\n toHaveEditorTextContaining(needle: string): void;\n toHaveFile(path: string): void;\n toHaveFileContaining(path: string, needle: string): void;\n toHaveFrontmatter(path: string, expected: NoteFrontmatter): void;\n toHaveJsonFile(path: string): void;\n toHaveNote(path: string, expected: NoteMatcherOptions): void;\n toHaveOpenTab(title: string, viewType?: string): void;\n toHavePluginData(expected: unknown): void;\n toHaveWorkspaceNode(label: string): void;\n }\n}\n\nexport {};\n\nfunction hasWorkspaceNode(\n nodes: Awaited<ReturnType<ObsidianClient[\"workspace\"]>>,\n label: string,\n): boolean {\n for (const node of nodes) {\n if (node.label === label || hasWorkspaceNode(node.children, label)) {\n return true;\n }\n }\n\n return false;\n}\n"],"mappings":"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"}