diff --git a/src/components/RenderHTML.tsx b/src/components/RenderHTML.tsx index f7a9dcada1e35..469633f17f5cb 100644 --- a/src/components/RenderHTML.tsx +++ b/src/components/RenderHTML.tsx @@ -1,6 +1,7 @@ import React, {useMemo} from 'react'; import {RenderHTMLConfigProvider, RenderHTMLSource} from 'react-native-render-html'; import type {RenderersProps} from 'react-native-render-html'; +import useHasTextAncestor from '@hooks/useHasTextAncestor'; import useWindowDimensions from '@hooks/useWindowDimensions'; import Parser from '@libs/Parser'; @@ -22,6 +23,11 @@ type RenderHTMLProps = { // context to RenderHTMLSource components. See https://git.io/JRcZb // The provider is available at src/components/HTMLEngineProvider/ function RenderHTML({html: htmlParam, onLinkPress, isSelectable}: RenderHTMLProps) { + const hasTextAncestor = useHasTextAncestor(); + if (__DEV__ && hasTextAncestor) { + throw new Error('RenderHTML must not be rendered inside a component, as it will break the layout on iOS. Render it as a sibling instead.'); + } + const {windowWidth} = useWindowDimensions(); const html = useMemo(() => { return ( diff --git a/src/hooks/useHasTextAncestor.native.ts b/src/hooks/useHasTextAncestor.native.ts new file mode 100644 index 0000000000000..b0dcf836f39c6 --- /dev/null +++ b/src/hooks/useHasTextAncestor.native.ts @@ -0,0 +1,7 @@ +import {useContext} from 'react'; +// eslint-disable-next-line no-restricted-imports +import {unstable_TextAncestorContext as TextAncestorContext} from 'react-native'; + +export default function useHasTextAncestor(): boolean { + return useContext(TextAncestorContext); +} diff --git a/src/hooks/useHasTextAncestor.ts b/src/hooks/useHasTextAncestor.ts new file mode 100644 index 0000000000000..518b587e46177 --- /dev/null +++ b/src/hooks/useHasTextAncestor.ts @@ -0,0 +1,4 @@ +// TextAncestorContext is not available on web (react-native-web), so always return false. +export default function useHasTextAncestor(): boolean { + return false; +} diff --git a/src/pages/workspace/tags/WorkspaceTagsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsPage.tsx index 957340630af4c..8ef4f0ee608ff 100644 --- a/src/pages/workspace/tags/WorkspaceTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx @@ -687,13 +687,14 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { shouldShow={!hasDependentTags && !hasIndependentTags} /> ) : ( - - {!hasDependentTags && !hasIndependentTags && !!policyTagLists.at(0)?.name ? ( - - ) : ( - translate('workspace.tags.subtitle') - )} - + <> + + {!hasDependentTags && !hasIndependentTags && !!policyTagLists.at(0)?.name ? ( + + ) : ( + translate('workspace.tags.subtitle') + )} + {hasDependentTags && ( )} - + )} {tagList.length > CONST.SEARCH_ITEM_LIMIT && ( diff --git a/tests/unit/RenderHTMLTest.tsx b/tests/unit/RenderHTMLTest.tsx new file mode 100644 index 0000000000000..ef8b5db5c38ae --- /dev/null +++ b/tests/unit/RenderHTMLTest.tsx @@ -0,0 +1,41 @@ +import {render} from '@testing-library/react-native'; +import React from 'react'; +import {View} from 'react-native'; +import RenderHTML from '@components/RenderHTML'; + +jest.mock('@hooks/useWindowDimensions', () => () => ({windowWidth: 400})); +jest.mock('react-native-render-html', () => { + // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment + const {View: MockView} = require('react-native'); + return { + RenderHTMLConfigProvider: ({children}: {children: React.ReactNode}) => children, + RenderHTMLSource: () => , + }; +}); + +const mockUseHasTextAncestor = jest.fn(() => false); +jest.mock('@hooks/useHasTextAncestor', () => () => mockUseHasTextAncestor()); + +describe('RenderHTML', () => { + it('throws when rendered inside a Text ancestor', () => { + mockUseHasTextAncestor.mockReturnValue(true); + expect(() => + render( + + + , + ), + ).toThrow('RenderHTML must not be rendered inside a component'); + }); + + it('does not throw when rendered outside a Text ancestor', () => { + mockUseHasTextAncestor.mockReturnValue(false); + expect(() => + render( + + + , + ), + ).not.toThrow(); + }); +});