Skip to content

Commit 0d93d1f

Browse files
committed
use contentrenderer
1 parent 4f2e6ec commit 0d93d1f

File tree

9 files changed

+225
-94
lines changed

9 files changed

+225
-94
lines changed

frontend/src/__tests__/components/molecules/ContentRenderer.test.tsx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { render, screen } from '@testing-library/react';
22
import {
33
ContentRenderer,
44
ContentBlock,
5+
ContentItem,
56
} from '@molecules/ContentRenderer/ContentRenderer';
67

78
describe('ContentRenderer', () => {
@@ -12,6 +13,11 @@ describe('ContentRenderer', () => {
1213
expect(container).toBeEmptyDOMElement();
1314
});
1415

16+
it('renders a plain string as MarkdownContent in inline mode', () => {
17+
render(<ContentRenderer content='Just a string block' />);
18+
expect(screen.getByText('Just a string block')).toBeInTheDocument();
19+
});
20+
1521
it('throws an error for an unsupported block type', () => {
1622
const invalidContent = [
1723
{ type: 'test', value: 'invalid' },
@@ -40,10 +46,63 @@ describe('ContentRenderer', () => {
4046
];
4147

4248
render(<ContentRenderer content={content} />);
49+
50+
const listItems = screen.getAllByRole('paragraph');
51+
expect(listItems).toHaveLength(2);
4352
expect(screen.getByText('This is a paragraph.')).toBeInTheDocument();
4453
expect(screen.getByText('Another paragraph.')).toBeInTheDocument();
4554
});
4655

56+
it('renders an inline-text block without a paragraph wrapper', () => {
57+
const content: ContentBlock[] = [
58+
{ type: 'inline-text', text: 'Inline content' },
59+
];
60+
61+
render(
62+
<ul>
63+
<li data-testid='list-item'>
64+
<ContentRenderer content={content} />
65+
</li>
66+
</ul>
67+
);
68+
69+
const listItem = screen.getByTestId('list-item');
70+
expect(listItem).toHaveTextContent('Inline content');
71+
expect(listItem.querySelector('p')).toBeNull();
72+
});
73+
74+
it('treats a string as inline text (no paragraph wrapper)', () => {
75+
const content: string = 'Inline via string';
76+
77+
render(
78+
<ul>
79+
<li data-testid='list-item'>
80+
<ContentRenderer content={content} />
81+
</li>
82+
</ul>
83+
);
84+
85+
const listItem = screen.getByTestId('list-item');
86+
expect(listItem).toHaveTextContent('Inline via string');
87+
expect(listItem.querySelector('p')).toBeNull();
88+
});
89+
90+
it('treats a string array as inline text (no paragraph wrapper)', () => {
91+
const content: ContentItem[] = ['Inline via string'];
92+
93+
render(
94+
<ul>
95+
<li data-testid='list-item'>
96+
<ContentRenderer content={content} />
97+
</li>
98+
</ul>
99+
);
100+
101+
const listItem = screen.getByTestId('list-item');
102+
expect(listItem).toHaveTextContent('Inline via string');
103+
expect(listItem.querySelector('p')).toBeNull();
104+
});
105+
47106
it('renders code blocks with accessible description', () => {
48107
const content: ContentBlock[] = [
49108
{

frontend/src/__tests__/components/molecules/MarkdownContent.test.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,26 @@ describe('MarkdownContent', () => {
8080
expect(container.asFragment()).toMatchSnapshot();
8181
});
8282

83+
it('renders inline mode without a paragraph wrapper', () => {
84+
render(
85+
<ul>
86+
<li data-testid='list-item'>
87+
<MarkdownContent content='Inline text here' mode='inline' />
88+
</li>
89+
</ul>
90+
);
91+
92+
const listItem = screen.getByTestId('list-item');
93+
expect(listItem).toHaveTextContent('Inline text here');
94+
expect(listItem.querySelector('p')).toBeNull();
95+
});
96+
97+
it('renders block mode with a paragraph wrapper by default', () => {
98+
const { container } = render(<MarkdownContent content='Block content' />);
99+
expect(container.querySelector('p')).not.toBeNull();
100+
expect(container.querySelector('p')).toHaveTextContent('Block content');
101+
});
102+
83103
it('renders interpolated variables correctly', () => {
84104
render(
85105
<MarkdownContent

frontend/src/__tests__/components/molecules/MessagePlans.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ describe('MessagePlans', () => {
7474
{
7575
count: draft.count,
7676
plans: draft.plans,
77-
planType: 'Draft',
77+
statusGroup: 'Draft',
7878
},
7979
undefined
8080
);
@@ -84,7 +84,7 @@ describe('MessagePlans', () => {
8484
{
8585
count: production.count,
8686
plans: production.plans,
87-
planType: 'Production',
87+
statusGroup: 'Production',
8888
},
8989
undefined
9090
);

frontend/src/__tests__/components/molecules/MessagePlansList.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ describe('MessagePlansList', () => {
1616

1717
const container = render(
1818
<MessagePlansList
19-
planType='Draft'
19+
statusGroup='Draft'
2020
count={draft.count}
2121
plans={draft.plans}
2222
/>
@@ -27,7 +27,7 @@ describe('MessagePlansList', () => {
2727

2828
it('matches snapshot when no data is available', async () => {
2929
const container = render(
30-
<MessagePlansList planType='Draft' count={0} plans={[]} />
30+
<MessagePlansList statusGroup='Draft' count={0} plans={[]} />
3131
);
3232

3333
expect(container.asFragment()).toMatchSnapshot();
@@ -47,7 +47,7 @@ describe('MessagePlansList', () => {
4747

4848
render(
4949
<MessagePlansList
50-
planType='Draft'
50+
statusGroup='Draft'
5151
count={draft.count}
5252
plans={draft.plans}
5353
/>

frontend/src/components/molecules/ContentRenderer/ContentRenderer.tsx

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,70 @@ import { MarkdownContent } from '@molecules/MarkdownContent/MarkdownContent';
33

44
type StandardBlock = { testId?: string };
55
export type MarkdownTextBlock = StandardBlock & { type: 'text'; text: string };
6+
export type MarkdownInlineBlock = StandardBlock & {
7+
type: 'inline-text';
8+
text: string;
9+
};
610
export type CodeBlock = StandardBlock & {
711
type: 'code';
812
code: string;
913
aria: { text: string; id: string };
1014
};
1115
export type ListBlock = StandardBlock & { type: 'list'; items: string[] };
1216

13-
export type ContentBlock = MarkdownTextBlock | CodeBlock | ListBlock;
17+
export type ContentBlock =
18+
| MarkdownTextBlock
19+
| MarkdownInlineBlock
20+
| CodeBlock
21+
| ListBlock;
22+
export type ContentItem = ContentBlock | string;
1423

1524
interface ContentRendererProps {
16-
content: ContentBlock[];
25+
content: ContentItem[] | string;
1726
variables?: Record<string, string | number>;
1827
}
1928

2029
export function ContentRenderer({ content, variables }: ContentRendererProps) {
30+
const items: ContentItem[] =
31+
typeof content === 'string' ? [content] : content;
32+
if (items.length === 0) return null;
33+
2134
return (
2235
<>
23-
{content.map((block, index) => {
36+
{items.map((block, index) => {
37+
if (typeof block === 'string') {
38+
return (
39+
<MarkdownContent
40+
key={index}
41+
content={block}
42+
variables={variables}
43+
mode='inline'
44+
/>
45+
);
46+
}
47+
2448
const key = block.testId ?? index;
2549

2650
switch (block.type) {
2751
case 'text': {
2852
return (
2953
<MarkdownContent
54+
key={key}
3055
testId={block.testId}
56+
content={block.text}
57+
variables={variables}
58+
mode='block'
59+
/>
60+
);
61+
}
62+
case 'inline-text': {
63+
return (
64+
<MarkdownContent
3165
key={key}
66+
testId={block.testId}
3267
content={block.text}
3368
variables={variables}
69+
mode='inline'
3470
/>
3571
);
3672
}
@@ -50,7 +86,13 @@ export function ContentRenderer({ content, variables }: ContentRendererProps) {
5086
return (
5187
<ul data-testid={block.testId} key={key}>
5288
{block.items.map((item, itemId) => (
53-
<li key={itemId}>{item}</li>
89+
<li key={itemId}>
90+
<MarkdownContent
91+
content={item}
92+
variables={variables}
93+
mode='inline'
94+
/>
95+
</li>
5496
))}
5597
</ul>
5698
);

frontend/src/components/molecules/MarkdownContent/MarkdownContent.tsx

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,64 @@
11
import { interpolate } from '@utils/interpolate';
2-
import Markdown from 'markdown-to-jsx';
2+
import Markdown, { MarkdownToJSX } from 'markdown-to-jsx';
33
import React from 'react';
44

55
type MarkdownContentProps = {
66
content: string | string[];
77
variables?: Record<string, string | number>;
88
testId?: string;
9+
mode?: 'block' | 'inline';
910
};
1011

1112
export function MarkdownContent({
1213
content,
1314
variables,
1415
testId,
16+
mode = 'block',
1517
}: MarkdownContentProps) {
1618
const items = Array.isArray(content) ? content : [content];
19+
1720
const rendered = items
1821
.map((item) => interpolate(item, variables))
1922
.filter((s) => s.trim().length > 0);
2023

2124
if (rendered.length === 0) return null;
2225

26+
const NoWrap = ({ children }: React.PropsWithChildren) => <>{children}</>;
27+
28+
const inlineOptions: MarkdownToJSX.Options = {
29+
wrapper: React.Fragment,
30+
forceInline: true,
31+
disableParsingRawHTML: true,
32+
overrides: {
33+
a: {
34+
component: 'a',
35+
props: { target: '_blank', rel: 'noopener noreferrer' },
36+
},
37+
span: { component: NoWrap },
38+
},
39+
};
40+
41+
const blockOptions: MarkdownToJSX.Options = {
42+
wrapper: React.Fragment,
43+
forceBlock: true,
44+
disableParsingRawHTML: true,
45+
overrides: {
46+
a: {
47+
component: 'a',
48+
props: { target: '_blank', rel: 'noopener noreferrer' },
49+
},
50+
},
51+
};
52+
2353
return (
2454
<>
2555
{rendered.map((item, index) => (
2656
<Markdown
2757
key={index}
2858
data-testid={testId ? `${testId}-${index}` : undefined}
29-
options={{
30-
forceBlock: true,
31-
wrapper: React.Fragment,
32-
disableParsingRawHTML: true,
33-
overrides: {
34-
a: {
35-
component: 'a',
36-
props: { rel: 'noopener noreferrer', target: '_blank' },
37-
},
38-
},
39-
}}
59+
options={mode === 'block' ? blockOptions : inlineOptions}
4060
>
41-
{interpolate(item, variables)}
61+
{item}
4262
</Markdown>
4363
))}
4464
</>

0 commit comments

Comments
 (0)