mirror of
https://github.com/vercel/streamdown.git
synced 2026-09-19 02:18:37 +08:00
e129f09aa5
* feat: add translations/i18n support via translations prop Implements #190 - adds a `translations` prop to the Streamdown component that allows customizing all UI strings for internationalization. Changes: - Add `packages/streamdown/lib/translations-context.tsx` with StreamdownTranslations type, defaultTranslations, TranslationsContext, and useTranslations hook - Add `translations?: Partial<StreamdownTranslations>` prop to Streamdown - Wrap render output with TranslationsContext.Provider (both static and streaming modes) - Export StreamdownTranslations type and defaultTranslations from index - Replace all hardcoded UI strings in sub-components with translations: - code-block/copy-button: copyCode - code-block/download-button: downloadFile - mermaid/download-button: downloadDiagram, downloadDiagramAs{Svg,Png,Mmd}, mermaidFormat{Svg,Png,Mmd} - mermaid/fullscreen-button: viewFullscreen, exitFullscreen - table/copy-dropdown: copyTable, copyTableAs{Markdown,Csv,Tsv}, tableFormat{Markdown,Csv,Tsv} - table/download-dropdown: downloadTable, downloadTableAs{Csv,Markdown}, tableFormat{Csv,Markdown} - image: imageNotAvailable, downloadImage - link-modal: openExternalLink, externalLinkWarning, close, copyLink, copied, openLink - Add __tests__/translations.test.tsx with 12 tests covering defaults, custom translations, partial overrides, and context access - Update download-dropdown.test.tsx to match new translation-based title All 804 tests pass. * Add changeset for translations prop * Fix misplaced comments in translations type and broken test type assertion Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Use value equality for translations memo to support inline objects Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Dmitrii Troitskii <jsleitor@gmail.com> Co-authored-by: Hayden Bleasel <hello@haydenbleasel.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
739 lines
20 KiB
TypeScript
739 lines
20 KiB
TypeScript
import { fireEvent, render } from "@testing-library/react";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { StreamdownContext } from "../index";
|
|
import {
|
|
TableDownloadButton,
|
|
TableDownloadDropdown,
|
|
} from "../lib/table/download-dropdown";
|
|
|
|
// Setup global URL mocks before any tests run
|
|
if (typeof URL.createObjectURL === "undefined") {
|
|
URL.createObjectURL = vi.fn();
|
|
URL.revokeObjectURL = vi.fn();
|
|
}
|
|
|
|
// Mock the utils
|
|
vi.mock("../lib/utils", async () => {
|
|
const actual = await vi.importActual("../lib/utils");
|
|
return {
|
|
...actual,
|
|
save: vi.fn(),
|
|
};
|
|
});
|
|
|
|
describe("TableDownloadButton", () => {
|
|
let mockTable: HTMLTableElement;
|
|
let mockWrapper: HTMLDivElement;
|
|
|
|
beforeEach(() => {
|
|
// Create a mock table structure
|
|
mockTable = document.createElement("table");
|
|
mockTable.innerHTML = `
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>Age</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td>John</td>
|
|
<td>30</td>
|
|
</tr>
|
|
</tbody>
|
|
`;
|
|
|
|
mockWrapper = document.createElement("div");
|
|
mockWrapper.setAttribute("data-streamdown", "table-wrapper");
|
|
mockWrapper.appendChild(mockTable);
|
|
document.body.appendChild(mockWrapper);
|
|
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
afterEach(() => {
|
|
document.body.removeChild(mockWrapper);
|
|
});
|
|
|
|
it("should render button with default icon", () => {
|
|
const { container } = render(
|
|
<StreamdownContext.Provider
|
|
value={{
|
|
isAnimating: false,
|
|
mode: "streaming",
|
|
shikiTheme: ["github-light", "github-dark"],
|
|
controls: true,
|
|
}}
|
|
>
|
|
<TableDownloadButton />
|
|
</StreamdownContext.Provider>,
|
|
{ container: mockWrapper }
|
|
);
|
|
|
|
const button = container.querySelector("button");
|
|
expect(button).toBeTruthy();
|
|
expect(button?.getAttribute("title")).toContain("CSV");
|
|
});
|
|
|
|
it("should render button with custom children", () => {
|
|
const { container } = render(
|
|
<StreamdownContext.Provider
|
|
value={{
|
|
isAnimating: false,
|
|
mode: "streaming",
|
|
shikiTheme: ["github-light", "github-dark"],
|
|
controls: true,
|
|
}}
|
|
>
|
|
<TableDownloadButton>
|
|
<span>Custom Download</span>
|
|
</TableDownloadButton>
|
|
</StreamdownContext.Provider>,
|
|
{ container: mockWrapper }
|
|
);
|
|
|
|
expect(container.textContent).toContain("Custom Download");
|
|
});
|
|
|
|
it("should be disabled when animating", () => {
|
|
const { container } = render(
|
|
<StreamdownContext.Provider
|
|
value={{
|
|
isAnimating: true,
|
|
mode: "streaming",
|
|
shikiTheme: ["github-light", "github-dark"],
|
|
controls: true,
|
|
}}
|
|
>
|
|
<TableDownloadButton />
|
|
</StreamdownContext.Provider>,
|
|
{ container: mockWrapper }
|
|
);
|
|
|
|
const button = container.querySelector("button");
|
|
expect(button?.hasAttribute("disabled")).toBe(true);
|
|
});
|
|
|
|
it("should download CSV by default", () => {
|
|
const onDownload = vi.fn();
|
|
|
|
// Mock DOM methods for download
|
|
const mockAnchor = {
|
|
href: "",
|
|
download: "",
|
|
click: vi.fn(),
|
|
} as unknown as HTMLAnchorElement;
|
|
|
|
const originalCreateElement = document.createElement.bind(document);
|
|
const createElementSpy = vi
|
|
.spyOn(document, "createElement")
|
|
.mockImplementation((tag) => {
|
|
if (tag === "a") {
|
|
return mockAnchor;
|
|
}
|
|
return originalCreateElement(tag);
|
|
});
|
|
const appendChildSpy = vi
|
|
.spyOn(document.body, "appendChild")
|
|
.mockImplementation(() => mockAnchor);
|
|
const removeChildSpy = vi
|
|
.spyOn(document.body, "removeChild")
|
|
.mockImplementation(() => mockAnchor);
|
|
const createObjectURLSpy = vi
|
|
.spyOn(URL, "createObjectURL")
|
|
.mockReturnValue("blob:mock");
|
|
const revokeObjectURLSpy = vi
|
|
.spyOn(URL, "revokeObjectURL")
|
|
.mockImplementation(() => {
|
|
// Intentionally empty mock
|
|
});
|
|
|
|
// Create wrapper and render button inside it properly
|
|
const wrapper = document.createElement("div");
|
|
wrapper.setAttribute("data-streamdown", "table-wrapper");
|
|
wrapper.appendChild(mockTable.cloneNode(true));
|
|
document.body.appendChild(wrapper);
|
|
|
|
const buttonDiv = document.createElement("div");
|
|
wrapper.appendChild(buttonDiv);
|
|
|
|
render(
|
|
<StreamdownContext.Provider
|
|
value={{
|
|
isAnimating: false,
|
|
mode: "streaming",
|
|
shikiTheme: ["github-light", "github-dark"],
|
|
controls: true,
|
|
}}
|
|
>
|
|
<TableDownloadButton onDownload={onDownload} />
|
|
</StreamdownContext.Provider>,
|
|
{ container: buttonDiv }
|
|
);
|
|
|
|
const button = buttonDiv.querySelector("button");
|
|
expect(button).toBeTruthy();
|
|
if (button) {
|
|
fireEvent.click(button);
|
|
}
|
|
|
|
expect(onDownload).toHaveBeenCalled();
|
|
|
|
document.body.removeChild(wrapper);
|
|
createElementSpy.mockRestore();
|
|
appendChildSpy.mockRestore();
|
|
removeChildSpy.mockRestore();
|
|
createObjectURLSpy.mockRestore();
|
|
revokeObjectURLSpy.mockRestore();
|
|
});
|
|
|
|
it("should download markdown when format is specified", () => {
|
|
const onDownload = vi.fn();
|
|
|
|
// Mock DOM methods for download
|
|
const mockAnchor = {
|
|
href: "",
|
|
download: "",
|
|
click: vi.fn(),
|
|
} as unknown as HTMLAnchorElement;
|
|
|
|
const originalCreateElement = document.createElement.bind(document);
|
|
const createElementSpy = vi
|
|
.spyOn(document, "createElement")
|
|
.mockImplementation((tag) => {
|
|
if (tag === "a") {
|
|
return mockAnchor;
|
|
}
|
|
return originalCreateElement(tag);
|
|
});
|
|
const appendChildSpy = vi
|
|
.spyOn(document.body, "appendChild")
|
|
.mockImplementation(() => mockAnchor);
|
|
const removeChildSpy = vi
|
|
.spyOn(document.body, "removeChild")
|
|
.mockImplementation(() => mockAnchor);
|
|
const createObjectURLSpy = vi
|
|
.spyOn(URL, "createObjectURL")
|
|
.mockReturnValue("blob:mock");
|
|
const revokeObjectURLSpy = vi
|
|
.spyOn(URL, "revokeObjectURL")
|
|
.mockImplementation(() => {
|
|
// Intentionally empty mock
|
|
});
|
|
|
|
// Create wrapper and render button inside it properly
|
|
const wrapper = document.createElement("div");
|
|
wrapper.setAttribute("data-streamdown", "table-wrapper");
|
|
wrapper.appendChild(mockTable.cloneNode(true));
|
|
document.body.appendChild(wrapper);
|
|
|
|
const buttonDiv = document.createElement("div");
|
|
wrapper.appendChild(buttonDiv);
|
|
|
|
render(
|
|
<StreamdownContext.Provider
|
|
value={{
|
|
isAnimating: false,
|
|
mode: "streaming",
|
|
shikiTheme: ["github-light", "github-dark"],
|
|
controls: true,
|
|
}}
|
|
>
|
|
<TableDownloadButton format="markdown" onDownload={onDownload} />
|
|
</StreamdownContext.Provider>,
|
|
{ container: buttonDiv }
|
|
);
|
|
|
|
const button = buttonDiv.querySelector("button");
|
|
expect(button?.getAttribute("title")).toContain("Markdown");
|
|
|
|
expect(button).toBeTruthy();
|
|
if (button) {
|
|
fireEvent.click(button);
|
|
}
|
|
|
|
expect(onDownload).toHaveBeenCalled();
|
|
|
|
document.body.removeChild(wrapper);
|
|
createElementSpy.mockRestore();
|
|
appendChildSpy.mockRestore();
|
|
removeChildSpy.mockRestore();
|
|
createObjectURLSpy.mockRestore();
|
|
revokeObjectURLSpy.mockRestore();
|
|
});
|
|
|
|
it("should use custom filename", () => {
|
|
const onDownload = vi.fn();
|
|
|
|
// Mock DOM methods for download
|
|
const mockAnchor = {
|
|
href: "",
|
|
download: "",
|
|
click: vi.fn(),
|
|
} as unknown as HTMLAnchorElement;
|
|
|
|
const originalCreateElement = document.createElement.bind(document);
|
|
const createElementSpy = vi
|
|
.spyOn(document, "createElement")
|
|
.mockImplementation((tag) => {
|
|
if (tag === "a") {
|
|
return mockAnchor;
|
|
}
|
|
return originalCreateElement(tag);
|
|
});
|
|
const appendChildSpy = vi
|
|
.spyOn(document.body, "appendChild")
|
|
.mockImplementation(() => mockAnchor);
|
|
const removeChildSpy = vi
|
|
.spyOn(document.body, "removeChild")
|
|
.mockImplementation(() => mockAnchor);
|
|
const createObjectURLSpy = vi
|
|
.spyOn(URL, "createObjectURL")
|
|
.mockReturnValue("blob:mock");
|
|
const revokeObjectURLSpy = vi
|
|
.spyOn(URL, "revokeObjectURL")
|
|
.mockImplementation(() => {
|
|
// Intentionally empty mock
|
|
});
|
|
|
|
// Create wrapper and render button inside it properly
|
|
const wrapper = document.createElement("div");
|
|
wrapper.setAttribute("data-streamdown", "table-wrapper");
|
|
wrapper.appendChild(mockTable.cloneNode(true));
|
|
document.body.appendChild(wrapper);
|
|
|
|
const buttonDiv = document.createElement("div");
|
|
wrapper.appendChild(buttonDiv);
|
|
|
|
render(
|
|
<StreamdownContext.Provider
|
|
value={{
|
|
isAnimating: false,
|
|
mode: "streaming",
|
|
shikiTheme: ["github-light", "github-dark"],
|
|
controls: true,
|
|
}}
|
|
>
|
|
<TableDownloadButton filename="custom-table" onDownload={onDownload} />
|
|
</StreamdownContext.Provider>,
|
|
{ container: buttonDiv }
|
|
);
|
|
|
|
const button = buttonDiv.querySelector("button");
|
|
expect(button).toBeTruthy();
|
|
if (button) {
|
|
fireEvent.click(button);
|
|
}
|
|
|
|
expect(onDownload).toHaveBeenCalled();
|
|
|
|
document.body.removeChild(wrapper);
|
|
createElementSpy.mockRestore();
|
|
appendChildSpy.mockRestore();
|
|
removeChildSpy.mockRestore();
|
|
createObjectURLSpy.mockRestore();
|
|
revokeObjectURLSpy.mockRestore();
|
|
});
|
|
|
|
it("should call onError when table is not found", () => {
|
|
const onError = vi.fn();
|
|
|
|
// Render button outside of table wrapper
|
|
const buttonDiv = document.createElement("div");
|
|
document.body.appendChild(buttonDiv);
|
|
|
|
render(
|
|
<StreamdownContext.Provider
|
|
value={{
|
|
isAnimating: false,
|
|
mode: "streaming",
|
|
shikiTheme: ["github-light", "github-dark"],
|
|
controls: true,
|
|
}}
|
|
>
|
|
<TableDownloadButton onError={onError} />
|
|
</StreamdownContext.Provider>,
|
|
{ container: buttonDiv }
|
|
);
|
|
|
|
const button = buttonDiv.querySelector("button");
|
|
expect(button).toBeTruthy();
|
|
if (button) {
|
|
fireEvent.click(button);
|
|
}
|
|
|
|
expect(onError).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
message: "Table not found",
|
|
})
|
|
);
|
|
|
|
document.body.removeChild(buttonDiv);
|
|
});
|
|
|
|
it("should apply custom className", () => {
|
|
const { container } = render(
|
|
<StreamdownContext.Provider
|
|
value={{
|
|
isAnimating: false,
|
|
mode: "streaming",
|
|
shikiTheme: ["github-light", "github-dark"],
|
|
controls: true,
|
|
}}
|
|
>
|
|
<TableDownloadButton className="custom-button-class" />
|
|
</StreamdownContext.Provider>,
|
|
{ container: mockWrapper }
|
|
);
|
|
|
|
const button = container.querySelector("button");
|
|
expect(button?.className).toContain("custom-button-class");
|
|
});
|
|
});
|
|
|
|
describe("TableDownloadDropdown", () => {
|
|
let mockTable: HTMLTableElement;
|
|
let mockWrapper: HTMLDivElement;
|
|
|
|
beforeEach(() => {
|
|
// Create a mock table structure
|
|
mockTable = document.createElement("table");
|
|
mockTable.innerHTML = `
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>Age</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td>John</td>
|
|
<td>30</td>
|
|
</tr>
|
|
</tbody>
|
|
`;
|
|
|
|
mockWrapper = document.createElement("div");
|
|
mockWrapper.setAttribute("data-streamdown", "table-wrapper");
|
|
mockWrapper.appendChild(mockTable);
|
|
document.body.appendChild(mockWrapper);
|
|
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
afterEach(() => {
|
|
document.body.removeChild(mockWrapper);
|
|
});
|
|
|
|
it("should render dropdown button", () => {
|
|
const { container } = render(
|
|
<StreamdownContext.Provider
|
|
value={{
|
|
isAnimating: false,
|
|
mode: "streaming",
|
|
shikiTheme: ["github-light", "github-dark"],
|
|
controls: true,
|
|
}}
|
|
>
|
|
<TableDownloadDropdown />
|
|
</StreamdownContext.Provider>,
|
|
{ container: mockWrapper }
|
|
);
|
|
|
|
const button = container.querySelector('button[title="Download table"]');
|
|
expect(button).toBeTruthy();
|
|
});
|
|
|
|
it("should render custom children", () => {
|
|
const { container } = render(
|
|
<StreamdownContext.Provider
|
|
value={{
|
|
isAnimating: false,
|
|
mode: "streaming",
|
|
shikiTheme: ["github-light", "github-dark"],
|
|
controls: true,
|
|
}}
|
|
>
|
|
<TableDownloadDropdown>
|
|
<span>Custom Text</span>
|
|
</TableDownloadDropdown>
|
|
</StreamdownContext.Provider>,
|
|
{ container: mockWrapper }
|
|
);
|
|
|
|
expect(container.textContent).toContain("Custom Text");
|
|
});
|
|
|
|
it("should be disabled when animating", () => {
|
|
const { container } = render(
|
|
<StreamdownContext.Provider
|
|
value={{
|
|
isAnimating: true,
|
|
mode: "streaming",
|
|
shikiTheme: ["github-light", "github-dark"],
|
|
controls: true,
|
|
}}
|
|
>
|
|
<TableDownloadDropdown />
|
|
</StreamdownContext.Provider>,
|
|
{ container: mockWrapper }
|
|
);
|
|
|
|
const button = container.querySelector("button");
|
|
expect(button?.hasAttribute("disabled")).toBe(true);
|
|
});
|
|
|
|
it("should toggle dropdown on button click", () => {
|
|
const { container } = render(
|
|
<StreamdownContext.Provider
|
|
value={{
|
|
isAnimating: false,
|
|
mode: "streaming",
|
|
shikiTheme: ["github-light", "github-dark"],
|
|
controls: true,
|
|
}}
|
|
>
|
|
<TableDownloadDropdown />
|
|
</StreamdownContext.Provider>,
|
|
{ container: mockWrapper }
|
|
);
|
|
|
|
const button = container.querySelector('button[title="Download table"]');
|
|
|
|
// Dropdown should be closed initially
|
|
expect(container.querySelector(".absolute")).toBeFalsy();
|
|
|
|
// Click to open
|
|
expect(button).toBeTruthy();
|
|
if (button) {
|
|
fireEvent.click(button);
|
|
}
|
|
expect(container.querySelector(".absolute")).toBeTruthy();
|
|
|
|
// Click to close
|
|
if (button) {
|
|
fireEvent.click(button);
|
|
}
|
|
expect(container.querySelector(".absolute")).toBeFalsy();
|
|
});
|
|
|
|
it("should show CSV and Markdown options when open", () => {
|
|
const { container, getByText } = render(
|
|
<StreamdownContext.Provider
|
|
value={{
|
|
isAnimating: false,
|
|
mode: "streaming",
|
|
shikiTheme: ["github-light", "github-dark"],
|
|
controls: true,
|
|
}}
|
|
>
|
|
<TableDownloadDropdown />
|
|
</StreamdownContext.Provider>,
|
|
{ container: mockWrapper }
|
|
);
|
|
|
|
const button = container.querySelector('button[title="Download table"]');
|
|
expect(button).toBeTruthy();
|
|
if (button) {
|
|
fireEvent.click(button);
|
|
}
|
|
|
|
expect(getByText("CSV")).toBeTruthy();
|
|
expect(getByText("Markdown")).toBeTruthy();
|
|
});
|
|
|
|
it("should download CSV when CSV option is clicked", async () => {
|
|
const { save } = await import("../lib/utils");
|
|
const onDownload = vi.fn();
|
|
|
|
// Append dropdown to wrapper
|
|
const dropdownContainer = document.createElement("div");
|
|
mockWrapper.appendChild(dropdownContainer);
|
|
|
|
const { container, getByText } = render(
|
|
<StreamdownContext.Provider
|
|
value={{
|
|
isAnimating: false,
|
|
mode: "streaming",
|
|
shikiTheme: ["github-light", "github-dark"],
|
|
controls: true,
|
|
}}
|
|
>
|
|
<TableDownloadDropdown onDownload={onDownload} />
|
|
</StreamdownContext.Provider>,
|
|
{ container: dropdownContainer }
|
|
);
|
|
|
|
const button = container.querySelector('button[title="Download table"]');
|
|
expect(button).toBeTruthy();
|
|
if (button) {
|
|
fireEvent.click(button);
|
|
}
|
|
|
|
const csvButton = getByText("CSV");
|
|
fireEvent.click(csvButton);
|
|
|
|
expect(save).toHaveBeenCalledWith(
|
|
"table.csv",
|
|
expect.stringContaining("Name,Age"),
|
|
"text/csv"
|
|
);
|
|
expect(onDownload).toHaveBeenCalledWith("csv");
|
|
|
|
mockWrapper.removeChild(dropdownContainer);
|
|
});
|
|
|
|
it("should download Markdown when Markdown option is clicked", async () => {
|
|
const { save } = await import("../lib/utils");
|
|
const onDownload = vi.fn();
|
|
|
|
// Append dropdown to wrapper
|
|
const dropdownContainer = document.createElement("div");
|
|
mockWrapper.appendChild(dropdownContainer);
|
|
|
|
const { container, getByText } = render(
|
|
<StreamdownContext.Provider
|
|
value={{
|
|
isAnimating: false,
|
|
mode: "streaming",
|
|
shikiTheme: ["github-light", "github-dark"],
|
|
controls: true,
|
|
}}
|
|
>
|
|
<TableDownloadDropdown onDownload={onDownload} />
|
|
</StreamdownContext.Provider>,
|
|
{ container: dropdownContainer }
|
|
);
|
|
|
|
const button = container.querySelector('button[title="Download table"]');
|
|
expect(button).toBeTruthy();
|
|
if (button) {
|
|
fireEvent.click(button);
|
|
}
|
|
|
|
const markdownButton = getByText("Markdown");
|
|
fireEvent.click(markdownButton);
|
|
|
|
expect(save).toHaveBeenCalledWith(
|
|
"table.md",
|
|
expect.stringContaining("| Name | Age |"),
|
|
"text/markdown"
|
|
);
|
|
expect(onDownload).toHaveBeenCalledWith("markdown");
|
|
|
|
mockWrapper.removeChild(dropdownContainer);
|
|
});
|
|
|
|
it("should close dropdown on outside click", () => {
|
|
const { container } = render(
|
|
<StreamdownContext.Provider
|
|
value={{
|
|
isAnimating: false,
|
|
mode: "streaming",
|
|
shikiTheme: ["github-light", "github-dark"],
|
|
controls: true,
|
|
}}
|
|
>
|
|
<TableDownloadDropdown />
|
|
</StreamdownContext.Provider>,
|
|
{ container: mockWrapper }
|
|
);
|
|
|
|
const button = container.querySelector('button[title="Download table"]');
|
|
expect(button).toBeTruthy();
|
|
if (button) {
|
|
fireEvent.click(button);
|
|
}
|
|
|
|
expect(container.querySelector(".absolute")).toBeTruthy();
|
|
|
|
// Click outside
|
|
fireEvent.mouseDown(document.body);
|
|
|
|
expect(container.querySelector(".absolute")).toBeFalsy();
|
|
});
|
|
|
|
it("should call onError when table is not found", () => {
|
|
const onError = vi.fn();
|
|
|
|
// Render dropdown outside of table wrapper
|
|
const dropdownDiv = document.createElement("div");
|
|
document.body.appendChild(dropdownDiv);
|
|
|
|
const { container, getByText } = render(
|
|
<StreamdownContext.Provider
|
|
value={{
|
|
isAnimating: false,
|
|
mode: "streaming",
|
|
shikiTheme: ["github-light", "github-dark"],
|
|
controls: true,
|
|
}}
|
|
>
|
|
<TableDownloadDropdown onError={onError} />
|
|
</StreamdownContext.Provider>,
|
|
{ container: dropdownDiv }
|
|
);
|
|
|
|
const button = container.querySelector('button[title="Download table"]');
|
|
expect(button).toBeTruthy();
|
|
if (button) {
|
|
fireEvent.click(button);
|
|
}
|
|
|
|
const csvButton = getByText("CSV");
|
|
fireEvent.click(csvButton);
|
|
|
|
expect(onError).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
message: "Table not found",
|
|
})
|
|
);
|
|
|
|
document.body.removeChild(dropdownDiv);
|
|
});
|
|
|
|
it("should apply custom className", () => {
|
|
const { container } = render(
|
|
<StreamdownContext.Provider
|
|
value={{
|
|
isAnimating: false,
|
|
mode: "streaming",
|
|
shikiTheme: ["github-light", "github-dark"],
|
|
controls: true,
|
|
}}
|
|
>
|
|
<TableDownloadDropdown className="custom-dropdown-class" />
|
|
</StreamdownContext.Provider>,
|
|
{ container: mockWrapper }
|
|
);
|
|
|
|
const button = container.querySelector("button");
|
|
expect(button?.className).toContain("custom-dropdown-class");
|
|
});
|
|
|
|
it("should cleanup event listener on unmount", () => {
|
|
const removeEventListenerSpy = vi.spyOn(document, "removeEventListener");
|
|
|
|
const { unmount } = render(
|
|
<StreamdownContext.Provider
|
|
value={{
|
|
isAnimating: false,
|
|
mode: "streaming",
|
|
shikiTheme: ["github-light", "github-dark"],
|
|
controls: true,
|
|
}}
|
|
>
|
|
<TableDownloadDropdown />
|
|
</StreamdownContext.Provider>,
|
|
{ container: mockWrapper }
|
|
);
|
|
|
|
unmount();
|
|
|
|
expect(removeEventListenerSpy).toHaveBeenCalledWith(
|
|
"mousedown",
|
|
expect.any(Function)
|
|
);
|
|
});
|
|
});
|