mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-11 16:07:15 +02:00
769 lines
23 KiB
TypeScript
769 lines
23 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { encodeSpell, decodeSpell } from "./spell-conversion";
|
|
import type { SpellEvent } from "@/types/spell";
|
|
|
|
describe("Spell Conversion", () => {
|
|
describe("encodeSpell", () => {
|
|
it("should encode a simple REQ command with kinds", () => {
|
|
const result = encodeSpell({
|
|
command: "req -k 1,3,7",
|
|
description: "Test spell",
|
|
});
|
|
|
|
expect(result.tags).toContainEqual(["cmd", "REQ"]);
|
|
expect(result.tags).toContainEqual(["client", "grimoire"]);
|
|
expect(result.tags).toContainEqual(["k", "1"]);
|
|
expect(result.tags).toContainEqual(["k", "3"]);
|
|
expect(result.tags).toContainEqual(["k", "7"]);
|
|
expect(result.filter.kinds).toEqual([1, 3, 7]);
|
|
expect(result.content).toBe("Test spell");
|
|
});
|
|
|
|
it("should encode with optional description", () => {
|
|
const result = encodeSpell({
|
|
command: "req -k 1",
|
|
description: "Test spell",
|
|
});
|
|
|
|
expect(result.tags).toContainEqual(["cmd", "REQ"]);
|
|
expect(result.content).toBe("Test spell");
|
|
});
|
|
|
|
it("should encode with empty description", () => {
|
|
const result = encodeSpell({
|
|
command: "req -k 1",
|
|
});
|
|
|
|
expect(result.tags).toContainEqual(["cmd", "REQ"]);
|
|
expect(result.content).toBe("");
|
|
});
|
|
|
|
it("should encode optional name tag", () => {
|
|
const result = encodeSpell({
|
|
command: "req -k 1",
|
|
name: "Bitcoin Feed",
|
|
});
|
|
|
|
expect(result.tags).toContainEqual(["name", "Bitcoin Feed"]);
|
|
expect(result.tags).toContainEqual(["cmd", "REQ"]);
|
|
});
|
|
|
|
it("should skip name tag if not provided", () => {
|
|
const result = encodeSpell({
|
|
command: "req -k 1",
|
|
});
|
|
|
|
const nameTag = result.tags.find((t) => t[0] === "name");
|
|
expect(nameTag).toBeUndefined();
|
|
});
|
|
|
|
it("should trim and skip empty name", () => {
|
|
const result = encodeSpell({
|
|
command: "req -k 1",
|
|
name: " ",
|
|
});
|
|
|
|
const nameTag = result.tags.find((t) => t[0] === "name");
|
|
expect(nameTag).toBeUndefined();
|
|
});
|
|
|
|
it("should encode both name and description", () => {
|
|
const result = encodeSpell({
|
|
command: "req -k 1",
|
|
name: "Bitcoin Feed",
|
|
description: "Notes about Bitcoin",
|
|
});
|
|
|
|
expect(result.tags).toContainEqual(["name", "Bitcoin Feed"]);
|
|
expect(result.content).toBe("Notes about Bitcoin");
|
|
});
|
|
|
|
it("should encode authors as array tag", () => {
|
|
const hex1 = "a".repeat(64);
|
|
const hex2 = "b".repeat(64);
|
|
const result = encodeSpell({
|
|
command: `req -k 1 -a ${hex1},${hex2}`,
|
|
description: "Author spell",
|
|
});
|
|
|
|
const authorsTag = result.tags.find((t) => t[0] === "authors");
|
|
expect(authorsTag).toEqual(["authors", hex1, hex2]);
|
|
expect(result.filter.authors).toEqual([hex1, hex2]);
|
|
});
|
|
|
|
it("should encode limit, since, until", () => {
|
|
const result = encodeSpell({
|
|
command: "req -k 1 -l 50 --since 7d --until now",
|
|
description: "Time spell",
|
|
});
|
|
|
|
expect(result.tags).toContainEqual(["limit", "50"]);
|
|
expect(result.tags).toContainEqual(["since", "7d"]);
|
|
expect(result.tags).toContainEqual(["until", "now"]);
|
|
expect(result.filter.limit).toBe(50);
|
|
});
|
|
|
|
it("should encode tag filters with new format", () => {
|
|
const hex = "c".repeat(64);
|
|
const result = encodeSpell({
|
|
command: `req -k 1 -t bitcoin,nostr -p ${hex} -d article1`,
|
|
description: "Tag spell",
|
|
});
|
|
|
|
expect(result.tags).toContainEqual(["tag", "t", "bitcoin", "nostr"]);
|
|
expect(result.tags).toContainEqual(["tag", "p", hex]);
|
|
expect(result.tags).toContainEqual(["tag", "d", "article1"]);
|
|
expect(result.filter["#t"]).toEqual(["bitcoin", "nostr"]);
|
|
expect(result.filter["#p"]).toEqual([hex]);
|
|
expect(result.filter["#d"]).toEqual(["article1"]);
|
|
});
|
|
|
|
it("should encode search query", () => {
|
|
const result = encodeSpell({
|
|
command: 'req -k 1 --search "bitcoin price"',
|
|
description: "Search spell",
|
|
});
|
|
|
|
expect(result.tags).toContainEqual(["search", "bitcoin price"]);
|
|
expect(result.filter.search).toBe("bitcoin price");
|
|
});
|
|
|
|
it("should encode relays", () => {
|
|
const result = encodeSpell({
|
|
command: "req -k 1 wss://relay1.com wss://relay2.com",
|
|
description: "Relay spell",
|
|
});
|
|
|
|
const relaysTag = result.tags.find((t) => t[0] === "relays");
|
|
expect(relaysTag).toEqual([
|
|
"relays",
|
|
"wss://relay1.com/",
|
|
"wss://relay2.com/",
|
|
]);
|
|
expect(result.relays).toEqual(["wss://relay1.com/", "wss://relay2.com/"]);
|
|
});
|
|
|
|
it("should encode close-on-eose flag", () => {
|
|
const result = encodeSpell({
|
|
command: "req -k 1 --close-on-eose",
|
|
description: "Close spell",
|
|
});
|
|
|
|
const closeTag = result.tags.find((t) => t[0] === "close-on-eose");
|
|
expect(closeTag).toBeDefined();
|
|
expect(result.closeOnEose).toBe(true);
|
|
});
|
|
|
|
it("should add topic tags", () => {
|
|
const result = encodeSpell({
|
|
command: "req -k 1",
|
|
description: "A test spell",
|
|
topics: ["bitcoin", "news"],
|
|
});
|
|
|
|
expect(result.tags).toContainEqual([
|
|
"alt",
|
|
"Grimoire REQ spell: A test spell",
|
|
]);
|
|
expect(result.tags).toContainEqual(["t", "bitcoin"]);
|
|
expect(result.tags).toContainEqual(["t", "news"]);
|
|
expect(result.content).toBe("A test spell");
|
|
});
|
|
|
|
it("should add fork provenance", () => {
|
|
const result = encodeSpell({
|
|
command: "req -k 1",
|
|
description: "Forked spell",
|
|
forkedFrom: "abc123def456",
|
|
});
|
|
|
|
expect(result.tags).toContainEqual(["e", "abc123def456"]);
|
|
});
|
|
|
|
it("should handle special aliases $me and $contacts", () => {
|
|
const result = encodeSpell({
|
|
command: "req -k 1 -a $me,$contacts",
|
|
description: "Alias spell",
|
|
});
|
|
|
|
const authorsTag = result.tags.find((t) => t[0] === "authors");
|
|
expect(authorsTag).toEqual(["authors", "$me", "$contacts"]);
|
|
expect(result.filter.authors).toEqual(["$me", "$contacts"]);
|
|
});
|
|
|
|
it("should handle uppercase P tag separately from lowercase p", () => {
|
|
const hex1 = "d".repeat(64);
|
|
const hex2 = "e".repeat(64);
|
|
const result = encodeSpell({
|
|
command: `req -k 9735 -p ${hex1} -P ${hex2}`,
|
|
description: "P tag spell",
|
|
});
|
|
|
|
expect(result.tags).toContainEqual(["tag", "p", hex1]);
|
|
expect(result.tags).toContainEqual(["tag", "P", hex2]);
|
|
expect(result.filter["#p"]).toEqual([hex1]);
|
|
expect(result.filter["#P"]).toEqual([hex2]);
|
|
});
|
|
|
|
it("should handle generic tags with -T flag", () => {
|
|
const result = encodeSpell({
|
|
command: "req -k 1 -T x value1,value2",
|
|
description: "Generic tag spell",
|
|
});
|
|
|
|
expect(result.tags).toContainEqual(["tag", "x", "value1", "value2"]);
|
|
expect(result.filter["#x"]).toEqual(["value1", "value2"]);
|
|
});
|
|
|
|
it("should handle complex command with multiple filters", () => {
|
|
const hex1 = "f".repeat(64);
|
|
const hex2 = "0".repeat(64);
|
|
const result = encodeSpell({
|
|
command: `req -k 1,3,30023 -a ${hex1},${hex2} -l 100 -t bitcoin,nostr --since 7d --search crypto wss://relay.com --close-on-eose`,
|
|
description: "Multi-filter spell",
|
|
topics: ["test"],
|
|
});
|
|
|
|
// Verify all components are present
|
|
expect(result.tags).toContainEqual(["k", "1"]);
|
|
expect(result.tags).toContainEqual(["k", "3"]);
|
|
expect(result.tags).toContainEqual(["k", "30023"]);
|
|
expect(result.tags).toContainEqual(["authors", hex1, hex2]);
|
|
expect(result.tags).toContainEqual(["limit", "100"]);
|
|
expect(result.tags).toContainEqual(["tag", "t", "bitcoin", "nostr"]);
|
|
expect(result.tags).toContainEqual(["since", "7d"]);
|
|
expect(result.tags).toContainEqual(["search", "crypto"]);
|
|
expect(result.tags).toContainEqual(["relays", "wss://relay.com/"]);
|
|
expect(result.tags).toContainEqual(["close-on-eose", ""]);
|
|
expect(result.tags).toContainEqual(["t", "test"]);
|
|
|
|
// Verify filter
|
|
expect(result.filter.kinds).toEqual([1, 3, 30023]);
|
|
expect(result.filter.authors).toEqual([hex1, hex2]);
|
|
expect(result.filter.limit).toBe(100);
|
|
expect(result.filter["#t"]).toEqual(["bitcoin", "nostr"]);
|
|
expect(result.filter.search).toBe("crypto");
|
|
});
|
|
});
|
|
|
|
describe("decodeSpell", () => {
|
|
it("should decode a simple spell back to command", () => {
|
|
const event: SpellEvent = {
|
|
id: "test-id",
|
|
pubkey: "test-pubkey",
|
|
created_at: 1234567890,
|
|
kind: 777,
|
|
tags: [
|
|
["cmd", "REQ"],
|
|
["client", "grimoire"],
|
|
["k", "1"],
|
|
["k", "3"],
|
|
],
|
|
content: "Test spell",
|
|
sig: "test-sig",
|
|
};
|
|
|
|
const parsed = decodeSpell(event);
|
|
|
|
expect(parsed.description).toBe("Test spell");
|
|
expect(parsed.filter.kinds).toEqual([1, 3]);
|
|
expect(parsed.command).toContain("-k 1,3");
|
|
});
|
|
|
|
it("should decode description from content", () => {
|
|
const event: SpellEvent = {
|
|
id: "test-id",
|
|
pubkey: "test-pubkey",
|
|
created_at: 1234567890,
|
|
kind: 777,
|
|
tags: [
|
|
["cmd", "REQ"],
|
|
["k", "1"],
|
|
],
|
|
content: "Test spell description",
|
|
sig: "test-sig",
|
|
};
|
|
|
|
const parsed = decodeSpell(event);
|
|
|
|
expect(parsed.description).toBe("Test spell description");
|
|
expect(parsed.filter.kinds).toEqual([1]);
|
|
});
|
|
|
|
it("should handle empty content", () => {
|
|
const event: SpellEvent = {
|
|
id: "test-id",
|
|
pubkey: "test-pubkey",
|
|
created_at: 1234567890,
|
|
kind: 777,
|
|
tags: [
|
|
["cmd", "REQ"],
|
|
["k", "1"],
|
|
],
|
|
content: "",
|
|
sig: "test-sig",
|
|
};
|
|
|
|
const parsed = decodeSpell(event);
|
|
|
|
expect(parsed.description).toBeUndefined();
|
|
expect(parsed.filter.kinds).toEqual([1]);
|
|
});
|
|
|
|
it("should decode name from tags", () => {
|
|
const event: SpellEvent = {
|
|
id: "test-id",
|
|
pubkey: "test-pubkey",
|
|
created_at: 1234567890,
|
|
kind: 777,
|
|
tags: [
|
|
["cmd", "REQ"],
|
|
["name", "Bitcoin Feed"],
|
|
["k", "1"],
|
|
],
|
|
content: "Notes about Bitcoin",
|
|
sig: "test-sig",
|
|
};
|
|
|
|
const parsed = decodeSpell(event);
|
|
|
|
expect(parsed.name).toBe("Bitcoin Feed");
|
|
expect(parsed.description).toBe("Notes about Bitcoin");
|
|
expect(parsed.filter.kinds).toEqual([1]);
|
|
});
|
|
|
|
it("should handle missing name tag", () => {
|
|
const event: SpellEvent = {
|
|
id: "test-id",
|
|
pubkey: "test-pubkey",
|
|
created_at: 1234567890,
|
|
kind: 777,
|
|
tags: [
|
|
["cmd", "REQ"],
|
|
["k", "1"],
|
|
],
|
|
content: "Test",
|
|
sig: "test-sig",
|
|
};
|
|
|
|
const parsed = decodeSpell(event);
|
|
|
|
expect(parsed.name).toBeUndefined();
|
|
expect(parsed.description).toBe("Test");
|
|
});
|
|
|
|
it("should decode authors", () => {
|
|
const event: SpellEvent = {
|
|
id: "test-id",
|
|
pubkey: "test-pubkey",
|
|
created_at: 1234567890,
|
|
kind: 777,
|
|
tags: [
|
|
["cmd", "REQ"],
|
|
["client", "grimoire"],
|
|
["k", "1"],
|
|
["authors", "abc123", "def456"],
|
|
],
|
|
content: "Author spell",
|
|
sig: "test-sig",
|
|
};
|
|
|
|
const parsed = decodeSpell(event);
|
|
|
|
expect(parsed.filter.authors).toEqual(["abc123", "def456"]);
|
|
expect(parsed.command).toContain("-a abc123,def456");
|
|
});
|
|
|
|
it("should decode tag filters with new format", () => {
|
|
const event: SpellEvent = {
|
|
id: "test-id",
|
|
pubkey: "test-pubkey",
|
|
created_at: 1234567890,
|
|
kind: 777,
|
|
tags: [
|
|
["cmd", "REQ"],
|
|
["client", "grimoire"],
|
|
["k", "1"],
|
|
["tag", "t", "bitcoin", "nostr"],
|
|
["tag", "p", "abc123"],
|
|
["tag", "P", "def456"],
|
|
],
|
|
content: "Tag spell",
|
|
sig: "test-sig",
|
|
};
|
|
|
|
const parsed = decodeSpell(event);
|
|
|
|
expect(parsed.filter["#t"]).toEqual(["bitcoin", "nostr"]);
|
|
expect(parsed.filter["#p"]).toEqual(["abc123"]);
|
|
expect(parsed.filter["#P"]).toEqual(["def456"]);
|
|
expect(parsed.command).toContain("-t bitcoin,nostr");
|
|
expect(parsed.command).toContain("-p abc123");
|
|
expect(parsed.command).toContain("-P def456");
|
|
});
|
|
|
|
it("should decode time bounds with relative format", () => {
|
|
const event: SpellEvent = {
|
|
id: "test-id",
|
|
pubkey: "test-pubkey",
|
|
created_at: 1234567890,
|
|
kind: 777,
|
|
tags: [
|
|
["cmd", "REQ"],
|
|
["client", "grimoire"],
|
|
["k", "1"],
|
|
["since", "7d"],
|
|
["until", "now"],
|
|
],
|
|
content: "Time spell",
|
|
sig: "test-sig",
|
|
};
|
|
|
|
const parsed = decodeSpell(event);
|
|
|
|
expect(parsed.command).toContain("--since 7d");
|
|
expect(parsed.command).toContain("--until now");
|
|
});
|
|
|
|
it("should decode topics", () => {
|
|
const event: SpellEvent = {
|
|
id: "test-id",
|
|
pubkey: "test-pubkey",
|
|
created_at: 1234567890,
|
|
kind: 777,
|
|
tags: [
|
|
["cmd", "REQ"],
|
|
["client", "grimoire"],
|
|
["k", "1"],
|
|
["t", "bitcoin"],
|
|
["t", "news"],
|
|
],
|
|
content: "A test spell",
|
|
sig: "test-sig",
|
|
};
|
|
|
|
const parsed = decodeSpell(event);
|
|
|
|
expect(parsed.description).toBe("A test spell");
|
|
expect(parsed.topics).toEqual(["bitcoin", "news"]);
|
|
});
|
|
|
|
it("should decode fork provenance", () => {
|
|
const event: SpellEvent = {
|
|
id: "test-id",
|
|
pubkey: "test-pubkey",
|
|
created_at: 1234567890,
|
|
kind: 777,
|
|
tags: [
|
|
["cmd", "REQ"],
|
|
["client", "grimoire"],
|
|
["k", "1"],
|
|
["e", "abc123def456"],
|
|
],
|
|
content: "Forked spell",
|
|
sig: "test-sig",
|
|
};
|
|
|
|
const parsed = decodeSpell(event);
|
|
|
|
expect(parsed.forkedFrom).toBe("abc123def456");
|
|
});
|
|
|
|
it("should throw error if cmd is not REQ", () => {
|
|
const event: SpellEvent = {
|
|
id: "test-id",
|
|
pubkey: "test-pubkey",
|
|
created_at: 1234567890,
|
|
kind: 777,
|
|
tags: [["cmd", "INVALID"]],
|
|
content: "Test",
|
|
sig: "test-sig",
|
|
};
|
|
|
|
expect(() => decodeSpell(event)).toThrow(
|
|
"Invalid spell command type: INVALID",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("Round-trip conversion", () => {
|
|
it("should preserve filter semantics through encode → decode", () => {
|
|
const hex1 = "1".repeat(64);
|
|
const hex2 = "2".repeat(64);
|
|
const original = {
|
|
command: `req -k 1,3,7 -a ${hex1},${hex2} -l 50 -t bitcoin,nostr --since 7d --search crypto`,
|
|
description: "Testing round-trip conversion",
|
|
topics: ["test"],
|
|
};
|
|
|
|
// Encode
|
|
const encoded = encodeSpell(original);
|
|
|
|
// Create event
|
|
const event: SpellEvent = {
|
|
id: "test-id",
|
|
pubkey: "test-pubkey",
|
|
created_at: 1234567890,
|
|
kind: 777,
|
|
tags: encoded.tags,
|
|
content: encoded.content,
|
|
sig: "test-sig",
|
|
};
|
|
|
|
// Decode
|
|
const decoded = decodeSpell(event);
|
|
|
|
// Verify filter semantics are preserved
|
|
expect(decoded.filter.kinds).toEqual([1, 3, 7]);
|
|
expect(decoded.filter.authors).toEqual([hex1, hex2]);
|
|
expect(decoded.filter.limit).toBe(50);
|
|
expect(decoded.filter["#t"]).toEqual(["bitcoin", "nostr"]);
|
|
expect(decoded.filter.search).toBe("crypto");
|
|
|
|
// Verify metadata
|
|
expect(decoded.description).toBe("Testing round-trip conversion");
|
|
expect(decoded.topics).toEqual(["test"]);
|
|
|
|
// Verify command contains key components (order may differ)
|
|
expect(decoded.command).toContain("-k 1,3,7");
|
|
expect(decoded.command).toContain(`-a ${hex1},${hex2}`);
|
|
expect(decoded.command).toContain("-l 50");
|
|
expect(decoded.command).toContain("-t bitcoin,nostr");
|
|
expect(decoded.command).toContain("--since 7d");
|
|
expect(decoded.command).toContain("--search");
|
|
});
|
|
|
|
it("should handle minimal spell without description", () => {
|
|
const original = {
|
|
command: "req -k 1",
|
|
};
|
|
|
|
const encoded = encodeSpell(original);
|
|
const event: SpellEvent = {
|
|
id: "test-id",
|
|
pubkey: "test-pubkey",
|
|
created_at: 1234567890,
|
|
kind: 777,
|
|
tags: encoded.tags,
|
|
content: encoded.content,
|
|
sig: "test-sig",
|
|
};
|
|
|
|
const decoded = decodeSpell(event);
|
|
|
|
expect(decoded.description).toBeUndefined();
|
|
expect(decoded.filter.kinds).toEqual([1]);
|
|
expect(decoded.command).toBe("req -k 1");
|
|
});
|
|
|
|
it("should round-trip with name", () => {
|
|
const original = {
|
|
command: "req -k 1",
|
|
name: "Bitcoin Feed",
|
|
description: "Notes about Bitcoin",
|
|
};
|
|
|
|
const encoded = encodeSpell(original);
|
|
const event: SpellEvent = {
|
|
id: "test-id",
|
|
pubkey: "test-pubkey",
|
|
created_at: 1234567890,
|
|
kind: 777,
|
|
tags: encoded.tags,
|
|
content: encoded.content,
|
|
sig: "test-sig",
|
|
};
|
|
|
|
const decoded = decodeSpell(event);
|
|
|
|
expect(decoded.name).toBe("Bitcoin Feed");
|
|
expect(decoded.description).toBe("Notes about Bitcoin");
|
|
expect(decoded.filter.kinds).toEqual([1]);
|
|
});
|
|
|
|
it("should preserve special aliases through round-trip", () => {
|
|
const original = {
|
|
command: "req -k 1 -a $me,$contacts -p $me",
|
|
description: "Alias spell",
|
|
};
|
|
|
|
const encoded = encodeSpell(original);
|
|
|
|
// Debug: Check what was encoded
|
|
const authorsTag = encoded.tags.find((t) => t[0] === "authors");
|
|
const pTag = encoded.tags.find((t) => t[0] === "tag" && t[1] === "p");
|
|
|
|
// Verify encoding worked
|
|
expect(authorsTag).toEqual(["authors", "$me", "$contacts"]);
|
|
expect(pTag).toEqual(["tag", "p", "$me"]);
|
|
|
|
const event: SpellEvent = {
|
|
id: "test-id",
|
|
pubkey: "test-pubkey",
|
|
created_at: 1234567890,
|
|
kind: 777,
|
|
tags: encoded.tags,
|
|
content: encoded.content,
|
|
sig: "test-sig",
|
|
};
|
|
|
|
const decoded = decodeSpell(event);
|
|
|
|
expect(decoded.filter.authors).toEqual(["$me", "$contacts"]);
|
|
expect(decoded.filter["#p"]).toEqual(["$me"]);
|
|
expect(decoded.command).toContain("-a $me,$contacts");
|
|
expect(decoded.command).toContain("-p $me");
|
|
});
|
|
});
|
|
|
|
describe("Validation and edge cases", () => {
|
|
it("should throw error for empty command", () => {
|
|
expect(() =>
|
|
encodeSpell({
|
|
command: "",
|
|
}),
|
|
).toThrow("Spell command is required");
|
|
});
|
|
|
|
it("should throw error for whitespace-only command", () => {
|
|
expect(() =>
|
|
encodeSpell({
|
|
command: " ",
|
|
}),
|
|
).toThrow("Spell command is required");
|
|
});
|
|
|
|
it("should throw error for 'req' with no filters", () => {
|
|
expect(() =>
|
|
encodeSpell({
|
|
command: "req",
|
|
}),
|
|
).toThrow(); // Will throw either empty tokens or no constraints error
|
|
});
|
|
|
|
it("should throw error for command with no valid filters", () => {
|
|
expect(() =>
|
|
encodeSpell({
|
|
command: "req --invalid-flag",
|
|
}),
|
|
).toThrow(
|
|
"Spell command must specify at least one filter (kinds, authors, tags, time bounds, search, or limit)",
|
|
);
|
|
});
|
|
|
|
it("should throw error for command with only invalid values", () => {
|
|
expect(() =>
|
|
encodeSpell({
|
|
command: "req -k invalid",
|
|
}),
|
|
).toThrow(
|
|
"Spell command must specify at least one filter (kinds, authors, tags, time bounds, search, or limit)",
|
|
);
|
|
});
|
|
|
|
it("should handle malformed author values gracefully", () => {
|
|
// Invalid hex should be ignored, not cause errors
|
|
const result = encodeSpell({
|
|
command: "req -k 1 -a invalid",
|
|
});
|
|
|
|
// Should have kinds but no authors
|
|
expect(result.filter.kinds).toEqual([1]);
|
|
expect(result.filter.authors).toBeUndefined();
|
|
});
|
|
|
|
it("should handle mixed valid and invalid values", () => {
|
|
const hex = "a".repeat(64);
|
|
const result = encodeSpell({
|
|
command: `req -k 1,invalid,3 -a ${hex},invalid`,
|
|
});
|
|
|
|
// Should keep only valid values
|
|
expect(result.filter.kinds).toEqual([1, 3]);
|
|
expect(result.filter.authors).toEqual([hex]);
|
|
});
|
|
|
|
it("should handle quotes in search query", () => {
|
|
const result = encodeSpell({
|
|
command: 'req -k 1 --search "quoted text"',
|
|
});
|
|
|
|
expect(result.filter.search).toBe("quoted text");
|
|
});
|
|
|
|
it("should handle single quotes in search query", () => {
|
|
const result = encodeSpell({
|
|
command: "req -k 1 --search 'single quoted'",
|
|
});
|
|
|
|
expect(result.filter.search).toBe("single quoted");
|
|
});
|
|
|
|
it("should handle special characters in search", () => {
|
|
const result = encodeSpell({
|
|
command: "req -k 1 --search 'text with #hashtag @mention'",
|
|
});
|
|
|
|
expect(result.filter.search).toBe("text with #hashtag @mention");
|
|
});
|
|
|
|
it("should accept commands with only limit", () => {
|
|
const result = encodeSpell({
|
|
command: "req -l 50",
|
|
});
|
|
|
|
expect(result.filter.limit).toBe(50);
|
|
});
|
|
|
|
it("should accept commands with only time bounds", () => {
|
|
const result = encodeSpell({
|
|
command: "req --since 7d",
|
|
});
|
|
|
|
expect(result.filter.since).toBeDefined();
|
|
});
|
|
|
|
it("should accept commands with only search", () => {
|
|
const result = encodeSpell({
|
|
command: "req --search bitcoin",
|
|
});
|
|
|
|
expect(result.filter.search).toBe("bitcoin");
|
|
});
|
|
|
|
it("should handle very long commands", () => {
|
|
// Generate 10 different hex values to test long author lists
|
|
const hexValues = Array(10)
|
|
.fill(0)
|
|
.map((_, i) => i.toString(16).repeat(64).slice(0, 64))
|
|
.join(",");
|
|
|
|
const result = encodeSpell({
|
|
command: `req -k 1 -a ${hexValues}`,
|
|
});
|
|
|
|
expect(result.filter.authors).toBeDefined();
|
|
expect(result.filter.authors!.length).toBe(10);
|
|
});
|
|
|
|
it("should handle Unicode in descriptions", () => {
|
|
const result = encodeSpell({
|
|
command: "req -k 1",
|
|
description: "Testing with emoji 🎨 and unicode 你好",
|
|
});
|
|
|
|
expect(result.content).toBe("Testing with emoji 🎨 and unicode 你好");
|
|
});
|
|
|
|
it("should preserve exact capitalization in $me/$contacts", () => {
|
|
const result = encodeSpell({
|
|
command: "req -k 1 -a $Me,$CONTACTS",
|
|
});
|
|
|
|
// Parser normalizes to lowercase
|
|
const authorsTag = result.tags.find((t) => t[0] === "authors");
|
|
expect(authorsTag).toEqual(["authors", "$me", "$contacts"]);
|
|
});
|
|
});
|
|
});
|