You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
875 lines
25 KiB
875 lines
25 KiB
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; |
|
import { NDKEvent } from "@nostr-dev-kit/ndk"; |
|
import type NDK from "@nostr-dev-kit/ndk"; |
|
|
|
// Mock flowbite-svelte components |
|
vi.mock("flowbite-svelte", () => ({ |
|
Button: vi.fn().mockImplementation((props) => ({ |
|
$$render: () => |
|
`<button data-testid="button">${props.children || ""}</button>`, |
|
})), |
|
Modal: vi.fn().mockImplementation(() => ({ |
|
$$render: () => `<div data-testid="modal"></div>`, |
|
})), |
|
Textarea: vi.fn().mockImplementation(() => ({ |
|
$$render: () => `<textarea data-testid="textarea"></textarea>`, |
|
})), |
|
P: vi.fn().mockImplementation(() => ({ |
|
$$render: () => `<p data-testid="p"></p>`, |
|
})), |
|
})); |
|
|
|
// Mock flowbite-svelte-icons |
|
vi.mock("flowbite-svelte-icons", () => ({ |
|
FontHighlightOutline: vi.fn().mockImplementation(() => ({ |
|
$$render: () => `<svg data-testid="highlight-icon"></svg>`, |
|
})), |
|
})); |
|
|
|
describe("HighlightButton Component Logic", () => { |
|
let isActive: boolean; |
|
|
|
beforeEach(() => { |
|
isActive = false; |
|
}); |
|
|
|
describe("Initial State", () => { |
|
it("should initialize with inactive state", () => { |
|
expect(isActive).toBe(false); |
|
}); |
|
|
|
it("should have correct inactive label", () => { |
|
const label = isActive ? "Exit Highlight Mode" : "Add Highlight"; |
|
expect(label).toBe("Add Highlight"); |
|
}); |
|
|
|
it("should have correct inactive title", () => { |
|
const title = isActive ? "Exit highlight mode" : "Enter highlight mode"; |
|
expect(title).toBe("Enter highlight mode"); |
|
}); |
|
|
|
it("should have correct inactive color", () => { |
|
const color = isActive ? "primary" : "light"; |
|
expect(color).toBe("light"); |
|
}); |
|
|
|
it("should not have ring styling when inactive", () => { |
|
const ringClass = isActive ? "ring-2 ring-primary-500" : ""; |
|
expect(ringClass).toBe(""); |
|
}); |
|
}); |
|
|
|
describe("Toggle Functionality", () => { |
|
it("should toggle to active state when clicked", () => { |
|
// Simulate toggle |
|
isActive = !isActive; |
|
expect(isActive).toBe(true); |
|
}); |
|
|
|
it("should toggle back to inactive state on second click", () => { |
|
// Simulate two toggles |
|
isActive = !isActive; |
|
isActive = !isActive; |
|
expect(isActive).toBe(false); |
|
}); |
|
|
|
it("should show correct label when active", () => { |
|
isActive = true; |
|
const label = isActive ? "Exit Highlight Mode" : "Add Highlight"; |
|
expect(label).toBe("Exit Highlight Mode"); |
|
}); |
|
|
|
it("should show correct title when active", () => { |
|
isActive = true; |
|
const title = isActive ? "Exit highlight mode" : "Enter highlight mode"; |
|
expect(title).toBe("Exit highlight mode"); |
|
}); |
|
}); |
|
|
|
describe("Active State Styling", () => { |
|
it("should apply primary color when active", () => { |
|
isActive = true; |
|
const color = isActive ? "primary" : "light"; |
|
expect(color).toBe("primary"); |
|
}); |
|
|
|
it("should apply ring styling when active", () => { |
|
isActive = true; |
|
const ringClass = isActive ? "ring-2 ring-primary-500" : ""; |
|
expect(ringClass).toBe("ring-2 ring-primary-500"); |
|
}); |
|
}); |
|
}); |
|
|
|
describe("HighlightSelectionHandler Component Logic", () => { |
|
let mockNDK: NDKEvent; |
|
let mockUserStore: any; |
|
let mockSelection: Selection; |
|
let mockPublicationEvent: NDKEvent; |
|
let isActive: boolean; |
|
|
|
beforeEach(() => { |
|
// Reset mocks |
|
vi.clearAllMocks(); |
|
isActive = false; |
|
|
|
// Mock document and DOM elements |
|
const mockElement = { |
|
createElement: vi.fn((tag: string) => ({ |
|
tagName: tag.toUpperCase(), |
|
textContent: "", |
|
className: "", |
|
closest: vi.fn(), |
|
parentElement: null, |
|
})), |
|
addEventListener: vi.fn(), |
|
removeEventListener: vi.fn(), |
|
body: { |
|
classList: { |
|
add: vi.fn(), |
|
remove: vi.fn(), |
|
}, |
|
}, |
|
}; |
|
|
|
global.document = mockElement as any; |
|
|
|
// Mock NDK event |
|
mockPublicationEvent = { |
|
id: "test-event-id", |
|
pubkey: "test-pubkey", |
|
kind: 30023, |
|
tagAddress: vi.fn().mockReturnValue("30023:test-pubkey:test-d-tag"), |
|
tags: [], |
|
content: "", |
|
} as unknown as NDKEvent; |
|
|
|
// Mock user store |
|
mockUserStore = { |
|
signedIn: true, |
|
signer: { |
|
sign: vi.fn().mockResolvedValue(undefined), |
|
}, |
|
}; |
|
|
|
// Mock window.getSelection |
|
const mockParagraph = { |
|
textContent: "This is the full paragraph context", |
|
closest: vi.fn(), |
|
}; |
|
|
|
mockSelection = { |
|
toString: vi.fn().mockReturnValue("Selected text from publication"), |
|
isCollapsed: false, |
|
removeAllRanges: vi.fn(), |
|
anchorNode: { |
|
parentElement: mockParagraph, |
|
}, |
|
} as unknown as Selection; |
|
|
|
global.window = { |
|
getSelection: vi.fn().mockReturnValue(mockSelection), |
|
} as any; |
|
}); |
|
|
|
afterEach(() => { |
|
vi.clearAllMocks(); |
|
}); |
|
|
|
describe("Selection Detection", () => { |
|
it("should ignore mouseup events when isActive is false", () => { |
|
isActive = false; |
|
const shouldProcess = isActive; |
|
expect(shouldProcess).toBe(false); |
|
}); |
|
|
|
it("should process mouseup events when isActive is true", () => { |
|
isActive = true; |
|
const shouldProcess = isActive; |
|
expect(shouldProcess).toBe(true); |
|
}); |
|
|
|
it("should ignore collapsed selections", () => { |
|
const selection = { isCollapsed: true } as Selection; |
|
const shouldIgnore = selection.isCollapsed; |
|
expect(shouldIgnore).toBe(true); |
|
}); |
|
|
|
it("should process non-collapsed selections", () => { |
|
const selection = { isCollapsed: false } as Selection; |
|
const shouldIgnore = selection.isCollapsed; |
|
expect(shouldIgnore).toBe(false); |
|
}); |
|
|
|
it("should ignore selections with less than 3 characters", () => { |
|
const text = "ab"; |
|
const isValid = text.length >= 3; |
|
expect(isValid).toBe(false); |
|
}); |
|
|
|
it("should accept selections with 3 or more characters", () => { |
|
const text = "abc"; |
|
const isValid = text.length >= 3; |
|
expect(isValid).toBe(true); |
|
}); |
|
|
|
it("should ignore empty selections after trim", () => { |
|
const text = " "; |
|
const trimmed = text.trim(); |
|
const isValid = trimmed.length >= 3; |
|
expect(isValid).toBe(false); |
|
}); |
|
}); |
|
|
|
describe("User Authentication", () => { |
|
it("should reject selection when user not signed in", () => { |
|
const userStore = { signedIn: false }; |
|
expect(userStore.signedIn).toBe(false); |
|
}); |
|
|
|
it("should process selection when user signed in", () => { |
|
const userStore = { signedIn: true }; |
|
expect(userStore.signedIn).toBe(true); |
|
}); |
|
|
|
it("should check for signer before creating highlight", () => { |
|
const userStore = { |
|
signedIn: true, |
|
signer: { sign: vi.fn() }, |
|
}; |
|
expect(userStore.signer).toBeDefined(); |
|
}); |
|
|
|
it("should reject creation without signer", () => { |
|
const userStore = { |
|
signedIn: true, |
|
signer: null, |
|
}; |
|
expect(userStore.signer).toBeNull(); |
|
}); |
|
}); |
|
|
|
describe("Publication Context Detection", () => { |
|
it("should detect selection within publication-leather class", () => { |
|
const mockElement = { |
|
className: "publication-leather", |
|
closest: vi.fn((selector: string) => { |
|
return selector === ".publication-leather" ? mockElement : null; |
|
}), |
|
}; |
|
const target = mockElement; |
|
const publicationSection = target.closest(".publication-leather"); |
|
expect(publicationSection).toBeTruthy(); |
|
}); |
|
|
|
it("should reject selection outside publication-leather class", () => { |
|
const mockElement = { |
|
className: "other-section", |
|
closest: vi.fn((selector: string) => { |
|
return selector === ".publication-leather" ? null : mockElement; |
|
}), |
|
}; |
|
const target = mockElement; |
|
const publicationSection = target.closest(".publication-leather"); |
|
expect(publicationSection).toBeNull(); |
|
}); |
|
}); |
|
|
|
describe("Context Extraction", () => { |
|
it("should extract context from parent paragraph", () => { |
|
const paragraph = { |
|
textContent: |
|
"This is the full paragraph context with selected text inside.", |
|
}; |
|
|
|
const context = paragraph.textContent?.trim() || ""; |
|
expect(context).toBe( |
|
"This is the full paragraph context with selected text inside.", |
|
); |
|
}); |
|
|
|
it("should extract context from parent section", () => { |
|
const section = { |
|
textContent: "Full section context including selected text.", |
|
}; |
|
|
|
const context = section.textContent?.trim() || ""; |
|
expect(context).toBe("Full section context including selected text."); |
|
}); |
|
|
|
it("should extract context from parent div", () => { |
|
const div = { |
|
textContent: "Full div context including selected text.", |
|
}; |
|
|
|
const context = div.textContent?.trim() || ""; |
|
expect(context).toBe("Full div context including selected text."); |
|
}); |
|
|
|
it("should handle missing context gracefully", () => { |
|
const context = ""; |
|
expect(context).toBe(""); |
|
}); |
|
}); |
|
|
|
describe("NIP-84 Event Creation - Addressable Events", () => { |
|
it("should use 'a' tag for addressable events", () => { |
|
const eventAddress = "30023:pubkey:d-tag"; |
|
const tags: string[][] = []; |
|
|
|
if (eventAddress) { |
|
tags.push(["a", eventAddress, ""]); |
|
} |
|
|
|
expect(tags).toContainEqual(["a", eventAddress, ""]); |
|
}); |
|
|
|
it("should create event with correct kind 9802", () => { |
|
const event = { |
|
kind: 9802, |
|
content: "", |
|
tags: [], |
|
}; |
|
|
|
expect(event.kind).toBe(9802); |
|
}); |
|
|
|
it("should include selected text as content", () => { |
|
const selectedText = "This is the selected highlight text"; |
|
const event = { |
|
kind: 9802, |
|
content: selectedText, |
|
tags: [], |
|
}; |
|
|
|
expect(event.content).toBe(selectedText); |
|
}); |
|
|
|
it("should include context tag", () => { |
|
const context = "This is the surrounding context"; |
|
const tags: string[][] = []; |
|
|
|
if (context) { |
|
tags.push(["context", context]); |
|
} |
|
|
|
expect(tags).toContainEqual(["context", context]); |
|
}); |
|
|
|
it("should include author p-tag with role", () => { |
|
const pubkey = "author-pubkey-hex"; |
|
const tags: string[][] = []; |
|
|
|
if (pubkey) { |
|
tags.push(["p", pubkey, "", "author"]); |
|
} |
|
|
|
expect(tags).toContainEqual(["p", pubkey, "", "author"]); |
|
}); |
|
|
|
it("should include comment tag when comment provided", () => { |
|
const comment = "This is my insightful comment"; |
|
const tags: string[][] = []; |
|
|
|
if (comment.trim()) { |
|
tags.push(["comment", comment.trim()]); |
|
} |
|
|
|
expect(tags).toContainEqual(["comment", comment]); |
|
}); |
|
|
|
it("should not include comment tag when comment is empty", () => { |
|
const comment = ""; |
|
const tags: string[][] = []; |
|
|
|
if (comment.trim()) { |
|
tags.push(["comment", comment.trim()]); |
|
} |
|
|
|
expect(tags).not.toContainEqual(["comment", ""]); |
|
expect(tags.length).toBe(0); |
|
}); |
|
|
|
it("should not include comment tag when comment is only whitespace", () => { |
|
const comment = " "; |
|
const tags: string[][] = []; |
|
|
|
if (comment.trim()) { |
|
tags.push(["comment", comment.trim()]); |
|
} |
|
|
|
expect(tags.length).toBe(0); |
|
}); |
|
}); |
|
|
|
describe("NIP-84 Event Creation - Regular Events", () => { |
|
it("should use 'e' tag for regular events", () => { |
|
const eventId = "regular-event-id"; |
|
const eventAddress = null; // No address means regular event |
|
const tags: string[][] = []; |
|
|
|
if (eventAddress) { |
|
tags.push(["a", eventAddress, ""]); |
|
} else { |
|
tags.push(["e", eventId, ""]); |
|
} |
|
|
|
expect(tags).toContainEqual(["e", eventId, ""]); |
|
}); |
|
|
|
it("should prefer addressable event over regular event", () => { |
|
const eventId = "regular-event-id"; |
|
const eventAddress = "30023:pubkey:d-tag"; |
|
const tags: string[][] = []; |
|
|
|
if (eventAddress) { |
|
tags.push(["a", eventAddress, ""]); |
|
} else { |
|
tags.push(["e", eventId, ""]); |
|
} |
|
|
|
expect(tags).toContainEqual(["a", eventAddress, ""]); |
|
expect(tags).not.toContainEqual(["e", eventId, ""]); |
|
}); |
|
}); |
|
|
|
describe("Complete Event Structure", () => { |
|
it("should create complete highlight event with all required tags", () => { |
|
const selectedText = "Highlighted text"; |
|
const context = "Full context paragraph"; |
|
const pubkey = "author-pubkey"; |
|
const eventAddress = "30023:pubkey:d-tag"; |
|
|
|
const event = { |
|
kind: 9802, |
|
content: selectedText, |
|
tags: [ |
|
["a", eventAddress, ""], |
|
["context", context], |
|
["p", pubkey, "", "author"], |
|
], |
|
}; |
|
|
|
expect(event.kind).toBe(9802); |
|
expect(event.content).toBe(selectedText); |
|
expect(event.tags).toHaveLength(3); |
|
expect(event.tags[0]).toEqual(["a", eventAddress, ""]); |
|
expect(event.tags[1]).toEqual(["context", context]); |
|
expect(event.tags[2]).toEqual(["p", pubkey, "", "author"]); |
|
}); |
|
|
|
it("should create complete quote highlight with comment", () => { |
|
const selectedText = "Highlighted text"; |
|
const context = "Full context paragraph"; |
|
const pubkey = "author-pubkey"; |
|
const eventAddress = "30023:pubkey:d-tag"; |
|
const comment = "My thoughtful comment"; |
|
|
|
const event = { |
|
kind: 9802, |
|
content: selectedText, |
|
tags: [ |
|
["a", eventAddress, ""], |
|
["context", context], |
|
["p", pubkey, "", "author"], |
|
["comment", comment], |
|
], |
|
}; |
|
|
|
expect(event.kind).toBe(9802); |
|
expect(event.content).toBe(selectedText); |
|
expect(event.tags).toHaveLength(4); |
|
expect(event.tags[3]).toEqual(["comment", comment]); |
|
}); |
|
|
|
it("should handle event without context", () => { |
|
const selectedText = "Highlighted text"; |
|
const context = ""; |
|
const pubkey = "author-pubkey"; |
|
const eventId = "event-id"; |
|
|
|
const tags: string[][] = []; |
|
tags.push(["e", eventId, ""]); |
|
if (context) { |
|
tags.push(["context", context]); |
|
} |
|
tags.push(["p", pubkey, "", "author"]); |
|
|
|
expect(tags).toHaveLength(2); |
|
expect(tags).not.toContainEqual(["context", ""]); |
|
}); |
|
}); |
|
|
|
describe("Event Signing and Publishing", () => { |
|
it("should sign event before publishing", async () => { |
|
const mockSigner = { |
|
sign: vi.fn().mockResolvedValue(undefined), |
|
}; |
|
|
|
const mockEvent = { |
|
kind: 9802, |
|
content: "test", |
|
tags: [], |
|
sign: vi.fn().mockResolvedValue(undefined), |
|
publish: vi.fn().mockResolvedValue(undefined), |
|
}; |
|
|
|
await mockEvent.sign(mockSigner); |
|
expect(mockEvent.sign).toHaveBeenCalledWith(mockSigner); |
|
}); |
|
|
|
it("should publish event after signing", async () => { |
|
const mockEvent = { |
|
sign: vi.fn().mockResolvedValue(undefined), |
|
publish: vi.fn().mockResolvedValue(undefined), |
|
}; |
|
|
|
await mockEvent.sign({}); |
|
await mockEvent.publish(); |
|
|
|
expect(mockEvent.publish).toHaveBeenCalled(); |
|
}); |
|
|
|
it("should handle signing errors", async () => { |
|
const mockEvent = { |
|
sign: vi.fn().mockRejectedValue(new Error("Signing failed")), |
|
}; |
|
|
|
await expect(mockEvent.sign({})).rejects.toThrow("Signing failed"); |
|
}); |
|
|
|
it("should handle publishing errors", async () => { |
|
const mockEvent = { |
|
sign: vi.fn().mockResolvedValue(undefined), |
|
publish: vi.fn().mockRejectedValue(new Error("Publishing failed")), |
|
}; |
|
|
|
await mockEvent.sign({}); |
|
await expect(mockEvent.publish()).rejects.toThrow("Publishing failed"); |
|
}); |
|
}); |
|
|
|
describe("Selection Cleanup", () => { |
|
it("should clear selection after successful highlight creation", () => { |
|
const mockSelection = { |
|
removeAllRanges: vi.fn(), |
|
}; |
|
|
|
mockSelection.removeAllRanges(); |
|
expect(mockSelection.removeAllRanges).toHaveBeenCalled(); |
|
}); |
|
|
|
it("should reset selectedText after creation", () => { |
|
let selectedText = "Some text"; |
|
selectedText = ""; |
|
expect(selectedText).toBe(""); |
|
}); |
|
|
|
it("should reset comment after creation", () => { |
|
let comment = "Some comment"; |
|
comment = ""; |
|
expect(comment).toBe(""); |
|
}); |
|
|
|
it("should reset context after creation", () => { |
|
let context = "Some context"; |
|
context = ""; |
|
expect(context).toBe(""); |
|
}); |
|
|
|
it("should close modal after creation", () => { |
|
let showModal = true; |
|
showModal = false; |
|
expect(showModal).toBe(false); |
|
}); |
|
}); |
|
|
|
describe("Cancel Functionality", () => { |
|
it("should clear selection when cancelled", () => { |
|
const mockSelection = { |
|
removeAllRanges: vi.fn(), |
|
}; |
|
|
|
// Simulate cancel |
|
mockSelection.removeAllRanges(); |
|
expect(mockSelection.removeAllRanges).toHaveBeenCalled(); |
|
}); |
|
|
|
it("should reset all state when cancelled", () => { |
|
let selectedText = "text"; |
|
let comment = "comment"; |
|
let context = "context"; |
|
let showModal = true; |
|
|
|
// Simulate cancel |
|
selectedText = ""; |
|
comment = ""; |
|
context = ""; |
|
showModal = false; |
|
|
|
expect(selectedText).toBe(""); |
|
expect(comment).toBe(""); |
|
expect(context).toBe(""); |
|
expect(showModal).toBe(false); |
|
}); |
|
}); |
|
|
|
describe("Feedback Messages", () => { |
|
it("should show success message after creation", () => { |
|
const message = "Highlight created successfully!"; |
|
const type = "success"; |
|
|
|
expect(message).toBe("Highlight created successfully!"); |
|
expect(type).toBe("success"); |
|
}); |
|
|
|
it("should show error message on failure", () => { |
|
const message = "Failed to create highlight. Please try again."; |
|
const type = "error"; |
|
|
|
expect(message).toBe("Failed to create highlight. Please try again."); |
|
expect(type).toBe("error"); |
|
}); |
|
|
|
it("should show error when not signed in", () => { |
|
const message = "Please sign in to create highlights"; |
|
const type = "error"; |
|
|
|
expect(message).toBe("Please sign in to create highlights"); |
|
expect(type).toBe("error"); |
|
}); |
|
|
|
it("should auto-hide feedback after delay", () => { |
|
let showFeedback = true; |
|
|
|
// Simulate timeout |
|
setTimeout(() => { |
|
showFeedback = false; |
|
}, 3000); |
|
|
|
// Initially shown |
|
expect(showFeedback).toBe(true); |
|
}); |
|
}); |
|
|
|
describe("Event Listeners", () => { |
|
it("should add mouseup listener on mount", () => { |
|
const mockAddEventListener = vi.fn(); |
|
document.addEventListener = mockAddEventListener; |
|
|
|
document.addEventListener("mouseup", () => {}); |
|
expect(mockAddEventListener).toHaveBeenCalledWith( |
|
"mouseup", |
|
expect.any(Function), |
|
); |
|
}); |
|
|
|
it("should remove mouseup listener on unmount", () => { |
|
const mockRemoveEventListener = vi.fn(); |
|
document.removeEventListener = mockRemoveEventListener; |
|
|
|
const handler = () => {}; |
|
document.removeEventListener("mouseup", handler); |
|
expect(mockRemoveEventListener).toHaveBeenCalledWith("mouseup", handler); |
|
}); |
|
}); |
|
|
|
describe("Highlight Mode Body Class", () => { |
|
it("should add highlight-mode-active class when active", () => { |
|
const mockClassList = { |
|
add: vi.fn(), |
|
remove: vi.fn(), |
|
}; |
|
document.body.classList = mockClassList as any; |
|
|
|
// Simulate active mode |
|
document.body.classList.add("highlight-mode-active"); |
|
expect(mockClassList.add).toHaveBeenCalledWith("highlight-mode-active"); |
|
}); |
|
|
|
it("should remove highlight-mode-active class when inactive", () => { |
|
const mockClassList = { |
|
add: vi.fn(), |
|
remove: vi.fn(), |
|
}; |
|
document.body.classList = mockClassList as any; |
|
|
|
// Simulate inactive mode |
|
document.body.classList.remove("highlight-mode-active"); |
|
expect(mockClassList.remove).toHaveBeenCalledWith( |
|
"highlight-mode-active", |
|
); |
|
}); |
|
|
|
it("should clean up class on unmount", () => { |
|
const mockClassList = { |
|
add: vi.fn(), |
|
remove: vi.fn(), |
|
}; |
|
document.body.classList = mockClassList as any; |
|
|
|
// Simulate cleanup |
|
document.body.classList.remove("highlight-mode-active"); |
|
expect(mockClassList.remove).toHaveBeenCalledWith( |
|
"highlight-mode-active", |
|
); |
|
}); |
|
}); |
|
|
|
describe("Modal Display", () => { |
|
it("should show modal when text is selected", () => { |
|
let showModal = false; |
|
|
|
// Simulate successful selection |
|
showModal = true; |
|
expect(showModal).toBe(true); |
|
}); |
|
|
|
it("should display selected text in modal", () => { |
|
const selectedText = "This is the selected text"; |
|
const displayText = `"${selectedText}"`; |
|
|
|
expect(displayText).toBe('"This is the selected text"'); |
|
}); |
|
|
|
it("should provide textarea for optional comment", () => { |
|
let comment = ""; |
|
const placeholder = "Share your thoughts about this highlight..."; |
|
|
|
expect(placeholder).toBe("Share your thoughts about this highlight..."); |
|
expect(comment).toBe(""); |
|
}); |
|
|
|
it("should disable buttons while submitting", () => { |
|
const isSubmitting = true; |
|
const disabled = isSubmitting; |
|
|
|
expect(disabled).toBe(true); |
|
}); |
|
|
|
it("should show 'Creating...' text while submitting", () => { |
|
const isSubmitting = true; |
|
const buttonText = isSubmitting ? "Creating..." : "Create Highlight"; |
|
|
|
expect(buttonText).toBe("Creating..."); |
|
}); |
|
|
|
it("should show normal text when not submitting", () => { |
|
const isSubmitting = false; |
|
const buttonText = isSubmitting ? "Creating..." : "Create Highlight"; |
|
|
|
expect(buttonText).toBe("Create Highlight"); |
|
}); |
|
}); |
|
|
|
describe("Callback Execution", () => { |
|
it("should call onHighlightCreated callback after creation", () => { |
|
const mockCallback = vi.fn(); |
|
|
|
// Simulate successful creation |
|
mockCallback(); |
|
|
|
expect(mockCallback).toHaveBeenCalled(); |
|
}); |
|
|
|
it("should not call callback if creation fails", () => { |
|
const mockCallback = vi.fn(); |
|
|
|
// Simulate failed creation - callback not called |
|
expect(mockCallback).not.toHaveBeenCalled(); |
|
}); |
|
}); |
|
|
|
describe("Integration Scenarios", () => { |
|
it("should handle complete highlight workflow", () => { |
|
// Setup |
|
let isActive = true; |
|
let showModal = false; |
|
let selectedText = ""; |
|
const userSignedIn = true; |
|
const selection = { |
|
toString: () => "Selected text for highlighting", |
|
isCollapsed: false, |
|
}; |
|
|
|
// User selects text |
|
if (isActive && userSignedIn && !selection.isCollapsed) { |
|
selectedText = selection.toString(); |
|
showModal = true; |
|
} |
|
|
|
expect(selectedText).toBe("Selected text for highlighting"); |
|
expect(showModal).toBe(true); |
|
}); |
|
|
|
it("should handle complete quote highlight workflow with comment", () => { |
|
// Setup |
|
let isActive = true; |
|
let showModal = false; |
|
let selectedText = ""; |
|
let comment = ""; |
|
const userSignedIn = true; |
|
const selection = { |
|
toString: () => "Selected text", |
|
isCollapsed: false, |
|
}; |
|
|
|
// User selects text |
|
if (isActive && userSignedIn && !selection.isCollapsed) { |
|
selectedText = selection.toString(); |
|
showModal = true; |
|
} |
|
|
|
// User adds comment |
|
comment = "This is insightful"; |
|
|
|
// Create event with comment |
|
const tags: string[][] = []; |
|
if (comment.trim()) { |
|
tags.push(["comment", comment]); |
|
} |
|
|
|
expect(selectedText).toBe("Selected text"); |
|
expect(comment).toBe("This is insightful"); |
|
expect(tags).toContainEqual(["comment", "This is insightful"]); |
|
}); |
|
|
|
it("should reject workflow when user not signed in", () => { |
|
let isActive = true; |
|
let showModal = false; |
|
const userSignedIn = false; |
|
const selection = { |
|
toString: () => "Selected text", |
|
isCollapsed: false, |
|
}; |
|
|
|
// User tries to select text |
|
if (isActive && userSignedIn && !selection.isCollapsed) { |
|
showModal = true; |
|
} |
|
|
|
expect(showModal).toBe(false); |
|
}); |
|
|
|
it("should handle workflow cancellation", () => { |
|
// Setup initial state |
|
let showModal = true; |
|
let selectedText = "Some text"; |
|
let comment = "Some comment"; |
|
const mockSelection = { |
|
removeAllRanges: vi.fn(), |
|
}; |
|
|
|
// User cancels |
|
showModal = false; |
|
selectedText = ""; |
|
comment = ""; |
|
mockSelection.removeAllRanges(); |
|
|
|
expect(showModal).toBe(false); |
|
expect(selectedText).toBe(""); |
|
expect(comment).toBe(""); |
|
expect(mockSelection.removeAllRanges).toHaveBeenCalled(); |
|
}); |
|
}); |
|
});
|
|
|