Files
grimoire/src/actions/publish-spell.test.ts
Alejandro b2b398b9fb docs: add applesauce v5 upgrade plan (#39)
* docs: add applesauce v5 upgrade plan

Comprehensive migration plan covering:
- Package updates (add applesauce-common, update to v5)
- EventFactory import migration (applesauce-factory → applesauce-core)
- Unified event loader setup
- ActionHub → ActionRunner migration
- useObservableMemo → use$ hook migration
- New features: casting system, encrypted content caching
- Documentation and skills updates needed

* feat: upgrade applesauce libraries to v5

Major upgrade from applesauce v4 to v5 with breaking changes:

Package updates:
- applesauce-core: ^4.0.0 → ^5.0.0
- applesauce-actions: ^4.0.0 → ^5.0.0
- applesauce-loaders: ^4.0.0 → ^5.0.0
- applesauce-react: ^4.0.0 → ^5.0.0
- applesauce-relay: ^4.0.0 → ^5.0.0
- applesauce-signers: ^4.0.0 → ^5.0.0
- applesauce-accounts: ^4.0.0 → ^5.0.0
- Added new applesauce-common: ^5.0.0 package

API migrations:
- EventFactory: applesauce-factory → applesauce-core/event-factory
- ActionHub → ActionRunner with async function pattern (not generators)
- useObservableMemo → use$ hook across all components
- Helper imports: article, highlight, threading, zap, comment, lists
  moved from applesauce-core to applesauce-common
- parseCoordinate → parseReplaceableAddress
- Subscription options: retries → reconnect
- getEventPointerFromETag now returns null instead of throwing

New features:
- Unified event loader via createEventLoaderForStore
- Updated loaders.ts to use v5 unified loader pattern

Documentation:
- Updated CLAUDE.md with v5 patterns and migration notes
- Updated applesauce-core skill for v5 changes
- Created new applesauce-common skill

Test fixes:
- Updated publish-spellbook.test.ts for v5 ActionRunner pattern
- Updated publish-spell.test.ts with eventStore mock
- Updated relay-selection.test.ts with valid test events
- Updated loaders.test.ts with valid 64-char hex event IDs
- Added createEventLoaderForStore mock

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-05 14:54:21 +01:00

113 lines
2.6 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from "vitest";
import { PublishSpellAction } from "./publish-spell";
import accountManager from "@/services/accounts";
import pool from "@/services/relay-pool";
import * as spellStorage from "@/services/spell-storage";
import { LocalSpell } from "@/services/db";
// Mock dependencies
vi.mock("@/services/accounts", () => ({
default: {
active: {
signer: {},
pubkey: "test-pubkey",
},
},
}));
vi.mock("@/services/relay-pool", () => ({
default: {
publish: vi.fn(),
},
}));
vi.mock("@/services/spell-storage", () => ({
markSpellPublished: vi.fn(),
}));
vi.mock("@/services/relay-list-cache", () => ({
relayListCache: {
getOutboxRelays: vi.fn().mockResolvedValue([]),
},
}));
vi.mock("@/services/event-store", () => ({
default: {
add: vi.fn(),
},
}));
describe("PublishSpellAction", () => {
let action: PublishSpellAction;
beforeEach(() => {
vi.clearAllMocks();
action = new PublishSpellAction();
});
it("should fail if no active account", async () => {
// @ts-expect-error: mocking internal state for test
accountManager.active = null;
const spell: LocalSpell = {
id: "spell-1",
command: "req -k 1",
createdAt: 123,
isPublished: false,
};
await expect(action.execute(spell)).rejects.toThrow("No active account");
});
it("should publish spell and update storage", async () => {
const mockSigner = {
getPublicKey: vi.fn().mockResolvedValue("pubkey"),
signEvent: vi.fn().mockImplementation((draft) =>
Promise.resolve({
...draft,
id: "event-id",
pubkey: "pubkey",
sig: "sig",
}),
),
};
// @ts-expect-error: mocking internal state for test
accountManager.active = {
signer: mockSigner,
pubkey: "pubkey",
};
const spell: LocalSpell = {
id: "local-id",
command: "req -k 1",
name: "My Spell",
description: "Description",
createdAt: 1234567890,
isPublished: false,
};
await action.execute(spell);
// Check if signer was called
expect(mockSigner.signEvent).toHaveBeenCalled();
// Check if published to pool
expect(pool.publish).toHaveBeenCalled();
// Check if storage updated
expect(spellStorage.markSpellPublished).toHaveBeenCalledWith(
"local-id",
expect.objectContaining({
kind: 777,
// We expect tags to contain name and alt (description)
tags: expect.arrayContaining([
["name", "My Spell"],
["alt", expect.stringContaining("Description")],
["cmd", "REQ"],
]),
}),
);
});
});