mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-06-08 05:39:52 +02:00
feat: complete Phase 2 network features for spellbooks
Sharing Enhancements: - Install qrcode library for QR code generation - Create ShareSpellbookDialog with tabbed interface - Support multiple share formats: Web URL, naddr, nevent - QR code generation and download for each format - Quick copy buttons with visual feedback - Integrated into SpellbookDetailRenderer Network Discovery: - Add "Discover" filter to browse spellbooks from other users - Query AGGREGATOR_RELAYS for network spellbook discovery - Show author names using UserName component - Conditional UI: hide owner actions for discovered spellbooks - Support viewing and applying layouts from the community Preview Route Polish: - Loading states with spinner during NIP-05 resolution - 10-second timeout for NIP-05 resolution - Error banners for resolution failures - Author name and creation date in preview banner - Copy link button in preview mode Conflict Resolution: - compareSpellbookVersions() function in spellbook-manager - ConflictResolutionDialog component for version conflicts - Side-by-side comparison of local vs network versions - Show workspace/window counts and timestamps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,13 @@ import type { ActionHub } from "applesauce-actions";
|
||||
import type { GrimoireState } from "@/types/app";
|
||||
import type { NostrEvent } from "nostr-tools/core";
|
||||
|
||||
// Mock accountManager
|
||||
vi.mock("@/services/accounts", () => ({
|
||||
default: {
|
||||
active: null, // Will be set in tests
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock implementations
|
||||
const mockSigner = {
|
||||
getPublicKey: vi.fn(async () => "test-pubkey"),
|
||||
@@ -29,9 +36,6 @@ const mockFactory = {
|
||||
};
|
||||
|
||||
const mockHub: ActionHub = {
|
||||
accountManager: {
|
||||
active: mockAccount,
|
||||
},
|
||||
factory: mockFactory,
|
||||
} as any;
|
||||
|
||||
@@ -59,8 +63,12 @@ const mockState: GrimoireState = {
|
||||
} as any;
|
||||
|
||||
describe("PublishSpellbook action", () => {
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Set up accountManager mock
|
||||
const accountManager = await import("@/services/accounts");
|
||||
(accountManager.default as any).active = mockAccount;
|
||||
});
|
||||
|
||||
describe("validation", () => {
|
||||
@@ -87,37 +95,38 @@ describe("PublishSpellbook action", () => {
|
||||
});
|
||||
|
||||
it("should throw error if no active account", async () => {
|
||||
const hubWithoutAccount: ActionHub = {
|
||||
...mockHub,
|
||||
accountManager: { active: null } as any,
|
||||
};
|
||||
const accountManager = await import("@/services/accounts");
|
||||
(accountManager.default as any).active = null;
|
||||
|
||||
await expect(async () => {
|
||||
for await (const event of PublishSpellbook(hubWithoutAccount, {
|
||||
for await (const event of PublishSpellbook(mockHub, {
|
||||
state: mockState,
|
||||
title: "Test Spellbook",
|
||||
})) {
|
||||
// Should not reach here
|
||||
}
|
||||
}).rejects.toThrow("No active account");
|
||||
|
||||
// Restore for other tests
|
||||
(accountManager.default as any).active = mockAccount;
|
||||
});
|
||||
|
||||
it("should throw error if no signer available", async () => {
|
||||
const hubWithoutSigner: ActionHub = {
|
||||
...mockHub,
|
||||
accountManager: {
|
||||
active: { ...mockAccount, signer: null } as any,
|
||||
} as any,
|
||||
};
|
||||
const accountManager = await import("@/services/accounts");
|
||||
const accountWithoutSigner = { ...mockAccount, signer: null };
|
||||
(accountManager.default as any).active = accountWithoutSigner;
|
||||
|
||||
await expect(async () => {
|
||||
for await (const event of PublishSpellbook(hubWithoutSigner, {
|
||||
for await (const event of PublishSpellbook(mockHub, {
|
||||
state: mockState,
|
||||
title: "Test Spellbook",
|
||||
})) {
|
||||
// Should not reach here
|
||||
}
|
||||
}).rejects.toThrow("No signer available");
|
||||
|
||||
// Restore for other tests
|
||||
(accountManager.default as any).active = mockAccount;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { GrimoireState } from "@/types/app";
|
||||
import { SpellbookContent } from "@/types/spell";
|
||||
import { mergeRelaySets } from "applesauce-core/helpers";
|
||||
import { AGGREGATOR_RELAYS } from "@/services/loaders";
|
||||
import accountManager from "@/services/accounts";
|
||||
import type { ActionHub } from "applesauce-actions";
|
||||
import type { NostrEvent } from "nostr-tools/core";
|
||||
|
||||
@@ -55,7 +56,7 @@ export async function* PublishSpellbook(
|
||||
throw new Error("Title is required");
|
||||
}
|
||||
|
||||
const account = hub.accountManager?.active;
|
||||
const account = accountManager.active;
|
||||
if (!account) {
|
||||
throw new Error("No active account. Please log in first.");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user