Skip to content
Merged
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
3 changes: 3 additions & 0 deletions packages/react-tei/src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,7 @@ export const en: Translation = {
enter: "Enter full screen mode",
exit: "Exit full screen mode",
},
figure: {
unloaded: "Image not loaded",
},
};
3 changes: 3 additions & 0 deletions packages/react-tei/src/i18n/locales/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ export const fr = {
enter: "Passer en mode plein écran",
exit: "Quitter le mode plein écran",
},
figure: {
unloaded: "Image non chargée",
},
};

export type Translation = typeof fr;
48 changes: 48 additions & 0 deletions packages/react-tei/src/tags/Figure.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,52 @@ describe("Figure", () => {
expect(screen.getByRole("cell", { name: "Data 1" })).toBeVisible();
expect(screen.getByRole("cell", { name: "Data 2" })).toBeVisible();
});
it("should render unloaded figure for non-table types", async () => {
const jsonDocument: DocumentJson = {
tag: "figure",
attributes: { "@type": "image", "@xml:id": "f1" },
value: [],
};

const screen = await render(<Figure data={jsonDocument} />, {
wrapper: ({ children }) => (
<TagCatalogProvider tagCatalog={tagCatalog}>
{children}
</TagCatalogProvider>
),
});

expect(screen.getByText("figure.unloaded")).toBeVisible();
});

it("should render figure.unloaded along figure head and figDesc when they are presents", async () => {
const jsonDocument: DocumentJson = {
tag: "figure",
attributes: { "@type": "image", "@xml:id": "f2" },
value: [
{
tag: "head",
attributes: {},
value: "This is the figure head",
},
{
tag: "figDesc",
attributes: {},
value: "This is the figure description",
},
],
};

const screen = await render(<Figure data={jsonDocument} />, {
wrapper: ({ children }) => (
<TagCatalogProvider tagCatalog={tagCatalog}>
{children}
</TagCatalogProvider>
),
});

expect(screen.getByText("figure.unloaded")).toBeVisible();
expect(screen.getByText("This is the figure head")).toBeVisible();
expect(screen.getByText("This is the figure description")).toBeVisible();
});
});
91 changes: 78 additions & 13 deletions packages/react-tei/src/tags/Figure.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,87 @@
import { Card, CardContent, CardMedia } from "@mui/material";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { DebugTag } from "../debug/DebugTag";
import { FigureTable } from "./figure/FigureTable";
import { figureTagCatalog } from "./figure/figureTagCatalog";
import { TagCatalogProvider } from "./TagCatalogProvider";
import type { ComponentProps } from "./type";
import { Value } from "./Value";

