diff --git a/react-app-internal/src/utils/parser/rehypeCallout.js b/react-app-internal/src/utils/parser/rehypeCallout.js index bf945d9..6df2961 100644 --- a/react-app-internal/src/utils/parser/rehypeCallout.js +++ b/react-app-internal/src/utils/parser/rehypeCallout.js @@ -47,7 +47,8 @@ const rehypeCallout = () => { } function isCallout(indicatorNode) { - return indicatorNode.tagName === 'p' && indicatorNode.children[0].value.match(/\[!.*]/) + const indicatorValue = indicatorNode?.children?.[0]?.value; + return indicatorNode?.tagName === 'p' && typeof indicatorValue === 'string' && /\[!.*]/.test(indicatorValue) } function parseIndicatorNode(indicatorNode) { @@ -97,4 +98,4 @@ function determineClass(isFoldable, foldableState) { return calloutClass; } -export default rehypeCallout \ No newline at end of file +export default rehypeCallout diff --git a/react-app-internal/src/utils/parser/rehypeCallout.test.js b/react-app-internal/src/utils/parser/rehypeCallout.test.js index 29b1282..d86473e 100644 --- a/react-app-internal/src/utils/parser/rehypeCallout.test.js +++ b/react-app-internal/src/utils/parser/rehypeCallout.test.js @@ -85,6 +85,34 @@ describe('rehypeCallout 동작 시', () => { expect(inputAst).toEqual(expectedAst) }); + it('indicator의 첫 자식이 text 노드가 아니어도 에러 없이 통과한다.', () => { + inputAst = u('root', [ + { + type: 'element', + tagName: 'blockquote', + children: [ + { type: 'text', value: '\n' }, + { + type: 'element', + tagName: 'p', + children: [ + { + type: 'element', + tagName: 'strong', + children: [{ type: 'text', value: '[!info]' }], + }, + { type: 'text', value: '내용' }, + ], + }, + ], + } + ]); + expectedAst = structuredClone(inputAst); + + expect(() => rehypeCallout()(inputAst)).not.toThrow(); + expect(inputAst).toEqual(expectedAst); + }); + it('콜아웃 지시자가 있는 blockquote 태그의 경우 rehype 된다.', () => { inputAst = u('root', [ { @@ -353,4 +381,4 @@ describe('rehypeCallout 동작 시', () => { expect(found).true }); -}); \ No newline at end of file +});