Skip to content

Commit 19abd44

Browse files
Merge pull request #154 from istex/figure-with-graphic
Figure with graphic
2 parents 5b927da + cc64d5f commit 19abd44

11 files changed

Lines changed: 537 additions & 29 deletions

File tree

packages/react-tei/src/i18n/locales/en.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,7 @@ export const en: Translation = {
8585
enter: "Enter full screen mode",
8686
exit: "Exit full screen mode",
8787
},
88+
figure: {
89+
unloaded: "Image not loaded",
90+
},
8891
};

packages/react-tei/src/i18n/locales/fr.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ export const fr = {
8484
enter: "Passer en mode plein écran",
8585
exit: "Quitter le mode plein écran",
8686
},
87+
figure: {
88+
unloaded: "Image non chargée",
89+
},
8790
};
8891

8992
export type Translation = typeof fr;

packages/react-tei/src/tags/Figure.spec.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,52 @@ describe("Figure", () => {
5656
expect(screen.getByRole("cell", { name: "Data 1" })).toBeVisible();
5757
expect(screen.getByRole("cell", { name: "Data 2" })).toBeVisible();
5858
});
59+
it("should render unloaded figure for non-table types", async () => {
60+
const jsonDocument: DocumentJson = {
61+
tag: "figure",
62+
attributes: { "@type": "image", "@xml:id": "f1" },
63+
value: [],
64+
};
65+
66+
const screen = await render(<Figure data={jsonDocument} />, {
67+
wrapper: ({ children }) => (
68+
<TagCatalogProvider tagCatalog={tagCatalog}>
69+
{children}
70+
</TagCatalogProvider>
71+
),
72+
});
73+
74+
expect(screen.getByText("figure.unloaded")).toBeVisible();
75+
});
76+
77+
it("should render figure.unloaded along figure head and figDesc when they are presents", async () => {
78+
const jsonDocument: DocumentJson = {
79+
tag: "figure",
80+
attributes: { "@type": "image", "@xml:id": "f2" },
81+
value: [
82+
{
83+
tag: "head",
84+
attributes: {},
85+
value: "This is the figure head",
86+
},
87+
{
88+
tag: "figDesc",
89+
attributes: {},
90+
value: "This is the figure description",
91+
},
92+
],
93+
};
94+
95+
const screen = await render(<Figure data={jsonDocument} />, {
96+
wrapper: ({ children }) => (
97+
<TagCatalogProvider tagCatalog={tagCatalog}>
98+
{children}
99+
</TagCatalogProvider>
100+
),
101+
});
102+
103+
expect(screen.getByText("figure.unloaded")).toBeVisible();
104+
expect(screen.getByText("This is the figure head")).toBeVisible();
105+
expect(screen.getByText("This is the figure description")).toBeVisible();
106+
});
59107
});
Lines changed: 78 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,87 @@
1+
import { Card, CardContent, CardMedia } from "@mui/material";
2+
import { useMemo } from "react";
3+
import { useTranslation } from "react-i18next";
14
import { DebugTag } from "../debug/DebugTag";
25
import { FigureTable } from "./figure/FigureTable";
6+
import { figureTagCatalog } from "./figure/figureTagCatalog";
7+
import { TagCatalogProvider } from "./TagCatalogProvider";
38
import type { ComponentProps } from "./type";
9+
import { Value } from "./Value";
410

