mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 20:44:47 +00:00
test: tighten remaining defined assertions
This commit is contained in:
@@ -149,12 +149,14 @@ describe("resolveEmbeddedAgentStreamFn", () => {
|
||||
|
||||
it("routes PI native OpenAI-compatible provider streams through boundary-aware transports", async () => {
|
||||
const nativeStreamFn = getApiProvider("openai-completions")?.streamSimple;
|
||||
expect(nativeStreamFn).toBeDefined();
|
||||
if (!nativeStreamFn) {
|
||||
throw new Error("expected native OpenAI-compatible stream function");
|
||||
}
|
||||
const innerStreamFn = vi.fn(async (_model, _context, options) => options);
|
||||
overrideBoundaryAwareStreamFnOnce(innerStreamFn as never);
|
||||
|
||||
const streamFn = resolveEmbeddedAgentStreamFn({
|
||||
currentStreamFn: nativeStreamFn as StreamFn,
|
||||
currentStreamFn: nativeStreamFn,
|
||||
shouldUseWebSocketTransport: false,
|
||||
sessionId: "session-1",
|
||||
model: {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { parseMd } from '../parse.js';
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { parseMd } from "../parse.js";
|
||||
|
||||
describe('parseMd — frontmatter', () => {
|
||||
it('parses simple frontmatter', () => {
|
||||
describe("parseMd — frontmatter", () => {
|
||||
it("parses simple frontmatter", () => {
|
||||
const raw = `---
|
||||
name: github
|
||||
description: gh CLI for issues, PRs, runs
|
||||
@@ -13,20 +13,20 @@ Body text.
|
||||
const { ast, diagnostics } = parseMd(raw);
|
||||
expect(diagnostics).toEqual([]);
|
||||
expect(ast.frontmatter).toEqual([
|
||||
{ key: 'name', value: 'github', line: 2 },
|
||||
{ key: 'description', value: 'gh CLI for issues, PRs, runs', line: 3 },
|
||||
{ key: "name", value: "github", line: 2 },
|
||||
{ key: "description", value: "gh CLI for issues, PRs, runs", line: 3 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('handles no frontmatter', () => {
|
||||
it("handles no frontmatter", () => {
|
||||
const raw = `## First section\n\nContent.\n`;
|
||||
const { ast } = parseMd(raw);
|
||||
expect(ast.frontmatter).toEqual([]);
|
||||
expect(ast.preamble).toBe('');
|
||||
expect(ast.preamble).toBe("");
|
||||
expect(ast.blocks.length).toBe(1);
|
||||
});
|
||||
|
||||
it('emits diagnostic for unclosed frontmatter', () => {
|
||||
it("emits diagnostic for unclosed frontmatter", () => {
|
||||
const raw = `---
|
||||
name: github
|
||||
description: never closes
|
||||
@@ -35,24 +35,24 @@ Body.
|
||||
`;
|
||||
const { diagnostics } = parseMd(raw);
|
||||
expect(diagnostics).toContainEqual(
|
||||
expect.objectContaining({ code: 'OC_FRONTMATTER_UNCLOSED' }),
|
||||
expect.objectContaining({ code: "OC_FRONTMATTER_UNCLOSED" }),
|
||||
);
|
||||
});
|
||||
|
||||
it('strips quotes from values', () => {
|
||||
it("strips quotes from values", () => {
|
||||
const raw = `---
|
||||
title: "Hello world"
|
||||
hint: 'quoted'
|
||||
---
|
||||
`;
|
||||
const { ast } = parseMd(raw);
|
||||
expect(ast.frontmatter[0]?.value).toBe('Hello world');
|
||||
expect(ast.frontmatter[1]?.value).toBe('quoted');
|
||||
expect(ast.frontmatter[0]?.value).toBe("Hello world");
|
||||
expect(ast.frontmatter[1]?.value).toBe("quoted");
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseMd — H2 blocks', () => {
|
||||
it('splits sections', () => {
|
||||
describe("parseMd — H2 blocks", () => {
|
||||
it("splits sections", () => {
|
||||
const raw = `Preamble text.
|
||||
|
||||
## First
|
||||
@@ -64,14 +64,14 @@ Body of first.
|
||||
Body of second.
|
||||
`;
|
||||
const { ast } = parseMd(raw);
|
||||
expect(ast.preamble.trim()).toBe('Preamble text.');
|
||||
expect(ast.preamble.trim()).toBe("Preamble text.");
|
||||
expect(ast.blocks.length).toBe(2);
|
||||
expect(ast.blocks[0]?.heading).toBe('First');
|
||||
expect(ast.blocks[0]?.slug).toBe('first');
|
||||
expect(ast.blocks[1]?.heading).toBe('Second');
|
||||
expect(ast.blocks[0]?.heading).toBe("First");
|
||||
expect(ast.blocks[0]?.slug).toBe("first");
|
||||
expect(ast.blocks[1]?.heading).toBe("Second");
|
||||
});
|
||||
|
||||
it('preserves line numbers (1-based)', () => {
|
||||
it("preserves line numbers (1-based)", () => {
|
||||
const raw = `Line 1
|
||||
## Heading at line 2
|
||||
Line 3
|
||||
@@ -80,7 +80,7 @@ Line 3
|
||||
expect(ast.blocks[0]?.line).toBe(2);
|
||||
});
|
||||
|
||||
it('does NOT split on `## ` inside fenced code blocks', () => {
|
||||
it("does NOT split on `## ` inside fenced code blocks", () => {
|
||||
const raw = `## Real section
|
||||
|
||||
\`\`\`md
|
||||
@@ -91,12 +91,12 @@ content
|
||||
## Another section
|
||||
`;
|
||||
const { ast } = parseMd(raw);
|
||||
expect(ast.blocks.map((b) => b.heading)).toEqual(['Real section', 'Another section']);
|
||||
expect(ast.blocks.map((b) => b.heading)).toEqual(["Real section", "Another section"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseMd — items', () => {
|
||||
it('extracts plain bullet items', () => {
|
||||
describe("parseMd — items", () => {
|
||||
it("extracts plain bullet items", () => {
|
||||
const raw = `## Boundaries
|
||||
|
||||
- never write to /etc
|
||||
@@ -104,23 +104,23 @@ describe('parseMd — items', () => {
|
||||
`;
|
||||
const { ast } = parseMd(raw);
|
||||
expect(ast.blocks[0]?.items.length).toBe(2);
|
||||
expect(ast.blocks[0]?.items[0]?.text).toBe('never write to /etc');
|
||||
expect(ast.blocks[0]?.items[0]?.text).toBe("never write to /etc");
|
||||
expect(ast.blocks[0]?.items[0]?.kv).toBeUndefined();
|
||||
});
|
||||
|
||||
it('extracts kv items', () => {
|
||||
it("extracts kv items", () => {
|
||||
const raw = `## Tools
|
||||
|
||||
- gh: GitHub CLI
|
||||
- curl: HTTP client
|
||||
`;
|
||||
const { ast } = parseMd(raw);
|
||||
expect(ast.blocks[0]?.items[0]?.kv).toEqual({ key: 'gh', value: 'GitHub CLI' });
|
||||
expect(ast.blocks[0]?.items[0]?.slug).toBe('gh');
|
||||
expect(ast.blocks[0]?.items[1]?.kv).toEqual({ key: 'curl', value: 'HTTP client' });
|
||||
expect(ast.blocks[0]?.items[0]?.kv).toEqual({ key: "gh", value: "GitHub CLI" });
|
||||
expect(ast.blocks[0]?.items[0]?.slug).toBe("gh");
|
||||
expect(ast.blocks[0]?.items[1]?.kv).toEqual({ key: "curl", value: "HTTP client" });
|
||||
});
|
||||
|
||||
it('does NOT extract bullets inside fenced code', () => {
|
||||
it("does NOT extract bullets inside fenced code", () => {
|
||||
const raw = `## Section
|
||||
|
||||
\`\`\`
|
||||
@@ -131,12 +131,12 @@ describe('parseMd — items', () => {
|
||||
`;
|
||||
const { ast } = parseMd(raw);
|
||||
expect(ast.blocks[0]?.items.length).toBe(1);
|
||||
expect(ast.blocks[0]?.items[0]?.text).toBe('real bullet');
|
||||
expect(ast.blocks[0]?.items[0]?.text).toBe("real bullet");
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseMd — tables', () => {
|
||||
it('extracts a simple table', () => {
|
||||
describe("parseMd — tables", () => {
|
||||
it("extracts a simple table", () => {
|
||||
const raw = `## Tool Guidance
|
||||
|
||||
| tool | guidance |
|
||||
@@ -146,15 +146,17 @@ describe('parseMd — tables', () => {
|
||||
`;
|
||||
const { ast } = parseMd(raw);
|
||||
const table = ast.blocks[0]?.tables[0];
|
||||
expect(table).toBeDefined();
|
||||
expect(table?.headers).toEqual(['tool', 'guidance']);
|
||||
expect(table?.rows.length).toBe(2);
|
||||
expect(table?.rows[0]).toEqual(['gh', 'use for GitHub']);
|
||||
if (!table) {
|
||||
throw new Error("expected parsed markdown table");
|
||||
}
|
||||
expect(table.headers).toEqual(["tool", "guidance"]);
|
||||
expect(table.rows.length).toBe(2);
|
||||
expect(table.rows[0]).toEqual(["gh", "use for GitHub"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseMd — code blocks', () => {
|
||||
it('extracts a fenced code block', () => {
|
||||
describe("parseMd — code blocks", () => {
|
||||
it("extracts a fenced code block", () => {
|
||||
const raw = `## Examples
|
||||
|
||||
\`\`\`ts
|
||||
@@ -163,12 +165,12 @@ const x = 1;
|
||||
`;
|
||||
const { ast } = parseMd(raw);
|
||||
expect(ast.blocks[0]?.codeBlocks[0]).toMatchObject({
|
||||
lang: 'ts',
|
||||
text: 'const x = 1;',
|
||||
lang: "ts",
|
||||
text: "const x = 1;",
|
||||
});
|
||||
});
|
||||
|
||||
it('handles unlanguaged fences', () => {
|
||||
it("handles unlanguaged fences", () => {
|
||||
const raw = `## Block
|
||||
|
||||
\`\`\`
|
||||
@@ -180,24 +182,24 @@ plain text
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseMd — byte-fidelity', () => {
|
||||
it('preserves raw on the AST', () => {
|
||||
describe("parseMd — byte-fidelity", () => {
|
||||
it("preserves raw on the AST", () => {
|
||||
const raw = `---\nname: x\n---\n\n## Sec\n\n- a\n- b\n`;
|
||||
const { ast } = parseMd(raw);
|
||||
expect(ast.raw).toBe(raw);
|
||||
});
|
||||
|
||||
it('preserves BOM in raw but ignores it for parsing', () => {
|
||||
const raw = '## Heading\n';
|
||||
it("preserves BOM in raw but ignores it for parsing", () => {
|
||||
const raw = "## Heading\n";
|
||||
const { ast } = parseMd(raw);
|
||||
expect(ast.raw).toBe(raw);
|
||||
expect(ast.blocks[0]?.heading).toBe('Heading');
|
||||
expect(ast.blocks[0]?.heading).toBe("Heading");
|
||||
});
|
||||
|
||||
it('handles CRLF line endings', () => {
|
||||
const raw = '## Heading\r\n\r\n- item\r\n';
|
||||
it("handles CRLF line endings", () => {
|
||||
const raw = "## Heading\r\n\r\n- item\r\n";
|
||||
const { ast } = parseMd(raw);
|
||||
expect(ast.blocks[0]?.heading).toBe('Heading');
|
||||
expect(ast.blocks[0]?.items[0]?.text).toBe('item');
|
||||
expect(ast.blocks[0]?.heading).toBe("Heading");
|
||||
expect(ast.blocks[0]?.items[0]?.text).toBe("item");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,228 +5,220 @@
|
||||
* dispatches via `ast.kind` and coerces value strings based on AST
|
||||
* shape at the path location.
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { emitJsonc } from '../jsonc/emit.js';
|
||||
import { parseJsonc } from '../jsonc/parse.js';
|
||||
import { emitJsonl } from '../jsonl/emit.js';
|
||||
import { parseJsonl } from '../jsonl/parse.js';
|
||||
import { emitMd } from '../emit.js';
|
||||
import { parseMd } from '../parse.js';
|
||||
import { parseOcPath } from '../oc-path.js';
|
||||
import {
|
||||
detectInsertion,
|
||||
resolveOcPath,
|
||||
setOcPath,
|
||||
} from '../universal.js';
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { emitMd } from "../emit.js";
|
||||
import { emitJsonc } from "../jsonc/emit.js";
|
||||
import { parseJsonc } from "../jsonc/parse.js";
|
||||
import { emitJsonl } from "../jsonl/emit.js";
|
||||
import { parseJsonl } from "../jsonl/parse.js";
|
||||
import { parseOcPath } from "../oc-path.js";
|
||||
import { parseMd } from "../parse.js";
|
||||
import { detectInsertion, resolveOcPath, setOcPath } from "../universal.js";
|
||||
|
||||
// ---------- detectInsertion ------------------------------------------------
|
||||
|
||||
describe('detectInsertion', () => {
|
||||
it('returns null for plain paths', () => {
|
||||
expect(detectInsertion(parseOcPath('oc://X.md/section/item/field'))).toBeNull();
|
||||
describe("detectInsertion", () => {
|
||||
it("returns null for plain paths", () => {
|
||||
expect(detectInsertion(parseOcPath("oc://X.md/section/item/field"))).toBeNull();
|
||||
});
|
||||
|
||||
it('detects bare `+` end-insertion at section', () => {
|
||||
const info = detectInsertion(parseOcPath('oc://X.md/tools/+'));
|
||||
expect(info?.marker).toBe('+');
|
||||
expect(info?.parentPath.section).toBe('tools');
|
||||
it("detects bare `+` end-insertion at section", () => {
|
||||
const info = detectInsertion(parseOcPath("oc://X.md/tools/+"));
|
||||
expect(info?.marker).toBe("+");
|
||||
expect(info?.parentPath.section).toBe("tools");
|
||||
expect(info?.parentPath.item).toBeUndefined();
|
||||
});
|
||||
|
||||
it('detects `+key` keyed insertion', () => {
|
||||
const info = detectInsertion(parseOcPath('oc://config/plugins/+gitlab'));
|
||||
expect(info?.marker).toEqual({ kind: 'keyed', key: 'gitlab' });
|
||||
it("detects `+key` keyed insertion", () => {
|
||||
const info = detectInsertion(parseOcPath("oc://config/plugins/+gitlab"));
|
||||
expect(info?.marker).toEqual({ kind: "keyed", key: "gitlab" });
|
||||
});
|
||||
|
||||
it('detects `+nnn` indexed insertion', () => {
|
||||
const info = detectInsertion(parseOcPath('oc://config/items/+2'));
|
||||
expect(info?.marker).toEqual({ kind: 'indexed', index: 2 });
|
||||
it("detects `+nnn` indexed insertion", () => {
|
||||
const info = detectInsertion(parseOcPath("oc://config/items/+2"));
|
||||
expect(info?.marker).toEqual({ kind: "indexed", index: 2 });
|
||||
});
|
||||
|
||||
it('detects file-root insertion', () => {
|
||||
const info = detectInsertion(parseOcPath('oc://session.jsonl/+'));
|
||||
expect(info?.marker).toBe('+');
|
||||
it("detects file-root insertion", () => {
|
||||
const info = detectInsertion(parseOcPath("oc://session.jsonl/+"));
|
||||
expect(info?.marker).toBe("+");
|
||||
expect(info?.parentPath.section).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------- resolveOcPath — universal across kinds -------------------------
|
||||
|
||||
describe('resolveOcPath — md AST', () => {
|
||||
const md = parseMd(
|
||||
'---\nname: github\n---\n\n## Boundaries\n\n- enabled: true\n',
|
||||
).ast;
|
||||
describe("resolveOcPath — md AST", () => {
|
||||
const md = parseMd("---\nname: github\n---\n\n## Boundaries\n\n- enabled: true\n").ast;
|
||||
|
||||
it('returns leaf with valueText for frontmatter entry', () => {
|
||||
const m = resolveOcPath(md, parseOcPath('oc://X.md/[frontmatter]/name'));
|
||||
expect(m).toMatchObject({ kind: 'leaf', valueText: 'github', leafType: 'string' });
|
||||
it("returns leaf with valueText for frontmatter entry", () => {
|
||||
const m = resolveOcPath(md, parseOcPath("oc://X.md/[frontmatter]/name"));
|
||||
expect(m).toMatchObject({ kind: "leaf", valueText: "github", leafType: "string" });
|
||||
});
|
||||
|
||||
it('returns leaf for item-field', () => {
|
||||
const m = resolveOcPath(
|
||||
md,
|
||||
parseOcPath('oc://X.md/boundaries/enabled/enabled'),
|
||||
);
|
||||
expect(m).toMatchObject({ kind: 'leaf', valueText: 'true', leafType: 'string' });
|
||||
it("returns leaf for item-field", () => {
|
||||
const m = resolveOcPath(md, parseOcPath("oc://X.md/boundaries/enabled/enabled"));
|
||||
expect(m).toMatchObject({ kind: "leaf", valueText: "true", leafType: "string" });
|
||||
});
|
||||
|
||||
it('returns node for block', () => {
|
||||
const m = resolveOcPath(md, parseOcPath('oc://X.md/boundaries'));
|
||||
expect(m).toMatchObject({ kind: 'node', descriptor: 'md-block' });
|
||||
it("returns node for block", () => {
|
||||
const m = resolveOcPath(md, parseOcPath("oc://X.md/boundaries"));
|
||||
expect(m).toMatchObject({ kind: "node", descriptor: "md-block" });
|
||||
});
|
||||
|
||||
it('returns root for file-only path', () => {
|
||||
const m = resolveOcPath(md, parseOcPath('oc://X.md'));
|
||||
expect(m?.kind).toBe('root');
|
||||
it("returns root for file-only path", () => {
|
||||
const m = resolveOcPath(md, parseOcPath("oc://X.md"));
|
||||
expect(m?.kind).toBe("root");
|
||||
});
|
||||
|
||||
it('returns null for unresolved', () => {
|
||||
expect(resolveOcPath(md, parseOcPath('oc://X.md/missing'))).toBeNull();
|
||||
it("returns null for unresolved", () => {
|
||||
expect(resolveOcPath(md, parseOcPath("oc://X.md/missing"))).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveOcPath — jsonc AST', () => {
|
||||
describe("resolveOcPath — jsonc AST", () => {
|
||||
const ast = parseJsonc('{ "k": 42, "s": "x", "b": true, "n": null, "arr": [1,2,3] }').ast;
|
||||
|
||||
it('returns leaf:number for numeric value', () => {
|
||||
const m = resolveOcPath(ast, parseOcPath('oc://config/k'));
|
||||
expect(m).toMatchObject({ kind: 'leaf', valueText: '42', leafType: 'number' });
|
||||
it("returns leaf:number for numeric value", () => {
|
||||
const m = resolveOcPath(ast, parseOcPath("oc://config/k"));
|
||||
expect(m).toMatchObject({ kind: "leaf", valueText: "42", leafType: "number" });
|
||||
});
|
||||
|
||||
it('returns leaf:string for string value', () => {
|
||||
const m = resolveOcPath(ast, parseOcPath('oc://config/s'));
|
||||
expect(m).toMatchObject({ kind: 'leaf', valueText: 'x', leafType: 'string' });
|
||||
it("returns leaf:string for string value", () => {
|
||||
const m = resolveOcPath(ast, parseOcPath("oc://config/s"));
|
||||
expect(m).toMatchObject({ kind: "leaf", valueText: "x", leafType: "string" });
|
||||
});
|
||||
|
||||
it('returns leaf:boolean for bool value', () => {
|
||||
const m = resolveOcPath(ast, parseOcPath('oc://config/b'));
|
||||
expect(m).toMatchObject({ kind: 'leaf', valueText: 'true', leafType: 'boolean' });
|
||||
it("returns leaf:boolean for bool value", () => {
|
||||
const m = resolveOcPath(ast, parseOcPath("oc://config/b"));
|
||||
expect(m).toMatchObject({ kind: "leaf", valueText: "true", leafType: "boolean" });
|
||||
});
|
||||
|
||||
it('returns leaf:null for null value', () => {
|
||||
const m = resolveOcPath(ast, parseOcPath('oc://config/n'));
|
||||
expect(m).toMatchObject({ kind: 'leaf', valueText: 'null', leafType: 'null' });
|
||||
it("returns leaf:null for null value", () => {
|
||||
const m = resolveOcPath(ast, parseOcPath("oc://config/n"));
|
||||
expect(m).toMatchObject({ kind: "leaf", valueText: "null", leafType: "null" });
|
||||
});
|
||||
|
||||
it('returns node:jsonc-array for array value', () => {
|
||||
const m = resolveOcPath(ast, parseOcPath('oc://config/arr'));
|
||||
expect(m).toMatchObject({ kind: 'node', descriptor: 'jsonc-array' });
|
||||
it("returns node:jsonc-array for array value", () => {
|
||||
const m = resolveOcPath(ast, parseOcPath("oc://config/arr"));
|
||||
expect(m).toMatchObject({ kind: "node", descriptor: "jsonc-array" });
|
||||
});
|
||||
|
||||
it('returns leaf at array index', () => {
|
||||
const m = resolveOcPath(ast, parseOcPath('oc://config/arr.1'));
|
||||
expect(m).toMatchObject({ kind: 'leaf', valueText: '2', leafType: 'number' });
|
||||
it("returns leaf at array index", () => {
|
||||
const m = resolveOcPath(ast, parseOcPath("oc://config/arr.1"));
|
||||
expect(m).toMatchObject({ kind: "leaf", valueText: "2", leafType: "number" });
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveOcPath — jsonl AST', () => {
|
||||
describe("resolveOcPath — jsonl AST", () => {
|
||||
const ast = parseJsonl('{"event":"start","n":1}\n{"event":"step","n":2}\n').ast;
|
||||
|
||||
it('returns node:jsonl-line for line address', () => {
|
||||
const m = resolveOcPath(ast, parseOcPath('oc://log/L1'));
|
||||
expect(m).toMatchObject({ kind: 'node', descriptor: 'jsonl-line' });
|
||||
it("returns node:jsonl-line for line address", () => {
|
||||
const m = resolveOcPath(ast, parseOcPath("oc://log/L1"));
|
||||
expect(m).toMatchObject({ kind: "node", descriptor: "jsonl-line" });
|
||||
});
|
||||
|
||||
it('returns leaf for field on line', () => {
|
||||
const m = resolveOcPath(ast, parseOcPath('oc://log/L2/event'));
|
||||
expect(m).toMatchObject({ kind: 'leaf', valueText: 'step', leafType: 'string' });
|
||||
it("returns leaf for field on line", () => {
|
||||
const m = resolveOcPath(ast, parseOcPath("oc://log/L2/event"));
|
||||
expect(m).toMatchObject({ kind: "leaf", valueText: "step", leafType: "string" });
|
||||
});
|
||||
|
||||
it('returns leaf:number for $last/n', () => {
|
||||
const m = resolveOcPath(ast, parseOcPath('oc://log/$last/n'));
|
||||
expect(m).toMatchObject({ kind: 'leaf', valueText: '2', leafType: 'number' });
|
||||
it("returns leaf:number for $last/n", () => {
|
||||
const m = resolveOcPath(ast, parseOcPath("oc://log/$last/n"));
|
||||
expect(m).toMatchObject({ kind: "leaf", valueText: "2", leafType: "number" });
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveOcPath — insertion-point detection', () => {
|
||||
it('returns insertion-point for md section append', () => {
|
||||
const md = parseMd('## Tools\n').ast;
|
||||
const m = resolveOcPath(md, parseOcPath('oc://X.md/tools/+'));
|
||||
expect(m).toMatchObject({ kind: 'insertion-point', container: 'md-section' });
|
||||
describe("resolveOcPath — insertion-point detection", () => {
|
||||
it("returns insertion-point for md section append", () => {
|
||||
const md = parseMd("## Tools\n").ast;
|
||||
const m = resolveOcPath(md, parseOcPath("oc://X.md/tools/+"));
|
||||
expect(m).toMatchObject({ kind: "insertion-point", container: "md-section" });
|
||||
});
|
||||
|
||||
it('returns insertion-point for md file-level', () => {
|
||||
const md = parseMd('## Tools\n').ast;
|
||||
const m = resolveOcPath(md, parseOcPath('oc://X.md/+'));
|
||||
expect(m).toMatchObject({ kind: 'insertion-point', container: 'md-file' });
|
||||
it("returns insertion-point for md file-level", () => {
|
||||
const md = parseMd("## Tools\n").ast;
|
||||
const m = resolveOcPath(md, parseOcPath("oc://X.md/+"));
|
||||
expect(m).toMatchObject({ kind: "insertion-point", container: "md-file" });
|
||||
});
|
||||
|
||||
it('returns insertion-point for md frontmatter +key', () => {
|
||||
const md = parseMd('---\nname: x\n---\n').ast;
|
||||
const m = resolveOcPath(
|
||||
md,
|
||||
parseOcPath('oc://X.md/[frontmatter]/+description'),
|
||||
);
|
||||
expect(m).toMatchObject({ kind: 'insertion-point', container: 'md-frontmatter' });
|
||||
it("returns insertion-point for md frontmatter +key", () => {
|
||||
const md = parseMd("---\nname: x\n---\n").ast;
|
||||
const m = resolveOcPath(md, parseOcPath("oc://X.md/[frontmatter]/+description"));
|
||||
expect(m).toMatchObject({ kind: "insertion-point", container: "md-frontmatter" });
|
||||
});
|
||||
|
||||
it('returns insertion-point for jsonc array +', () => {
|
||||
it("returns insertion-point for jsonc array +", () => {
|
||||
const ast = parseJsonc('{ "items": [1,2,3] }').ast;
|
||||
const m = resolveOcPath(ast, parseOcPath('oc://config/items/+'));
|
||||
expect(m).toMatchObject({ kind: 'insertion-point', container: 'jsonc-array' });
|
||||
const m = resolveOcPath(ast, parseOcPath("oc://config/items/+"));
|
||||
expect(m).toMatchObject({ kind: "insertion-point", container: "jsonc-array" });
|
||||
});
|
||||
|
||||
it('returns insertion-point for jsonc object +key', () => {
|
||||
it("returns insertion-point for jsonc object +key", () => {
|
||||
const ast = parseJsonc('{ "plugins": {} }').ast;
|
||||
const m = resolveOcPath(ast, parseOcPath('oc://config/plugins/+gitlab'));
|
||||
expect(m).toMatchObject({ kind: 'insertion-point', container: 'jsonc-object' });
|
||||
const m = resolveOcPath(ast, parseOcPath("oc://config/plugins/+gitlab"));
|
||||
expect(m).toMatchObject({ kind: "insertion-point", container: "jsonc-object" });
|
||||
});
|
||||
|
||||
it('returns insertion-point for jsonl file-root +', () => {
|
||||
const ast = parseJsonl('').ast;
|
||||
const m = resolveOcPath(ast, parseOcPath('oc://log/+'));
|
||||
expect(m).toMatchObject({ kind: 'insertion-point', container: 'jsonl-file' });
|
||||
it("returns insertion-point for jsonl file-root +", () => {
|
||||
const ast = parseJsonl("").ast;
|
||||
const m = resolveOcPath(ast, parseOcPath("oc://log/+"));
|
||||
expect(m).toMatchObject({ kind: "insertion-point", container: "jsonl-file" });
|
||||
});
|
||||
|
||||
it('returns null when insertion target is not a container', () => {
|
||||
it("returns null when insertion target is not a container", () => {
|
||||
const ast = parseJsonc('{ "k": 42 }').ast;
|
||||
const m = resolveOcPath(ast, parseOcPath('oc://config/k/+'));
|
||||
const m = resolveOcPath(ast, parseOcPath("oc://config/k/+"));
|
||||
expect(m).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------- setOcPath — leaf assignment ------------------------------------
|
||||
|
||||
describe('setOcPath — md leaf', () => {
|
||||
it('replaces frontmatter value', () => {
|
||||
const md = parseMd('---\nname: old\n---\n').ast;
|
||||
const r = setOcPath(md, parseOcPath('oc://X.md/[frontmatter]/name'), 'new');
|
||||
describe("setOcPath — md leaf", () => {
|
||||
it("replaces frontmatter value", () => {
|
||||
const md = parseMd("---\nname: old\n---\n").ast;
|
||||
const r = setOcPath(md, parseOcPath("oc://X.md/[frontmatter]/name"), "new");
|
||||
expect(r.ok).toBe(true);
|
||||
if (r.ok) {expect(r.ast.kind === 'md' && r.ast.frontmatter[0]?.value).toBe('new');}
|
||||
if (r.ok) {
|
||||
expect(r.ast.kind === "md" && r.ast.frontmatter[0]?.value).toBe("new");
|
||||
}
|
||||
});
|
||||
|
||||
it('replaces item kv value', () => {
|
||||
const md = parseMd('## Boundaries\n\n- timeout: 5\n').ast;
|
||||
const r = setOcPath(md, parseOcPath('oc://X.md/boundaries/timeout/timeout'), '60');
|
||||
it("replaces item kv value", () => {
|
||||
const md = parseMd("## Boundaries\n\n- timeout: 5\n").ast;
|
||||
const r = setOcPath(md, parseOcPath("oc://X.md/boundaries/timeout/timeout"), "60");
|
||||
expect(r.ok).toBe(true);
|
||||
if (r.ok) {
|
||||
const out = emitMd(r.ast as Parameters<typeof emitMd>[0]);
|
||||
expect(out).toContain('- timeout: 60');
|
||||
expect(out).toContain("- timeout: 60");
|
||||
}
|
||||
});
|
||||
|
||||
it('returns unresolved for missing path', () => {
|
||||
const md = parseMd('').ast;
|
||||
const r = setOcPath(md, parseOcPath('oc://X.md/missing/x/x'), 'v');
|
||||
it("returns unresolved for missing path", () => {
|
||||
const md = parseMd("").ast;
|
||||
const r = setOcPath(md, parseOcPath("oc://X.md/missing/x/x"), "v");
|
||||
expect(r.ok).toBe(false);
|
||||
if (!r.ok) {expect(r.reason).toBe('unresolved');}
|
||||
if (!r.ok) {
|
||||
expect(r.reason).toBe("unresolved");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('setOcPath — jsonc leaf with coercion', () => {
|
||||
it('replaces string leaf with string value', () => {
|
||||
describe("setOcPath — jsonc leaf with coercion", () => {
|
||||
it("replaces string leaf with string value", () => {
|
||||
const ast = parseJsonc('{ "k": "old" }').ast;
|
||||
const r = setOcPath(ast, parseOcPath('oc://config/k'), 'new');
|
||||
const r = setOcPath(ast, parseOcPath("oc://config/k"), "new");
|
||||
expect(r.ok).toBe(true);
|
||||
if (r.ok) {
|
||||
const ast2 = r.ast as Parameters<typeof emitJsonc>[0];
|
||||
expect(JSON.parse(emitJsonc(ast2))).toEqual({ k: 'new' });
|
||||
expect(JSON.parse(emitJsonc(ast2))).toEqual({ k: "new" });
|
||||
}
|
||||
});
|
||||
|
||||
it('coerces value to number when leaf was number', () => {
|
||||
it("coerces value to number when leaf was number", () => {
|
||||
const ast = parseJsonc('{ "k": 1 }').ast;
|
||||
const r = setOcPath(ast, parseOcPath('oc://config/k'), '42');
|
||||
const r = setOcPath(ast, parseOcPath("oc://config/k"), "42");
|
||||
expect(r.ok).toBe(true);
|
||||
if (r.ok) {
|
||||
const ast2 = r.ast as Parameters<typeof emitJsonc>[0];
|
||||
@@ -236,7 +228,7 @@ describe('setOcPath — jsonc leaf with coercion', () => {
|
||||
|
||||
it('coerces "true"/"false" when leaf was boolean', () => {
|
||||
const ast = parseJsonc('{ "k": true }').ast;
|
||||
const r = setOcPath(ast, parseOcPath('oc://config/k'), 'false');
|
||||
const r = setOcPath(ast, parseOcPath("oc://config/k"), "false");
|
||||
expect(r.ok).toBe(true);
|
||||
if (r.ok) {
|
||||
const ast2 = r.ast as Parameters<typeof emitJsonc>[0];
|
||||
@@ -244,101 +236,109 @@ describe('setOcPath — jsonc leaf with coercion', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects non-numeric string for number leaf', () => {
|
||||
it("rejects non-numeric string for number leaf", () => {
|
||||
const ast = parseJsonc('{ "k": 1 }').ast;
|
||||
const r = setOcPath(ast, parseOcPath('oc://config/k'), 'not-a-number');
|
||||
const r = setOcPath(ast, parseOcPath("oc://config/k"), "not-a-number");
|
||||
expect(r.ok).toBe(false);
|
||||
if (!r.ok) {expect(r.reason).toBe('parse-error');}
|
||||
if (!r.ok) {
|
||||
expect(r.reason).toBe("parse-error");
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects non-bool string for boolean leaf', () => {
|
||||
it("rejects non-bool string for boolean leaf", () => {
|
||||
const ast = parseJsonc('{ "k": true }').ast;
|
||||
const r = setOcPath(ast, parseOcPath('oc://config/k'), 'maybe');
|
||||
const r = setOcPath(ast, parseOcPath("oc://config/k"), "maybe");
|
||||
expect(r.ok).toBe(false);
|
||||
if (!r.ok) {expect(r.reason).toBe('parse-error');}
|
||||
if (!r.ok) {
|
||||
expect(r.reason).toBe("parse-error");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('setOcPath — jsonl leaf', () => {
|
||||
it('replaces field on a value line with coercion', () => {
|
||||
describe("setOcPath — jsonl leaf", () => {
|
||||
it("replaces field on a value line with coercion", () => {
|
||||
const ast = parseJsonl('{"event":"start","n":1}\n').ast;
|
||||
const r = setOcPath(ast, parseOcPath('oc://log/L1/n'), '42');
|
||||
const r = setOcPath(ast, parseOcPath("oc://log/L1/n"), "42");
|
||||
expect(r.ok).toBe(true);
|
||||
if (r.ok) {
|
||||
const out = emitJsonl(r.ast as Parameters<typeof emitJsonl>[0]);
|
||||
expect(JSON.parse(out.split('\n')[0])).toEqual({ event: 'start', n: 42 });
|
||||
expect(JSON.parse(out.split("\n")[0])).toEqual({ event: "start", n: 42 });
|
||||
}
|
||||
});
|
||||
|
||||
it('replaces whole line via JSON value', () => {
|
||||
it("replaces whole line via JSON value", () => {
|
||||
const ast = parseJsonl('{"event":"start"}\n').ast;
|
||||
const r = setOcPath(ast, parseOcPath('oc://log/L1'), '{"event":"replaced"}');
|
||||
const r = setOcPath(ast, parseOcPath("oc://log/L1"), '{"event":"replaced"}');
|
||||
expect(r.ok).toBe(true);
|
||||
if (r.ok) {
|
||||
const out = emitJsonl(r.ast as Parameters<typeof emitJsonl>[0]);
|
||||
expect(JSON.parse(out.split('\n')[0])).toEqual({ event: 'replaced' });
|
||||
expect(JSON.parse(out.split("\n")[0])).toEqual({ event: "replaced" });
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects malformed JSON for whole-line replacement', () => {
|
||||
it("rejects malformed JSON for whole-line replacement", () => {
|
||||
const ast = parseJsonl('{"event":"start"}\n').ast;
|
||||
const r = setOcPath(ast, parseOcPath('oc://log/L1'), 'not json');
|
||||
const r = setOcPath(ast, parseOcPath("oc://log/L1"), "not json");
|
||||
expect(r.ok).toBe(false);
|
||||
if (!r.ok) {expect(r.reason).toBe('parse-error');}
|
||||
if (!r.ok) {
|
||||
expect(r.reason).toBe("parse-error");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ---------- setOcPath — insertion ------------------------------------------
|
||||
|
||||
describe('setOcPath — md insertion', () => {
|
||||
it('appends item to section with `+`', () => {
|
||||
const md = parseMd('## Tools\n\n- gh: GitHub CLI\n').ast;
|
||||
const r = setOcPath(md, parseOcPath('oc://X.md/tools/+'), 'docker: container CLI');
|
||||
describe("setOcPath — md insertion", () => {
|
||||
it("appends item to section with `+`", () => {
|
||||
const md = parseMd("## Tools\n\n- gh: GitHub CLI\n").ast;
|
||||
const r = setOcPath(md, parseOcPath("oc://X.md/tools/+"), "docker: container CLI");
|
||||
expect(r.ok).toBe(true);
|
||||
if (r.ok) {
|
||||
const out = emitMd(r.ast as Parameters<typeof emitMd>[0]);
|
||||
expect(out).toContain('- gh: GitHub CLI');
|
||||
expect(out).toContain('- docker: container CLI');
|
||||
expect(out).toContain("- gh: GitHub CLI");
|
||||
expect(out).toContain("- docker: container CLI");
|
||||
}
|
||||
});
|
||||
|
||||
it('appends new section at file root with `+`', () => {
|
||||
const md = parseMd('## Existing\n').ast;
|
||||
const r = setOcPath(md, parseOcPath('oc://X.md/+'), 'New Section');
|
||||
it("appends new section at file root with `+`", () => {
|
||||
const md = parseMd("## Existing\n").ast;
|
||||
const r = setOcPath(md, parseOcPath("oc://X.md/+"), "New Section");
|
||||
expect(r.ok).toBe(true);
|
||||
if (r.ok) {
|
||||
const out = emitMd(r.ast as Parameters<typeof emitMd>[0]);
|
||||
expect(out).toContain('## Existing');
|
||||
expect(out).toContain('## New Section');
|
||||
expect(out).toContain("## Existing");
|
||||
expect(out).toContain("## New Section");
|
||||
}
|
||||
});
|
||||
|
||||
it('adds new frontmatter key with +key', () => {
|
||||
const md = parseMd('---\nname: x\n---\n').ast;
|
||||
it("adds new frontmatter key with +key", () => {
|
||||
const md = parseMd("---\nname: x\n---\n").ast;
|
||||
const r = setOcPath(
|
||||
md,
|
||||
parseOcPath('oc://X.md/[frontmatter]/+description'),
|
||||
'a new description',
|
||||
parseOcPath("oc://X.md/[frontmatter]/+description"),
|
||||
"a new description",
|
||||
);
|
||||
expect(r.ok).toBe(true);
|
||||
if (r.ok) {
|
||||
const out = emitMd(r.ast as Parameters<typeof emitMd>[0]);
|
||||
expect(out).toContain('description: a new description');
|
||||
expect(out).toContain("description: a new description");
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects duplicate frontmatter key on insertion', () => {
|
||||
const md = parseMd('---\nname: x\n---\n').ast;
|
||||
const r = setOcPath(md, parseOcPath('oc://X.md/[frontmatter]/+name'), 'y');
|
||||
it("rejects duplicate frontmatter key on insertion", () => {
|
||||
const md = parseMd("---\nname: x\n---\n").ast;
|
||||
const r = setOcPath(md, parseOcPath("oc://X.md/[frontmatter]/+name"), "y");
|
||||
expect(r.ok).toBe(false);
|
||||
if (!r.ok) {expect(r.reason).toBe('type-mismatch');}
|
||||
if (!r.ok) {
|
||||
expect(r.reason).toBe("type-mismatch");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('setOcPath — jsonc insertion', () => {
|
||||
it('appends to array with `+`', () => {
|
||||
describe("setOcPath — jsonc insertion", () => {
|
||||
it("appends to array with `+`", () => {
|
||||
const ast = parseJsonc('{ "items": [1, 2] }').ast;
|
||||
const r = setOcPath(ast, parseOcPath('oc://config/items/+'), '3');
|
||||
const r = setOcPath(ast, parseOcPath("oc://config/items/+"), "3");
|
||||
expect(r.ok).toBe(true);
|
||||
if (r.ok) {
|
||||
const ast2 = r.ast as Parameters<typeof emitJsonc>[0];
|
||||
@@ -346,9 +346,9 @@ describe('setOcPath — jsonc insertion', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('inserts at index with `+nnn`', () => {
|
||||
it("inserts at index with `+nnn`", () => {
|
||||
const ast = parseJsonc('{ "items": [1, 3] }').ast;
|
||||
const r = setOcPath(ast, parseOcPath('oc://config/items/+1'), '2');
|
||||
const r = setOcPath(ast, parseOcPath("oc://config/items/+1"), "2");
|
||||
expect(r.ok).toBe(true);
|
||||
if (r.ok) {
|
||||
const ast2 = r.ast as Parameters<typeof emitJsonc>[0];
|
||||
@@ -356,120 +356,117 @@ describe('setOcPath — jsonc insertion', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('adds object key with `+key`', () => {
|
||||
it("adds object key with `+key`", () => {
|
||||
const ast = parseJsonc('{ "plugins": { "github": "tok" } }').ast;
|
||||
const r = setOcPath(
|
||||
ast,
|
||||
parseOcPath('oc://config/plugins/+gitlab'),
|
||||
'"new-tok"',
|
||||
);
|
||||
const r = setOcPath(ast, parseOcPath("oc://config/plugins/+gitlab"), '"new-tok"');
|
||||
expect(r.ok).toBe(true);
|
||||
if (r.ok) {
|
||||
const ast2 = r.ast as Parameters<typeof emitJsonc>[0];
|
||||
expect(JSON.parse(emitJsonc(ast2))).toEqual({
|
||||
plugins: { github: 'tok', gitlab: 'new-tok' },
|
||||
plugins: { github: "tok", gitlab: "new-tok" },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects duplicate object key', () => {
|
||||
it("rejects duplicate object key", () => {
|
||||
const ast = parseJsonc('{ "plugins": { "github": "x" } }').ast;
|
||||
const r = setOcPath(ast, parseOcPath('oc://config/plugins/+github'), '"y"');
|
||||
const r = setOcPath(ast, parseOcPath("oc://config/plugins/+github"), '"y"');
|
||||
expect(r.ok).toBe(false);
|
||||
if (!r.ok) {expect(r.reason).toBe('unresolved');}
|
||||
if (!r.ok) {
|
||||
expect(r.reason).toBe("unresolved");
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects +key on array', () => {
|
||||
it("rejects +key on array", () => {
|
||||
const ast = parseJsonc('{ "items": [1, 2] }').ast;
|
||||
const r = setOcPath(ast, parseOcPath('oc://config/items/+abc'), '3');
|
||||
const r = setOcPath(ast, parseOcPath("oc://config/items/+abc"), "3");
|
||||
expect(r.ok).toBe(false);
|
||||
if (!r.ok) {expect(r.reason).toBe('type-mismatch');}
|
||||
if (!r.ok) {
|
||||
expect(r.reason).toBe("type-mismatch");
|
||||
}
|
||||
});
|
||||
|
||||
it('inserts complex object via JSON value', () => {
|
||||
it("inserts complex object via JSON value", () => {
|
||||
const ast = parseJsonc('{ "plugins": {} }').ast;
|
||||
const r = setOcPath(
|
||||
ast,
|
||||
parseOcPath('oc://config/plugins/+gitlab'),
|
||||
parseOcPath("oc://config/plugins/+gitlab"),
|
||||
'{"token":"xyz","enabled":true}',
|
||||
);
|
||||
expect(r.ok).toBe(true);
|
||||
if (r.ok) {
|
||||
const ast2 = r.ast as Parameters<typeof emitJsonc>[0];
|
||||
expect(JSON.parse(emitJsonc(ast2))).toEqual({
|
||||
plugins: { gitlab: { token: 'xyz', enabled: true } },
|
||||
plugins: { gitlab: { token: "xyz", enabled: true } },
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('setOcPath — jsonl insertion (session append)', () => {
|
||||
it('appends a JSON line with `+`', () => {
|
||||
describe("setOcPath — jsonl insertion (session append)", () => {
|
||||
it("appends a JSON line with `+`", () => {
|
||||
const ast = parseJsonl('{"event":"start"}\n').ast;
|
||||
const r = setOcPath(
|
||||
ast,
|
||||
parseOcPath('oc://log/+'),
|
||||
'{"event":"step","n":1}',
|
||||
);
|
||||
const r = setOcPath(ast, parseOcPath("oc://log/+"), '{"event":"step","n":1}');
|
||||
expect(r.ok).toBe(true);
|
||||
if (r.ok) {
|
||||
const out = emitJsonl(r.ast as Parameters<typeof emitJsonl>[0]);
|
||||
const lines = out.split('\n').filter((l) => l.length > 0);
|
||||
const lines = out.split("\n").filter((l) => l.length > 0);
|
||||
expect(lines).toHaveLength(2);
|
||||
expect(JSON.parse(lines[1])).toEqual({ event: 'step', n: 1 });
|
||||
expect(JSON.parse(lines[1])).toEqual({ event: "step", n: 1 });
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects malformed JSON value', () => {
|
||||
const ast = parseJsonl('').ast;
|
||||
const r = setOcPath(ast, parseOcPath('oc://log/+'), 'not json');
|
||||
it("rejects malformed JSON value", () => {
|
||||
const ast = parseJsonl("").ast;
|
||||
const r = setOcPath(ast, parseOcPath("oc://log/+"), "not json");
|
||||
expect(r.ok).toBe(false);
|
||||
if (!r.ok) {expect(r.reason).toBe('parse-error');}
|
||||
if (!r.ok) {
|
||||
expect(r.reason).toBe("parse-error");
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects non-root insertion target', () => {
|
||||
it("rejects non-root insertion target", () => {
|
||||
const ast = parseJsonl('{"a":1}\n').ast;
|
||||
const r = setOcPath(ast, parseOcPath('oc://log/L1/+'), '{}');
|
||||
const r = setOcPath(ast, parseOcPath("oc://log/L1/+"), "{}");
|
||||
expect(r.ok).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------- Cross-cutting properties ---------------------------------------
|
||||
|
||||
describe('setOcPath — cross-cutting properties', () => {
|
||||
it('is non-mutating across all kinds', () => {
|
||||
const md = parseMd('---\nname: x\n---\n').ast;
|
||||
describe("setOcPath — cross-cutting properties", () => {
|
||||
it("is non-mutating across all kinds", () => {
|
||||
const md = parseMd("---\nname: x\n---\n").ast;
|
||||
const before = JSON.stringify(md);
|
||||
setOcPath(md, parseOcPath('oc://X.md/[frontmatter]/name'), 'new');
|
||||
setOcPath(md, parseOcPath("oc://X.md/[frontmatter]/name"), "new");
|
||||
expect(JSON.stringify(md)).toBe(before);
|
||||
|
||||
const jsonc = parseJsonc('{ "k": 1 }').ast;
|
||||
const before2 = JSON.stringify(jsonc);
|
||||
setOcPath(jsonc, parseOcPath('oc://config/k'), '99');
|
||||
setOcPath(jsonc, parseOcPath("oc://config/k"), "99");
|
||||
expect(JSON.stringify(jsonc)).toBe(before2);
|
||||
|
||||
const jsonl = parseJsonl('{"a":1}\n').ast;
|
||||
const before3 = JSON.stringify(jsonl);
|
||||
setOcPath(jsonl, parseOcPath('oc://log/L1/a'), '99');
|
||||
setOcPath(jsonl, parseOcPath("oc://log/L1/a"), "99");
|
||||
expect(JSON.stringify(jsonl)).toBe(before3);
|
||||
});
|
||||
|
||||
it('returns ok-tagged result with new ast on success', () => {
|
||||
const md = parseMd('---\nname: x\n---\n').ast;
|
||||
const r = setOcPath(md, parseOcPath('oc://X.md/[frontmatter]/name'), 'y');
|
||||
it("returns ok-tagged result with new ast on success", () => {
|
||||
const md = parseMd("---\nname: x\n---\n").ast;
|
||||
const r = setOcPath(md, parseOcPath("oc://X.md/[frontmatter]/name"), "y");
|
||||
expect(r.ok).toBe(true);
|
||||
if (r.ok) {
|
||||
expect(r.ast.kind).toBe('md');
|
||||
expect(r.ast.kind).toBe("md");
|
||||
}
|
||||
});
|
||||
|
||||
it('returns failure-tagged result with reason on unresolved', () => {
|
||||
const ast = parseJsonc('{}').ast;
|
||||
const r = setOcPath(ast, parseOcPath('oc://config/missing'), 'v');
|
||||
it("returns failure-tagged result with reason on unresolved", () => {
|
||||
const ast = parseJsonc("{}").ast;
|
||||
const r = setOcPath(ast, parseOcPath("oc://config/missing"), "v");
|
||||
expect(r.ok).toBe(false);
|
||||
if (!r.ok) {
|
||||
expect(r.reason).toBeDefined();
|
||||
expect(typeof r.reason).toBe('string');
|
||||
expect(r.reason).toBe("unresolved");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user