Skip to content

Commit ab7b6cd

Browse files
committed
fix: don't render bullets in ordered list
1 parent 4e1f72b commit ab7b6cd

6 files changed

Lines changed: 51 additions & 16 deletions

File tree

src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -191,10 +191,6 @@ function BaseHTMLEngineProvider({textSelectable = false, children, enableExperim
191191
contentModel: HTMLContentModel.block,
192192
mixedUAStyles: {marginVertical: 12},
193193
}),
194-
li: HTMLElementModel.fromCustomModel({
195-
tagName: 'li',
196-
contentModel: HTMLContentModel.block,
197-
}),
198194
'sparkles-icon': HTMLElementModel.fromCustomModel({
199195
tagName: 'sparkles-icon',
200196
contentModel: HTMLContentModel.mixed,

src/components/HTMLEngineProvider/HTMLRenderers/BulletItemRenderer.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import React from 'react';
22
import {View} from 'react-native';
3-
import type {CustomRendererProps, TBlock} from 'react-native-render-html';
3+
import type {TNode} from 'react-native-render-html';
44
import {TNodeChildrenRenderer} from 'react-native-render-html';
55
import Text from '@components/Text';
66
import useTheme from '@hooks/useTheme';
77
import useThemeStyles from '@hooks/useThemeStyles';
88
import variables from '@styles/variables';
99
import CONST from '@src/CONST';
1010

11-
function BulletItemRenderer({tnode}: CustomRendererProps<TBlock>) {
11+
function BulletItemRenderer({tnode}: {tnode: TNode}) {
1212
const styles = useThemeStyles();
1313
const theme = useTheme();
1414

src/components/HTMLEngineProvider/HTMLRenderers/ULRenderer.tsx

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,37 @@
11
import React from 'react';
22
import {View} from 'react-native';
33
import type {CustomRendererProps, TBlock} from 'react-native-render-html';
4-
import {TNodeChildrenRenderer} from 'react-native-render-html';
4+
import {TNodeRenderer} from 'react-native-render-html';
5+
import BulletItemRenderer from './BulletItemRenderer';
56

67
/**
78
* Bypasses the library's internal ULRenderer (which wraps children in MarkedListItem)
8-
* and renders ul as a plain block container — matching how bullet-list works.
9-
* The actual bullet markers are drawn by the li renderer (BulletItemRenderer).
9+
* and renders <ul> as a plain block container that draws bullet markers around each
10+
* direct <li> child — matching how <bullet-list>/<bullet-item> render. <li> is left
11+
* unregistered globally so that <ol><li> still uses the library's default numeric markers.
1012
*/
1113
function ULRenderer({tnode, style}: CustomRendererProps<TBlock>) {
1214
return (
1315
<View style={[style, {gap: 8}]}>
14-
<TNodeChildrenRenderer tnode={tnode} />
16+
{tnode.children.map((child, index) => {
17+
const key = `${child.tagName ?? 'node'}-${index}`;
18+
if (child.tagName === 'li') {
19+
return (
20+
<BulletItemRenderer
21+
key={key}
22+
tnode={child}
23+
/>
24+
);
25+
}
26+
return (
27+
<TNodeRenderer
28+
key={key}
29+
tnode={child}
30+
renderIndex={index}
31+
renderLength={tnode.children.length}
32+
/>
33+
);
34+
})}
1535
</View>
1636
);
1737
}

src/components/HTMLEngineProvider/HTMLRenderers/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ const HTMLEngineProviderComponentList: CustomTagRendererRecord = {
3131
a: AnchorRenderer,
3232
code: CodeRenderer,
3333
img: ImageRenderer,
34-
li: BulletItemRenderer,
3534
ul: ULRenderer,
3635
video: VideoRenderer,
3736

src/components/RenderHTML.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ function RenderHTML({html: htmlParam, onLinkPress, isSelectable}: RenderHTMLProp
5959
/* eslint-disable @typescript-eslint/naming-convention */
6060
'bullet-item': BulletItemRenderer,
6161
'sparkles-icon': SparklesIconRenderer,
62-
li: BulletItemRenderer,
6362
ul: ULRenderer,
6463
};
6564

tests/unit/BulletListRendererTest.tsx

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ jest.mock('react-native-render-html', () => {
2222
return ReactModule.createElement(MockView);
2323
},
2424
TNodeChildrenRenderer: ({tnode}: {tnode?: {mockText?: string}}) => ReactModule.createElement(MockText, null, tnode?.mockText ?? ''),
25+
TNodeRenderer: ({tnode}: {tnode?: {mockText?: string}}) => ReactModule.createElement(MockText, null, tnode?.mockText ?? ''),
2526
};
2627
});
2728

@@ -33,28 +34,48 @@ jest.mock('@libs/Parser', () => ({
3334
}));
3435

3536
const buildTNode = (text = '') => ({mockText: text}) as unknown as CustomRendererProps<TBlock>['tnode'];
37+
const buildULTNode = (children: Array<{tagName: string; text: string}>) =>
38+
({
39+
children: children.map((child) => ({tagName: child.tagName, mockText: child.text})),
40+
}) as unknown as CustomRendererProps<TBlock>['tnode'];
3641

3742
describe('Bullet list rendering', () => {
3843
beforeEach(() => {
3944
capturedSource.html = undefined;
4045
});
4146

4247
describe('ULRenderer', () => {
43-
it('renders its children', () => {
48+
it('wraps each <li> child with a bullet marker', () => {
4449
render(
4550
// @ts-expect-error — only the props read by the renderer are needed for this test
4651
<ULRenderer
47-
tnode={buildTNode('child content')}
52+
tnode={buildULTNode([
53+
{tagName: 'li', text: 'One'},
54+
{tagName: 'li', text: 'Two'},
55+
])}
4856
style={{}}
4957
/>,
5058
);
51-
expect(screen.getByText('child content')).toBeTruthy();
59+
expect(screen.getAllByText(CONST.DOT_SEPARATOR)).toHaveLength(2);
60+
expect(screen.getByText('One')).toBeTruthy();
61+
expect(screen.getByText('Two')).toBeTruthy();
62+
});
63+
64+
it('renders non-<li> children with the default node renderer', () => {
65+
render(
66+
// @ts-expect-error — only the props read by the renderer are needed for this test
67+
<ULRenderer
68+
tnode={buildULTNode([{tagName: 'span', text: 'stray child'}])}
69+
style={{}}
70+
/>,
71+
);
72+
expect(screen.queryByText(CONST.DOT_SEPARATOR)).toBeNull();
73+
expect(screen.getByText('stray child')).toBeTruthy();
5274
});
5375
});
5476

5577
describe('BulletItemRenderer (used for both <li> and <bullet-item>)', () => {
5678
it('renders a bullet marker next to the item content', () => {
57-
// @ts-expect-error — only the props read by the renderer are needed for this test
5879
render(<BulletItemRenderer tnode={buildTNode('First item')} />);
5980
expect(screen.getByText(CONST.DOT_SEPARATOR)).toBeTruthy();
6081
expect(screen.getByText('First item')).toBeTruthy();

0 commit comments

Comments
 (0)