import { render } from "@testing-library/react" import { afterEach, beforeEach, describe, expect, it, vi } from "vitest" import Highlighter from "./Highlighter" describe("Highlighter", () => { const HIGHLIGHT_CLASS = "customHighlightClass" const UNHIGHLIGHT_CLASS = "customUnhighlightClass" let consoleError: typeof console.error beforeEach(() => { consoleError = console.error // React DEV warnings should fail tests console.error = vi.fn((message: string, ...args: any[]) => { consoleError.call(console, message, ...args) throw new Error(message) }) }) afterEach(() => { console.error = consoleError }) function renderHighlighter(props: Parameters[0]) { return render( , ) } // Helper functions to reduce duplication function getHighlights(container: HTMLElement) { return container.querySelectorAll(`.${HIGHLIGHT_CLASS}`) } function expectHighlightCount(container: HTMLElement, count: number) { const highlights = getHighlights(container) expect(highlights.length).toBe(count) return highlights } function expectNoHighlights(container: HTMLElement, expectedText: string) { expectHighlightCount(container, 0) expect(container.textContent).toBe(expectedText) } function expectSingleHighlight(container: HTMLElement, expectedText: string) { const highlights = expectHighlightCount(container, 1) expect(highlights[0].textContent).toBe(expectedText) return highlights[0] } function renderAndExpectNoHighlights( props: Parameters[0], expectedText: string, ) { const { container } = renderHighlighter(props) expectNoHighlights(container, expectedText) } function renderAndExpectSingleHighlight( props: Parameters[0], expectedText: string, ) { const { container } = renderHighlighter(props) return expectSingleHighlight(container, expectedText) } it("should properly handle an empty array for searchWords", () => { renderAndExpectNoHighlights( { searchWords: [], textToHighlight: "This is text", }, "This is text", ) }) it("should properly handle an empty string for searchWords", () => { renderAndExpectNoHighlights( { searchWords: [""], textToHighlight: "This is text", }, "This is text", ) }) it("should properly handle empty textToHighlight", () => { renderAndExpectNoHighlights( { searchWords: ["search"], textToHighlight: "", }, "", ) }) it("should highlight searchText words that exactly match words in textToHighlight", () => { renderAndExpectSingleHighlight( { searchWords: ["text"], textToHighlight: "This is text", }, "text", ) }) it("should handle unclosed parentheses when autoEscape prop is truthy", () => { renderAndExpectSingleHighlight( { autoEscape: true, searchWords: ["("], textToHighlight: "(This is text)", }, "(", ) }) it("should highlight searchText words that partial-match text in textToHighlight", () => { renderAndExpectSingleHighlight( { searchWords: ["Th"], textToHighlight: "This is text", }, "Th", ) }) it("should highlight multiple occurrences of a searchText word", () => { const { container } = renderHighlighter({ searchWords: ["is"], textToHighlight: "This is text", }) expectHighlightCount(container, 2) expect(container.textContent).toBe("This is text") }) it("should highlight multiple searchText words", () => { const { container } = renderHighlighter({ searchWords: ["This", "text"], textToHighlight: "This is text", }) const highlights = expectHighlightCount(container, 2) expect(container.textContent).toBe("This is text") expect(highlights[0].textContent).toBe("This") expect(highlights[1].textContent).toBe("text") }) it("should match terms in a case insensitive way but show their case-sensitive representation", () => { renderAndExpectSingleHighlight( { searchWords: ["this"], textToHighlight: "This is text", }, "This", ) }) it("should use the highlightClassName if specified", () => { const { container } = renderHighlighter({ searchWords: ["text"], textToHighlight: "This is text", }) const highlight = container.querySelector("mark") expect(highlight).toHaveClass(HIGHLIGHT_CLASS) }) it("should use the highlightStyle if specified", () => { const { container } = renderHighlighter({ highlightStyle: { color: "red" }, searchWords: ["text"], textToHighlight: "This is text", }) const highlight = container.querySelector("mark") expect(highlight).toHaveStyle({ color: "red" }) }) it("should use the unhighlightStyle if specified", () => { const { container } = renderHighlighter({ unhighlightStyle: { color: "gray" }, searchWords: ["text"], textToHighlight: "This is text", }) // Find the unhighlighted span (not the highlighted mark) const spans = container.querySelectorAll("span") const unhighlightSpan = Array.from(spans).find(span => span.classList.contains(UNHIGHLIGHT_CLASS), ) expect(unhighlightSpan).toHaveStyle({ color: "gray" }) }) it("should use the highlightTag if specified", () => { const { container } = renderHighlighter({ autoEscape: true, highlightTag: "span", searchWords: ["text"], textToHighlight: "This is text", }) const highlights = getHighlights(container) expect(highlights[0].tagName).toBe("SPAN") }) it("should support functional components via highlightTag", () => { const HighlightTag = ({ children, highlightIndex, ...props }: any) => ( {children} ) const { container } = renderHighlighter({ autoEscape: true, highlightTag: HighlightTag, searchWords: ["text"], textToHighlight: "This is text", }) const highlights = getHighlights(container) expect(highlights[0].tagName).toBe("SPAN") expect(highlights[0]).toHaveAttribute("data-highlight-index", "0") }) it("should apply activeClassName to the match specified by activeIndex", () => { const activeClassName = "active" const { container } = renderHighlighter({ activeIndex: 1, activeClassName, searchWords: ["text"], textToHighlight: "This is text which should have this text highlighted", }) const highlights = container.querySelectorAll("mark") expect(highlights[1]).toHaveClass(activeClassName) }) it("should apply activeStyle to the match specified by activeIndex", () => { const activeStyle = { color: "red" } const { container } = renderHighlighter({ activeIndex: 1, activeStyle, searchWords: ["text"], textToHighlight: "This is text which should have this text highlighted", }) const highlights = container.querySelectorAll("mark") expect(highlights[1]).toHaveStyle({ color: "red" }) }) it("should support caseSensitive search", () => { const { container } = renderHighlighter({ caseSensitive: true, searchWords: ["th"], textToHighlight: "This the three time", }) const highlights = expectHighlightCount(container, 2) expect(highlights[0].textContent).toBe("th") expect(highlights[1].textContent).toBe("th") }) it("should support custom findChunks prop function", () => { const { container } = renderHighlighter({ findChunks: () => [ { start: 0, end: 1 }, { start: 5, end: 7 }, ], searchWords: ["xxx"], textToHighlight: "This is text", }) const highlights = expectHighlightCount(container, 2) expect(highlights[0].textContent).toBe("T") expect(highlights[1].textContent).toBe("is") const { container: container2 } = renderHighlighter({ findChunks: () => [], searchWords: ["This"], textToHighlight: "This is text", }) expectHighlightCount(container2, 0) }) })