From 9f1f76da7cb5cb7dcb6feccf5678a43083de888c Mon Sep 17 00:00:00 2001 From: Himanshu Soni Date: Sun, 22 Feb 2026 00:03:05 +0530 Subject: [PATCH 1/2] fix: prevent React crash on single-child table headers #2256 --- cypress/components/StyledMarkdownBlock.cy.tsx | 31 +++++++++++++++++++ lib/markdownUtils.ts | 17 +++++----- 2 files changed, 39 insertions(+), 9 deletions(-) create mode 100644 cypress/components/StyledMarkdownBlock.cy.tsx diff --git a/cypress/components/StyledMarkdownBlock.cy.tsx b/cypress/components/StyledMarkdownBlock.cy.tsx new file mode 100644 index 000000000..5d36aea35 --- /dev/null +++ b/cypress/components/StyledMarkdownBlock.cy.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { StyledMarkdownBlock } from '~/components/StyledMarkdownBlock'; + +describe('StyledMarkdownBlock', () => { + it('should render a single-column table without crashing', () => { + const markdown = ` +| Header | +| ------ | +| Cell 1 | +| Cell 2 | +`; + cy.mount(); + cy.get('table').should('exist'); + cy.get('th').should('have.length', 1).and('contain.text', 'Header'); + cy.get('td').should('have.length', 2); + cy.get('td').eq(0).should('contain.text', 'Cell 1'); + cy.get('td').eq(1).should('contain.text', 'Cell 2'); + }); + + it('should render a multi-column table without crashing', () => { + const markdown = ` +| Col A | Col B | +| ----- | ----- | +| A1 | B1 | +`; + cy.mount(); + cy.get('table').should('exist'); + cy.get('th').should('have.length', 2); + cy.get('td').should('have.length', 2); + }); +}); diff --git a/lib/markdownUtils.ts b/lib/markdownUtils.ts index b0d89691e..6a32475a7 100644 --- a/lib/markdownUtils.ts +++ b/lib/markdownUtils.ts @@ -1,3 +1,4 @@ +import React from 'react'; import getFindResultsByGlobalRegExp from '~/lib/getFindResultsByGlobalRegExp'; export const REGEX_TAB_GROUPS = @@ -45,18 +46,16 @@ export const hiddenElements = (...elements: string[]) => { }, {}); }; -export const checkHasContent = (reactNode: React.ReactChild) => { +export const checkHasContent = (reactNode: React.ReactChild): boolean => { if (!reactNode) return false; if (typeof reactNode === 'string' || typeof reactNode === 'number') return true; - if ((reactNode?.props?.children || []).length === 0) return false; - return reactNode.props.children.reduce( - (acc: boolean, reactNode: React.ReactChild) => { - if (acc) return acc; - return checkHasContent(reactNode); - }, - false, - ); + const children = React.Children.toArray(reactNode?.props?.children ?? []); + if (children.length === 0) return false; + return children.reduce((acc: boolean, child: React.ReactNode) => { + if (acc) return acc; + return checkHasContent(child as React.ReactChild); + }, false); }; export function parseTabsFromMarkdown(markdown: string) { From 503b4445e66df5fa5353410a226ee8b2226ff2f3 Mon Sep 17 00:00:00 2001 From: Himanshu Soni Date: Sun, 22 Feb 2026 00:15:06 +0530 Subject: [PATCH 2/2] fix(ci): replace component test with utility test to fix 13.71% coverage drop --- cypress/components/StyledMarkdownBlock.cy.tsx | 31 ----------- cypress/components/markdownUtils.cy.tsx | 54 +++++++++++++++++++ 2 files changed, 54 insertions(+), 31 deletions(-) delete mode 100644 cypress/components/StyledMarkdownBlock.cy.tsx create mode 100644 cypress/components/markdownUtils.cy.tsx diff --git a/cypress/components/StyledMarkdownBlock.cy.tsx b/cypress/components/StyledMarkdownBlock.cy.tsx deleted file mode 100644 index 5d36aea35..000000000 --- a/cypress/components/StyledMarkdownBlock.cy.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import { StyledMarkdownBlock } from '~/components/StyledMarkdownBlock'; - -describe('StyledMarkdownBlock', () => { - it('should render a single-column table without crashing', () => { - const markdown = ` -| Header | -| ------ | -| Cell 1 | -| Cell 2 | -`; - cy.mount(); - cy.get('table').should('exist'); - cy.get('th').should('have.length', 1).and('contain.text', 'Header'); - cy.get('td').should('have.length', 2); - cy.get('td').eq(0).should('contain.text', 'Cell 1'); - cy.get('td').eq(1).should('contain.text', 'Cell 2'); - }); - - it('should render a multi-column table without crashing', () => { - const markdown = ` -| Col A | Col B | -| ----- | ----- | -| A1 | B1 | -`; - cy.mount(); - cy.get('table').should('exist'); - cy.get('th').should('have.length', 2); - cy.get('td').should('have.length', 2); - }); -}); diff --git a/cypress/components/markdownUtils.cy.tsx b/cypress/components/markdownUtils.cy.tsx new file mode 100644 index 000000000..3c5d1346a --- /dev/null +++ b/cypress/components/markdownUtils.cy.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { checkHasContent } from '~/lib/markdownUtils'; + +describe('checkHasContent', () => { + it('returns false for null or undefined', () => { + expect(checkHasContent(null as any)).to.be.false; + expect(checkHasContent(undefined as any)).to.be.false; + }); + + it('returns true for strings and numbers', () => { + expect(checkHasContent('text')).to.be.true; + expect(checkHasContent(123)).to.be.true; + }); + + it('handles empty elements', () => { + const empty = ; + expect(checkHasContent(empty)).to.be.false; + }); + + it('handles single child elements without crashing', () => { + const single = Single; + expect(checkHasContent(single)).to.be.true; + }); + + it('handles arrays of children', () => { + const multiple = ( + + One + Two + + ); + expect(checkHasContent(multiple)).to.be.true; + }); + + it('handles deeply nested content', () => { + const nested = ( +
+ + Deep + +
+ ); + expect(checkHasContent(nested)).to.be.true; + }); + + it('handles deeply nested empty elements', () => { + const nestedEmpty = ( +
+ +
+ ); + expect(checkHasContent(nestedEmpty)).to.be.false; + }); +});