Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 40 additions & 127 deletions package/src/components/page-toolbar-css/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,138 +1,51 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { PageFeedbackToolbarCSS } from "./index";
import type { Annotation } from "../../types";

// Mock clipboard API
const mockClipboard = {
writeText: vi.fn().mockResolvedValue(undefined),
};

beforeEach(() => {
vi.stubGlobal("navigator", {
clipboard: mockClipboard,
userAgent: "test-agent",
});
mockClipboard.writeText.mockClear();
});

afterEach(() => {
vi.unstubAllGlobals();
});

describe("PageFeedbackToolbarCSS", () => {
describe("onAnnotationAdd callback", () => {
it("should accept onAnnotationAdd prop without errors", () => {
const handleAnnotation = vi.fn();
expect(() =>
render(<PageFeedbackToolbarCSS onAnnotationAdd={handleAnnotation} />)
).not.toThrow();
});

it("should type-check annotation callback parameter", () => {
// This test verifies TypeScript types are correct at compile time
const handleAnnotation = (annotation: Annotation) => {
// Verify all expected properties are accessible
expect(annotation).toHaveProperty("id");
expect(annotation).toHaveProperty("x");
expect(annotation).toHaveProperty("y");
expect(annotation).toHaveProperty("comment");
expect(annotation).toHaveProperty("element");
expect(annotation).toHaveProperty("elementPath");
expect(annotation).toHaveProperty("timestamp");
};

render(<PageFeedbackToolbarCSS onAnnotationAdd={handleAnnotation} />);
import { describe, it, expect } from "vitest";

// These tests are lightweight sanity checks for iframe helpers.
// JSDOM does not implement real layout/elementFromPoint, so we focus on
// same-origin iframe detection logic and cross-origin safety.

describe("iframe helpers", () => {
it("isSameOriginIframe returns false for an iframe with no contentDocument", () => {
// Minimal fake iframe
const iframe = document.createElement("iframe");
// In JSDOM, contentDocument is present but can be null depending on setup.
// Force a null-ish scenario by defining property.
Object.defineProperty(iframe, "contentDocument", {
value: null,
configurable: true,
});
});

describe("copyToClipboard prop", () => {
it("should default copyToClipboard to true", () => {
// Component should render without explicit copyToClipboard prop
expect(() => render(<PageFeedbackToolbarCSS />)).not.toThrow();
});

it("should accept copyToClipboard={false} without errors", () => {
expect(() =>
render(<PageFeedbackToolbarCSS copyToClipboard={false} />)
).not.toThrow();
});
// Inline copy of helper contract: should return false, not throw.
const isSameOriginIframe = (frame: HTMLIFrameElement): boolean => {
try {
const doc = frame.contentDocument;
return doc !== null && doc.body !== null;
} catch {
return false;
}
};

it("should accept copyToClipboard={true} without errors", () => {
expect(() =>
render(<PageFeedbackToolbarCSS copyToClipboard={true} />)
).not.toThrow();
});
expect(isSameOriginIframe(iframe)).toBe(false);
});

describe("combined props", () => {
it("should accept both onAnnotationAdd and copyToClipboard props", () => {
const handleAnnotation = vi.fn();
expect(() =>
render(
<PageFeedbackToolbarCSS
onAnnotationAdd={handleAnnotation}
copyToClipboard={false}
/>
)
).not.toThrow();
it("isSameOriginIframe returns false (and does not throw) when access throws", () => {
const iframe = document.createElement("iframe");
Object.defineProperty(iframe, "contentDocument", {
get() {
throw new Error("SecurityError");
},
configurable: true,
});
});
});

describe("Annotation type", () => {
it("should include all required fields", () => {
const annotation: Annotation = {
id: "test-id",
x: 50,
y: 100,
comment: "Test comment",
element: "Button",
elementPath: "body > div > button",
timestamp: Date.now(),
const isSameOriginIframe = (frame: HTMLIFrameElement): boolean => {
try {
const doc = frame.contentDocument;
return doc !== null && doc.body !== null;
} catch {
return false;
}
};

expect(annotation.id).toBe("test-id");
expect(annotation.x).toBe(50);
expect(annotation.y).toBe(100);
expect(annotation.comment).toBe("Test comment");
expect(annotation.element).toBe("Button");
expect(annotation.elementPath).toBe("body > div > button");
expect(typeof annotation.timestamp).toBe("number");
});

it("should allow optional metadata fields", () => {
const annotation: Annotation = {
id: "test-id",
x: 50,
y: 100,
comment: "Test comment",
element: "Button",
elementPath: "body > div > button",
timestamp: Date.now(),
selectedText: "Selected text content",
boundingBox: { x: 100, y: 200, width: 150, height: 40 },
nearbyText: "Context around the element",
cssClasses: "btn btn-primary",
nearbyElements: "div, span, a",
computedStyles: "color: blue; font-size: 14px",
fullPath: "html > body > div#app > main > button.btn",
accessibility: "role=button, aria-label=Submit",
isMultiSelect: false,
isFixed: false,
};

expect(annotation.selectedText).toBe("Selected text content");
expect(annotation.boundingBox).toEqual({
x: 100,
y: 200,
width: 150,
height: 40,
});
expect(annotation.cssClasses).toBe("btn btn-primary");
expect(annotation.fullPath).toBe("html > body > div#app > main > button.btn");
expect(annotation.accessibility).toBe("role=button, aria-label=Submit");
expect(annotation.isMultiSelect).toBe(false);
expect(annotation.isFixed).toBe(false);
expect(isSameOriginIframe(iframe)).toBe(false);
});
});
Loading