Skip to content

Commit e53b8ec

Browse files
test error boundry
1 parent 2cd944b commit e53b8ec

File tree

5 files changed

+172
-101
lines changed

5 files changed

+172
-101
lines changed

examples/vite_basic/src/App.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ const App = () => {
1818
handleFileChange,
1919
} = useFileLoader(AVAILABLE_FILES[0]);
2020

21+
console.log("test page ", testPage);
22+
23+
if (testPage) {
24+
testPage.children = [undefined];
25+
}
26+
2127
// Get backrefs for the currently selected file
2228
const currentBackrefs = selectedFile?.backrefs || [];
2329

typescript/src/renderer/JsonDocRenderer.tsx

Lines changed: 12 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
import "./styles/index.css";
2-
import React, { useEffect } from "react";
2+
import React from "react";
33

44
import { Page } from "@/models/generated";
5-
import { loadPage } from "@/serialization/loader";
65

76
import { BlockRenderer } from "./components/BlockRenderer";
8-
import { PageDelimiter } from "./components/PageDelimiter";
9-
import { JsonViewPanel } from "./components/dev/JsonViewPanel";
10-
import { RendererProvider } from "./context/RendererContext";
11-
import { HighlightNavigation } from "./components/HighlightNavigation";
12-
import { useHighlights } from "./hooks/useHighlights";
7+
import { RendererContainer } from "./components/RendererContainer";
138
import { Backref } from "./utils/highlightUtils";
149
import { GlobalErrorBoundary } from "./components/ErrorBoundary";
1510