511
export function Figure({ data }: ComponentProps) {
612
const type = data.attributes?.["@type"];
13+
const { t } = useTranslation();
14+
const value = useMemo(() => {
15+
if (!Array.isArray(data.value)) {
16+
return data.value;
17+
}
18+
return data.value.filter(
19+
({ tag }) => !["graphic", "link", "highlightedText"].includes(tag),
20+
);
21+
}, [data.value]);
722

8-
switch (type) {
9-
case "table":
10-
return <FigureTable data={data} />;
11-
default:
12-
return (
13-
<DebugTag
14-
tag={data.tag}
15-
attributes={data.attributes}
16-
message={`Unsupported figure type ${type}`}
17-
payload={data.value}
18-
type="error"
19-
/>
20-
);
23+
if (!Array.isArray(value)) {
24+
return (
25+
<DebugTag
26+
tag={data.tag}
27+
attributes={data.attributes}
28+
message="Figure tag with non-array value"
29+
payload={value}
30+
/>
31+
);
2132
}
33+
34+
if (!Array.isArray(value)) {
35+
return (
36+
<DebugTag
37+
tag={data.tag}
38+
attributes={data.attributes}
39+
message="Figure tag with non-array value"
40+
payload={value}
41+
/>
42+
);
43+
}
44+
45+
if (type === "table") {
46+
return <FigureTable data={data} />;
47+
}
48+
49+
if (value?.length === 0) {
50+
return (
51+
<Card elevation={1}>
52+
<CardMedia
53+
sx={{
54+
background: (theme) => theme.palette.grey[100],
55+
minHeight: 200,
56+
display: "flex",
57+
alignItems: "center",
58+
justifyContent: "center",
59+
}}
60+
>
61+
{t("figure.unloaded")}
62+
</CardMedia>
63+
</Card>
64+
);
65+
}
66+
67+
return (
68+
<TagCatalogProvider tagCatalog={figureTagCatalog}>
69+
<Card elevation={1}>
70+
<CardMedia
71+
sx={{
72+
background: (theme) => theme.palette.grey[100],
73+
minHeight: 200,
74+
display: "flex",
75+
alignItems: "center",
76+
justifyContent: "center",
77+
}}
78+
>
79+
{t("figure.unloaded")}
80+
</CardMedia>
81+
<CardContent>
82+
<Value data={value} />
83+
</CardContent>
84+
</Card>
85+
</TagCatalogProvider>
86+
);
2287
}

packages/react-tei/src/tags/P.spec.tsx

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,58 @@
11
import { describe, expect, it } from "vitest";
2+
import { userEvent } from "vitest/browser";
23
import { render } from "vitest-browser-react";
34
import type { DocumentJson } from "../parser/document";
45
import { groupConsecutiveNonTableValues, P } from "./P";
56
import { TagCatalogProvider } from "./TagCatalogProvider";
67
import { tagCatalog } from "./tagCatalog";
78

9+
// ...existing code...
10+
11+
it("should have a tooltip for InlineFigure inside paragraph if it contains displayable content", async () => {
12+
const jsonValue: DocumentJson = {
13+
tag: "p",
14+
attributes: {},
15+
value: [
16+
{ tag: "#text", value: "This is a paragraph with an inline figure: " },
17+
{
18+
tag: "figure",
19+
value: [
20+
{
21+
tag: "figDesc",
22+
attributes: {},
23+
value: [{ tag: "#text", value: "This is a figure description." }],
24+
},
25+
],
26+
},
27+
{
28+
tag: "#text",
29+
value: ".",
30+
},
31+
],
32+
};
33+
34+
const screen = await render(<P data={jsonValue} />, {
35+
wrapper: ({ children }) => (
36+
<TagCatalogProvider tagCatalog={tagCatalog}>
37+
{children}
38+
</TagCatalogProvider>
39+
),
40+
});
41+
42+
const inlineFigure = screen.getByText("figure.unloaded");
43+
await expect.element(inlineFigure).toBeVisible();
44+
45+
// Use userEvent.hover from vitest browser context
46+
await userEvent.hover(inlineFigure.element());
47+
48+
// Wait for tooltip to appear
49+
const tooltip = screen.getByRole("tooltip");
50+
await expect.element(tooltip).toBeVisible();
51+
await expect
52+
.element(tooltip)
53+
.toHaveTextContent("This is a figure description.");
54+
});
55+
856
describe("groupConsecutiveNonTableValues", () => {
957
it("should return a single group when there are no tables", () => {
1058
const values: DocumentJson[] = [
@@ -301,4 +349,111 @@ describe("P", () => {
301349
.element(screen.getByRole("paragraph"))
302350
.toHaveTextContent("Valid text");
303351
});
352+
353+
it("should interpret nested p as NoOp and not create nested <p> tags", async () => {
354+
const jsonValue: DocumentJson = {
355+
tag: "p",
356+
attributes: {},
357+
value: [
358+
{ tag: "#text", value: "This is a paragraph with " },
359+
{
360+
tag: "p",
361+
attributes: {},
362+
value: [{ tag: "#text", value: "nested p tag" }],
363+
},
364+
{
365+
tag: "#text",
366+
value: ".",
367+
},
368+
],
369+
};
370+
371+
const screen = await render(<P data={jsonValue} />, {
372+
wrapper: ({ children }) => (
373+
<TagCatalogProvider tagCatalog={tagCatalog}>
374+
{children}
375+
</TagCatalogProvider>
376+
),
377+
});
378+
379+
expect(screen.getByRole("paragraph")).toHaveTextContent(
380+
"This is a paragraph with nested p tag.",
381+
);
382+
});
383+
384+
it("should render figure with image as InlineFigure inside paragraph", async () => {
385+
const jsonValue: DocumentJson = {
386+
tag: "p",
387+
attributes: {},
388+
value: [
389+
{ tag: "#text", value: "This is a paragraph with an inline figure: " },
390+
{
391+
tag: "figure",
392+
value: [
393+
{
394+
tag: "graphic",
395+
},
396+
],
397+
},
398+
{
399+
tag: "#text",
400+
value: ".",
401+
},
402+
],
403+
};
404+
405+
const screen = await render(<P data={jsonValue} />, {
406+
wrapper: ({ children }) => (
407+
<TagCatalogProvider tagCatalog={tagCatalog}>
408+
{children}
409+
</TagCatalogProvider>
410+
),
411+
});
412+
413+
expect(screen.getByRole("paragraph")).toHaveTextContent(
414+
"This is a paragraph with an inline figure: figure.unloaded.",
415+
);
416+
});
417+
418+
it("should have a tooltip for InlineFigure inside paragraph if it contains displayable content", async () => {
419+
const jsonValue: DocumentJson = {
420+
tag: "p",
421+
attributes: {},
422+
value: [
423+
{ tag: "#text", value: "This is a paragraph with an inline figure: " },
424+
{
425+
tag: "figure",
426+
value: [
427+
{
428+
tag: "figDesc",
429+
attributes: {},
430+
value: [{ tag: "#text", value: "This is a figure description." }],
431+
},
432+
],
433+
},
434+
{
435+
tag: "#text",
436+
value: ".",
437+
},
438+
],
439+
};
440+
userEvent.setup();
441+
442+
const screen = await render(<P data={jsonValue} />, {
443+
wrapper: ({ children }) => (
444+
<TagCatalogProvider tagCatalog={tagCatalog}>
445+
{children}
446+
</TagCatalogProvider>
447+
),
448+
});
449+
450+
const inlineFigure = screen.getByText("figure.unloaded");
451+
expect(inlineFigure).toBeVisible();
452+
453+
await userEvent.hover(inlineFigure.element());
454+
455+
const tooltip = screen.getByRole("tooltip");
456+
expect(tooltip).toBeVisible();
457+
expect(tooltip).toHaveTextContent("This is a figure description.");
458+
});
304459
});

0 commit comments

Comments
 (0)