export function Figure({ data }: ComponentProps) {
const type = data.attributes?.["@type"];
const { t } = useTranslation();
const value = useMemo(() => {
if (!Array.isArray(data.value)) {
return data.value;
}
return data.value.filter(
({ tag }) => !["graphic", "link", "highlightedText"].includes(tag),
);
}, [data.value]);

switch (type) {
case "table":
return <FigureTable data={data} />;
default:
return (
<DebugTag
tag={data.tag}
attributes={data.attributes}
message={`Unsupported figure type ${type}`}
payload={data.value}
type="error"
/>
);
if (!Array.isArray(value)) {
return (
<DebugTag
tag={data.tag}
attributes={data.attributes}
message="Figure tag with non-array value"
payload={value}
/>
);
}

if (!Array.isArray(value)) {
return (
<DebugTag
tag={data.tag}
attributes={data.attributes}
message="Figure tag with non-array value"
payload={value}
/>
);
}

if (type === "table") {
return <FigureTable data={data} />;
}

if (value?.length === 0) {
return (
<Card elevation={1}>
<CardMedia
sx={{
background: (theme) => theme.palette.grey[100],
minHeight: 200,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
{t("figure.unloaded")}
</CardMedia>
</Card>
);
}

return (
<TagCatalogProvider tagCatalog={figureTagCatalog}>
<Card elevation={1}>
<CardMedia
sx={{
background: (theme) => theme.palette.grey[100],
minHeight: 200,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
{t("figure.unloaded")}
</CardMedia>
<CardContent>
<Value data={value} />
</CardContent>
</Card>
</TagCatalogProvider>
);
}
155 changes: 155 additions & 0 deletions packages/react-tei/src/tags/P.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,58 @@
import { describe, expect, it } from "vitest";
import { userEvent } from "vitest/browser";
import { render } from "vitest-browser-react";
import type { DocumentJson } from "../parser/document";
import { groupConsecutiveNonTableValues, P } from "./P";
import { TagCatalogProvider } from "./TagCatalogProvider";
import { tagCatalog } from "./tagCatalog";

// ...existing code...

it("should have a tooltip for InlineFigure inside paragraph if it contains displayable content", async () => {
const jsonValue: DocumentJson = {
tag: "p",
attributes: {},
value: [
{ tag: "#text", value: "This is a paragraph with an inline figure: " },
{
tag: "figure",
value: [
{
tag: "figDesc",
attributes: {},
value: [{ tag: "#text", value: "This is a figure description." }],
},
],
},
{
tag: "#text",
value: ".",
},
],
};

const screen = await render(<P data={jsonValue} />, {
wrapper: ({ children }) => (
<TagCatalogProvider tagCatalog={tagCatalog}>
{children}
</TagCatalogProvider>
),
});

const inlineFigure = screen.getByText("figure.unloaded");
await expect.element(inlineFigure).toBeVisible();

// Use userEvent.hover from vitest browser context
await userEvent.hover(inlineFigure.element());

// Wait for tooltip to appear
const tooltip = screen.getByRole("tooltip");
await expect.element(tooltip).toBeVisible();
await expect
.element(tooltip)
.toHaveTextContent("This is a figure description.");
});

describe("groupConsecutiveNonTableValues", () => {
it("should return a single group when there are no tables", () => {
const values: DocumentJson[] = [
Expand Down Expand Up @@ -301,4 +349,111 @@ describe("P", () => {
.element(screen.getByRole("paragraph"))
.toHaveTextContent("Valid text");
});

it("should interpret nested p as NoOp and not create nested <p> tags", async () => {
const jsonValue: DocumentJson = {
tag: "p",
attributes: {},
value: [
{ tag: "#text", value: "This is a paragraph with " },
{
tag: "p",
attributes: {},
value: [{ tag: "#text", value: "nested p tag" }],
},
{
tag: "#text",
value: ".",
},
],
};

const screen = await render(<P data={jsonValue} />, {
wrapper: ({ children }) => (
<TagCatalogProvider tagCatalog={tagCatalog}>
{children}
</TagCatalogProvider>
),
});

expect(screen.getByRole("paragraph")).toHaveTextContent(
"This is a paragraph with nested p tag.",
);
});

it("should render figure with image as InlineFigure inside paragraph", async () => {
const jsonValue: DocumentJson = {
tag: "p",
attributes: {},
value: [
{ tag: "#text", value: "This is a paragraph with an inline figure: " },
{
tag: "figure",
value: [
{
tag: "graphic",
},
],
},
{
tag: "#text",
value: ".",
},
],
};

const screen = await render(<P data={jsonValue} />, {
wrapper: ({ children }) => (
<TagCatalogProvider tagCatalog={tagCatalog}>
{children}
</TagCatalogProvider>
),
});

expect(screen.getByRole("paragraph")).toHaveTextContent(
"This is a paragraph with an inline figure: figure.unloaded.",
);
});

it("should have a tooltip for InlineFigure inside paragraph if it contains displayable content", async () => {
const jsonValue: DocumentJson = {
tag: "p",
attributes: {},
value: [
{ tag: "#text", value: "This is a paragraph with an inline figure: " },
{
tag: "figure",
value: [
{
tag: "figDesc",
attributes: {},
value: [{ tag: "#text", value: "This is a figure description." }],
},
],
},
{
tag: "#text",
value: ".",
},
],
};
userEvent.setup();

const screen = await render(<P data={jsonValue} />, {
wrapper: ({ children }) => (
<TagCatalogProvider tagCatalog={tagCatalog}>
{children}
</TagCatalogProvider>
),
});

const inlineFigure = screen.getByText("figure.unloaded");
expect(inlineFigure).toBeVisible();

await userEvent.hover(inlineFigure.element());

const tooltip = screen.getByRole("tooltip");
expect(tooltip).toBeVisible();
expect(tooltip).toHaveTextContent("This is a figure description.");
});
});
Loading