import { describe, expect, it } from
"vitest";
import {
parseFrontmatter,
resolveOpenClawMetadata,
resolveHookInvocationPolicy,
} from
"./frontmatter.js";
describe(
"parseFrontmatter", () => {
it(
"parses single-line key-value pairs", () => {
const content = `---
name: test-hook
description:
"A test hook"
homepage: https:
//example.com
---
# Test Hook
`;
const result = parseFrontmatter(content);
expect(result.name).toBe(
"test-hook");
expect(result.description).toBe(
"A test hook");
expect(result.homepage).toBe(
"https://example.com");
});
it(
"handles missing frontmatter", () => {
const content =
"# Just a markdown file";
const result = parseFrontmatter(content);
expect(result).toEqual({});
});
it(
"handles unclosed frontmatter", () => {
const content = `---
name: broken
`;
const result = parseFrontmatter(content);
expect(result).toEqual({});
});
it(
"parses multi-line metadata block with indented JSON", () => {
const content = `---
name: session-memory
description:
"Save session context"
metadata:
{
"openclaw": {
"emoji":
"",
"events": [
"command:new"]
}
}
---
# Session Memory Hook
`;
const result = parseFrontmatter(content);
expect(result.name).toBe(
"session-memory");
expect(result.description).toBe(
"Save session context");
expect(result.metadata).toBeDefined();
expect(
typeof result.metadata).toBe(
"string");
// Verify the metadata is valid JSON
const parsed = JSON.parse(result.metadata);
expect(parsed.openclaw.emoji).toBe(
"");
expect(parsed.openclaw.events).toEqual([
"command:new"]);
});
it(
"parses multi-line metadata with complex nested structure", () => {
const content = `---
name: command-logger
description:
"Log all command events"
metadata:
{
"openclaw":
{
"emoji":
"",
"events": [
"command"],
"requires": {
"config": [
"workspace.dir"] },
"install": [{
"id":
"bundled",
"kind":
"bundled",
"label":
"Bundled" }]
}
}
---
`;
const result = parseFrontmatter(content);
expect(result.name).toBe(
"command-logger");
expect(result.metadata).toBeDefined();
const parsed = JSON.parse(result.metadata);
expect(parsed.openclaw.emoji).toBe(
"");
expect(parsed.openclaw.events).toEqual([
"command"]);
expect(parsed.openclaw.requires.config).toEqual([
"workspace.dir"]);
expect(parsed.openclaw.install[
0].kind).toBe(
"bundled");
});
it(
"handles single-line metadata (inline JSON)", () => {
const content = `---
name: simple-hook
metadata: {
"openclaw": {
"events": [
"test"]}}
---
`;
const result = parseFrontmatter(content);
expect(result.name).toBe(
"simple-hook");
expect(result.metadata).toBe(
'{"openclaw": {"events": ["test"]}}');
});
it(
"handles mixed single-line and multi-line values", () => {
const content = `---
name: mixed-hook
description:
"A hook with mixed values"
homepage: https:
//example.com
metadata:
{
"openclaw": {
"events": [
"command:new"]
}
}
enabled:
true
---
`;
const result = parseFrontmatter(content);
expect(result.name).toBe(
"mixed-hook");
expect(result.description).toBe(
"A hook with mixed values");
expect(result.homepage).toBe(
"https://example.com");
expect(result.metadata).toBeDefined();
expect(result.enabled).toBe(
"true");
});
it(
"strips surrounding quotes from values", () => {
const content = `---
name:
"quoted-name"
description:
'single-quoted'
---
`;
const result = parseFrontmatter(content);
expect(result.name).toBe(
"quoted-name");
expect(result.description).toBe(
"single-quoted");
});
it(
"handles CRLF line endings", () => {
const content =
"---\r\nname: test\r\ndescription: crlf\r\n---\r\n";
const result = parseFrontmatter(content);
expect(result.name).toBe(
"test");
expect(result.description).toBe(
"crlf");
});
it(
"handles CR line endings", () => {
const content =
"---\rname: test\rdescription: cr\r---\r";
const result = parseFrontmatter(content);
expect(result.name).toBe(
"test");
expect(result.description).toBe(
"cr");
});
});
describe(
"resolveOpenClawMetadata", () => {
it(
"extracts openclaw metadata from parsed frontmatter", () => {
const frontmatter = {
name:
"test-hook",
metadata: JSON.stringify({
openclaw: {
emoji:
"",
events: [
"command:new",
"command:reset"],
requires: {
config: [
"workspace.dir"],
bins: [
"git"],
},
},
}),
};
const result = resolveOpenClawMetadata(frontmatter);
expect(result).toBeDefined();
expect(result?.emoji).toBe(
"");
expect(result?.events).toEqual([
"command:new",
"command:reset"]);
expect(result?.requires?.config).toEqual([
"workspace.dir"]);
expect(result?.requires?.bins).toEqual([
"git"]);
});
it(
"returns undefined when metadata is missing", () => {
const frontmatter = { name:
"no-metadata" };
const result = resolveOpenClawMetadata(frontmatter);
expect(result).toBeUndefined();
});
it(
"returns undefined when openclaw key is missing", () => {
const frontmatter = {
metadata: JSON.stringify({ other:
"data" }),
};
const result = resolveOpenClawMetadata(frontmatter);
expect(result).toBeUndefined();
});
it(
"returns undefined for invalid JSON", () => {
const frontmatter = {
metadata:
"not valid json {",
};
const result = resolveOpenClawMetadata(frontmatter);
expect(result).toBeUndefined();
});
it(
"handles install specs", () => {
const frontmatter = {
metadata: JSON.stringify({
openclaw: {
events: [
"command"],
install: [
{ id:
"bundled", kind:
"bundled", label:
"Bundled with OpenClaw" },
{ id:
"npm", kind:
"npm",
package:
"@openclaw/hook" },
],
},
}),
};
const result = resolveOpenClawMetadata(frontmatter);
expect(result?.install).toHaveLength(
2);
expect(result?.install?.[
0].kind).toBe(
"bundled");
expect(result?.install?.[
1].kind).toBe(
"npm");
expect(result?.install?.[
1].
package).toBe(
"@openclaw/hook");
});
it(
"handles os restrictions", () => {
const frontmatter = {
metadata: JSON.stringify({
openclaw: {
events: [
"command"],
os: [
"darwin",
"linux"],
},
}),
};
const result = resolveOpenClawMetadata(frontmatter);
expect(result?.os).toEqual([
"darwin",
"linux"]);
});
it(
"parses real session-memory HOOK.md format", () => {
// This is the actual format used in the bundled hooks
const content = `---
name: session-memory
description:
"Save session context to memory when /new or /reset command is issued"
homepage: https:
//docs.openclaw.ai/automation/hooks#session-memory
metadata:
{
"openclaw":
{
"emoji":
"",
"events": [
"command:new",
"command:reset"],
"requires": {
"config": [
"workspace.dir"] },
"install": [{
"id":
"bundled",
"kind":
"bundled",
"label":
"Bundled with OpenClaw" }],
},
}
---
# Session Memory Hook
`;
const frontmatter = parseFrontmatter(content);
expect(frontmatter.name).toBe(
"session-memory");
expect(frontmatter.metadata).toBeDefined();
const openclaw = resolveOpenClawMetadata(frontmatter);
expect(openclaw).toBeDefined();
expect(openclaw?.emoji).toBe(
"");
expect(openclaw?.events).toEqual([
"command:new",
"command:reset"]);
expect(openclaw?.requires?.config).toEqual([
"workspace.dir"]);
expect(openclaw?.install?.[
0].kind).toBe(
"bundled");
});
it(
"parses YAML metadata map", () => {
const content = `---
name: yaml-metadata
metadata:
openclaw:
emoji: disk
events:
- command:
new
---
`;
const frontmatter = parseFrontmatter(content);
const openclaw = resolveOpenClawMetadata(frontmatter);
expect(openclaw?.emoji).toBe(
"disk");
expect(openclaw?.events).toEqual([
"command:new"]);
});
});
describe(
"resolveHookInvocationPolicy", () => {
it(
"defaults to enabled when missing", () => {
expect(resolveHookInvocationPolicy({}).enabled).toBe(
true);
});
it(
"parses enabled flag", () => {
expect(resolveHookInvocationPolicy({ enabled:
"no" }).enabled).toBe(
false);
expect(resolveHookInvocationPolicy({ enabled:
"on" }).enabled).toBe(
true);
});
});