@@ -40,105 +35,22 @@ export const JsonDocRenderer = ({
4035
backrefs = [],
4136
onError,
4237
}: JsonDocRendererProps) => {
43-
// Use the modular hooks for highlight management
44-
const { highlightCount, currentActiveIndex, navigateToHighlight } =
45-
useHighlights({
46-
backrefs,
47-
});
48-
49-
useEffect(() => {
50-
try {
51-
//TODO: this is not throwing for invalid page object (one that doesn't follow schema)
52-
loadPage(page);
53-
} catch (_) {
54-
// console.log("error ", error);
55-
}
56-
}, [page]);
57-
58-
// return null;
59-
const renderedContent = (
60-
<div className="json-doc-page">
61-
{/* Page icon */}
62-
{page.icon && (
63-
<div className="json-doc-page-icon">
64-
{page.icon.type === "emoji" && page.icon.emoji}
65-
</div>
66-
)}
67-
{/* Page title */}
68-
{page.properties?.title && (
69-
<h1
70-
className="json-doc-page-title"
71-
data-page-id={page.id}
72-
role="heading"
73-
>
74-
{page.properties.title.title?.[0]?.plain_text || "Untitled"}
75-
</h1>
76-
)}
77-
{/* Page children blocks */}
78-
{page.children && page.children.length > 0 && (
79-
<div className="json-doc-page-content">
80-
{page.children.map((block: any, index: number) => {
81-
const currentPageNum = block.metadata?.origin?.page_num;
82-
const nextPageNum =
83-
index < page.children.length - 1
84-
? (page.children[index + 1]?.metadata as any)?.origin?.page_num
85-
: null;
86-
87-
// Show delimiter after the last block of each page
88-
const showPageDelimiter =
89-
currentPageNum &&
90-
(nextPageNum !== currentPageNum ||
91-
index === page.children.length - 1);
92-
93-
return (
94-
<React.Fragment key={block.id || index}>
95-
<BlockRenderer
96-
block={block}
97-
depth={0}
98-
components={components}
99-
/>
100-
101-
{showPageDelimiter && !components?.page_delimiter && (
102-
<PageDelimiter pageNumber={currentPageNum} />
103-
)}
104-
{showPageDelimiter && components?.page_delimiter && (
105-
<components.page_delimiter pageNumber={currentPageNum} />
106-
)}
107-
</React.Fragment>
108-
);
109-
})}
110-
</div>
111-
)}
112-
</div>
113-
);
114-
38+
console.log("theme: ", theme);
11539
return (
11640
<div
11741
className={`jsondoc-theme-${theme}`}
11842
data-testid="jsondoc-renderer-root"
11943
>
12044
<GlobalErrorBoundary onError={onError}>
121-
<RendererProvider value={{ devMode, resolveImageUrl }}>
122-
<div
123-
className={`json-doc-renderer${className ? " " + className : ""}`}
124-
>
125-
{viewJson ? (
126-
<div className="flex h-screen">
127-
<JsonViewPanel data={page} />
128-
</div>
129-
) : (
130-
renderedContent
131-
)}
132-
{/* Show highlight navigation when there are highlights */}
133-
{highlightCount > 0 && (
134-
<HighlightNavigation
135-
highlightCount={highlightCount}
136-
onNavigate={navigateToHighlight}
137-
currentIndex={currentActiveIndex}
138-
/>
139-
)}
140-
</div>
141-
</RendererProvider>
45+
<RendererContainer
46+
page={page}
47+
className={className}
48+
components={components}
49+
devMode={devMode}
50+
resolveImageUrl={resolveImageUrl}
51+
viewJson={viewJson}
52+
backrefs={backrefs}
53+
/>
14254
</GlobalErrorBoundary>
14355
</div>
14456
);

typescript/src/renderer/components/ErrorBoundary.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ interface GlobalErrorBoundaryProps {
99
function GlobalErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
1010
console.log("error ", error);
1111
return (
12-
<div className="json-doc-error-boundary">
12+
<div className="json-doc-error-boundary" role="alert">
1313
<div className="json-doc-error-content">
1414
<h2>Document Failed to Load</h2>
1515
<p>Something went wrong while rendering this document.</p>
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import React, { useEffect } from "react";
2+
3+
import { Page } from "@/models/generated";
4+
import { loadPage } from "@/serialization/loader";
5+
6+
import { RendererProvider } from "../context/RendererContext";
7+
import { useHighlights } from "../hooks/useHighlights";
8+
import { Backref } from "../utils/highlightUtils";
9+
10+
import { HighlightNavigation } from "./HighlightNavigation";
11+
import { JsonViewPanel } from "./dev/JsonViewPanel";
12+
import { BlockRenderer } from "./BlockRenderer";
13+
import { PageDelimiter } from "./PageDelimiter";
14+
15+
interface RendererContainerProps {
16+
page: Page;
17+
className?: string;
18+
components?: React.ComponentProps<typeof BlockRenderer>["components"] & {
19+
page_delimiter: React.ComponentType<{
20+
pageNumber: number;
21+
}>;
22+
};
23+
devMode?: boolean;
24+
resolveImageUrl?: (url: string) => Promise<string>;
25+
viewJson?: boolean;
26+
backrefs?: Backref[];
27+
}
28+
29+
export const RendererContainer: React.FC<RendererContainerProps> = ({
30+
page,
31+
className = "",
32+
components,
33+
devMode = false,
34+
resolveImageUrl,
35+
viewJson = false,
36+
backrefs = [],
37+
}) => {
38+
// Use the modular hooks for highlight management
39+
const { highlightCount, currentActiveIndex, navigateToHighlight } =
40+
useHighlights({
41+
backrefs,
42+
});
43+
44+
useEffect(() => {
45+
try {
46+
//TODO: this is not throwing for invalid page object (one that doesn't follow schema)
47+
loadPage(page);
48+
} catch (_) {
49+
// console.log("error ", error);
50+
}
51+
}, [page]);
52+
53+
const renderedContent = (
54+
<div className="json-doc-page">
55+
{/* Page icon */}
56+
{page.icon && (
57+
<div className="json-doc-page-icon">
58+
{page.icon.type === "emoji" && page.icon.emoji}
59+
</div>
60+
)}
61+
{/* Page title */}
62+
{page.properties?.title && (
63+
<h1
64+
className="json-doc-page-title"
65+
data-page-id={page.id}
66+
role="heading"
67+
>
68+
{page.properties.title.title?.[0]?.plain_text || "Untitled"}
69+
</h1>
70+
)}
71+
{/* Page children blocks */}
72+
{page.children && page.children.length > 0 && (
73+
<div className="json-doc-page-content">
74+
{page.children.map((block: any, index: number) => {
75+
const currentPageNum = block.metadata?.origin?.page_num;
76+
const nextPageNum =
77+
index < page.children.length - 1
78+
? (page.children[index + 1]?.metadata as any)?.origin?.page_num
79+
: null;
80+
81+
// Show delimiter after the last block of each page
82+
const showPageDelimiter =
83+
currentPageNum &&
84+
(nextPageNum !== currentPageNum ||
85+
index === page.children.length - 1);
86+
87+
return (
88+
<React.Fragment key={block.id || index}>
89+
<BlockRenderer
90+
block={block}
91+
depth={0}
92+
components={components}
93+
/>
94+
95+
{showPageDelimiter && !components?.page_delimiter && (
96+
<PageDelimiter pageNumber={currentPageNum} />
97+
)}
98+
{showPageDelimiter && components?.page_delimiter && (
99+
<components.page_delimiter pageNumber={currentPageNum} />
100+
)}
101+
</React.Fragment>
102+
);
103+
})}
104+
</div>
105+
)}
106+
</div>
107+
);
108+
109+
return (
110+
<RendererProvider value={{ devMode, resolveImageUrl }}>
111+
<div className={`json-doc-renderer${className ? " " + className : ""}`}>
112+
{viewJson ? (
113+
<div className="flex h-screen">
114+
<JsonViewPanel data={page} />
115+
</div>
116+
) : (
117+
renderedContent
118+
)}
119+
{/* Show highlight navigation when there are highlights */}
120+
{highlightCount > 0 && (
121+
<HighlightNavigation
122+
highlightCount={highlightCount}
123+
onNavigate={navigateToHighlight}
124+
currentIndex={currentActiveIndex}
125+
/>
126+
)}
127+
</div>
128+
</RendererProvider>
129+
);
130+
};

typescript/tests/BlockRenderer.test.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,4 +275,27 @@ describe("JsonDocRenderer - All Block Types", () => {
275275
expect(screen.getByText(/Page 1/)).toBeInTheDocument();
276276
expect(screen.getByText(/Page 2/)).toBeInTheDocument();
277277
});
278+
279+
it("should handle bad page input gracefully", () => {
280+
const badBlocks = [
281+
undefined,
282+
{
283+
...mockBlocks.paragraph,
284+
metadata: {
285+
origin: {
286+
file_id: "test-file",
287+
page_num: 3,
288+
},
289+
},
290+
},
291+
];
292+
const badPage = createPageWithBlocks(badBlocks);
293+
294+
render(<JsonDocRenderer page={badPage} />);
295+
296+
screen.debug();
297+
298+
expect(screen.getByRole("alert")).toBeInTheDocument();
299+
expect(screen.getByText(/failed to load/i)).toBeInTheDocument();
300+
});
278301
});

0 commit comments

Comments
 (0)