From 283c68f5cf4b2bd378cf9382ed49de615a791fcc Mon Sep 17 00:00:00 2001 From: Fanzzzd Date: Fri, 27 Sep 2024 22:59:37 +0800 Subject: [PATCH 1/6] [Lab][Masonry] Fix hidden first item causing layout to collapse --- packages/mui-lab/src/Masonry/Masonry.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/mui-lab/src/Masonry/Masonry.js b/packages/mui-lab/src/Masonry/Masonry.js index 243532ef6dfbdd..2afa3c6ab27e18 100644 --- a/packages/mui-lab/src/Masonry/Masonry.js +++ b/packages/mui-lab/src/Masonry/Masonry.js @@ -220,15 +220,21 @@ const Masonry = React.forwardRef(function Masonry(inProps, ref) { } const masonry = masonryRef.current; - const masonryFirstChild = masonryRef.current.firstChild; + // Find the first visible child + const masonryFirstVisibleChild = Array.from(masonry.childNodes).find((child) => { + const childStyle = window.getComputedStyle(child); + return childStyle.display !== 'none' && childStyle.visibility !== 'hidden'; + }); + if (!masonryFirstVisibleChild) { + return; + } const parentWidth = masonry.clientWidth; - const firstChildWidth = masonryFirstChild.clientWidth; + const firstChildWidth = masonryFirstVisibleChild.clientWidth; if (parentWidth === 0 || firstChildWidth === 0) { return; } - - const firstChildComputedStyle = window.getComputedStyle(masonryFirstChild); + const firstChildComputedStyle = window.getComputedStyle(masonryFirstVisibleChild); const firstChildMarginLeft = parseToNumber(firstChildComputedStyle.marginLeft); const firstChildMarginRight = parseToNumber(firstChildComputedStyle.marginRight); @@ -243,6 +249,8 @@ const Masonry = React.forwardRef(function Masonry(inProps, ref) { if (child.nodeType !== Node.ELEMENT_NODE || child.dataset.class === 'line-break' || skip) { return; } + // Get the visibility of the child element to check if it has appeared in the window + const childVisibility = child.style.visibility; const childComputedStyle = window.getComputedStyle(child); const childMarginTop = parseToNumber(childComputedStyle.marginTop); const childMarginBottom = parseToNumber(childComputedStyle.marginBottom); @@ -250,7 +258,7 @@ const Masonry = React.forwardRef(function Masonry(inProps, ref) { const childHeight = parseToNumber(childComputedStyle.height) ? Math.ceil(parseToNumber(childComputedStyle.height)) + childMarginTop + childMarginBottom : 0; - if (childHeight === 0) { + if (childHeight === 0 && childVisibility === 'hidden') { skip = true; return; } From 0f207a02d87dc9c21527a07c005e43733afb86a9 Mon Sep 17 00:00:00 2001 From: fanzzzd Date: Mon, 21 Jul 2025 16:19:13 +0800 Subject: [PATCH 2/6] fix(lab): [Masonry] Fix layout flicker and single column issue This commit addresses two long-standing issues with the Masonry component: 1. A flicker/layout shift on initial render, where items would briefly appear in a single column. 2. The layout breaking into a single column when the first item is hidden. The fix involves: - Removing `requestAnimationFrame` from the `ResizeObserver` to prevent rendering delays. - Using `ReactDOM.flushSync` to ensure DOM updates are painted synchronously. - Introducing a `MutationObserver` to dynamically track and measure children, making the layout updates more robust. - Modifying the layout logic to find the first *visible* child for width calculation, which correctly handles cases where initial items are hidden. Fixes #36673 Fixes #42611 --- packages/mui-lab/src/Masonry/Masonry.js | 202 +++++++++++++----------- 1 file changed, 107 insertions(+), 95 deletions(-) diff --git a/packages/mui-lab/src/Masonry/Masonry.js b/packages/mui-lab/src/Masonry/Masonry.js index 25de2aa9b8975d..c443749c54bd05 100644 --- a/packages/mui-lab/src/Masonry/Masonry.js +++ b/packages/mui-lab/src/Masonry/Masonry.js @@ -208,126 +208,138 @@ const Masonry = React.forwardRef(function Masonry(inProps, ref) { const classes = useUtilityClasses(ownerState); - const handleResize = React.useCallback( - (masonryChildren) => { - if (!masonryRef.current || !masonryChildren || masonryChildren.length === 0) { + const handleResize = React.useCallback(() => { + if (!masonryRef.current) { + return; + } + + const masonry = masonryRef.current; + const firstVisibleChild = Array.from(masonry.childNodes).find( + (child) => + child.nodeType === Node.ELEMENT_NODE && + child.dataset.class !== 'line-break' && + window.getComputedStyle(child).display !== 'none', + ); + + if (!firstVisibleChild) { + return; + } + + const parentWidth = masonry.clientWidth; + const firstChildWidth = firstVisibleChild.clientWidth; + + if (parentWidth === 0 || firstChildWidth === 0) { + return; + } + + const firstChildComputedStyle = window.getComputedStyle(firstVisibleChild); + const firstChildMarginLeft = parseToNumber(firstChildComputedStyle.marginLeft); + const firstChildMarginRight = parseToNumber(firstChildComputedStyle.marginRight); + + const currentNumberOfColumns = Math.round( + parentWidth / (firstChildWidth + firstChildMarginLeft + firstChildMarginRight), + ); + + const columnHeights = new Array(currentNumberOfColumns).fill(0); + let skip = false; + let nextOrder = 1; + masonry.childNodes.forEach((child) => { + if (child.nodeType !== Node.ELEMENT_NODE || child.dataset.class === 'line-break' || skip) { return; } - - const masonry = masonryRef.current; - // Find the first visible child - const masonryFirstVisibleChild = Array.from(masonry.childNodes).find((child) => { - const childStyle = window.getComputedStyle(child); - return childStyle.display !== 'none' && childStyle.visibility !== 'hidden'; - }); - if (!masonryFirstVisibleChild) { + const childComputedStyle = window.getComputedStyle(child); + if (childComputedStyle.display === 'none') { return; } - const parentWidth = masonry.clientWidth; - const firstChildWidth = masonryFirstVisibleChild.clientWidth; - - if (parentWidth === 0 || firstChildWidth === 0) { + const childMarginTop = parseToNumber(childComputedStyle.marginTop); + const childMarginBottom = parseToNumber(childComputedStyle.marginBottom); + const childHeight = parseToNumber(childComputedStyle.height) + ? Math.ceil(parseToNumber(childComputedStyle.height)) + childMarginTop + childMarginBottom + : 0; + if (childHeight === 0) { + skip = true; return; } - const firstChildComputedStyle = window.getComputedStyle(masonryFirstVisibleChild); - const firstChildMarginLeft = parseToNumber(firstChildComputedStyle.marginLeft); - const firstChildMarginRight = parseToNumber(firstChildComputedStyle.marginRight); - - const currentNumberOfColumns = Math.round( - parentWidth / (firstChildWidth + firstChildMarginLeft + firstChildMarginRight), - ); - - const columnHeights = new Array(currentNumberOfColumns).fill(0); - let skip = false; - let nextOrder = 1; - masonry.childNodes.forEach((child) => { - if (child.nodeType !== Node.ELEMENT_NODE || child.dataset.class === 'line-break' || skip) { - return; - } - // Get the visibility of the child element to check if it has appeared in the window - const childVisibility = child.style.visibility; - const childComputedStyle = window.getComputedStyle(child); - const childMarginTop = parseToNumber(childComputedStyle.marginTop); - const childMarginBottom = parseToNumber(childComputedStyle.marginBottom); - // if any one of children isn't rendered yet, masonry's height shouldn't be computed yet - const childHeight = parseToNumber(childComputedStyle.height) - ? Math.ceil(parseToNumber(childComputedStyle.height)) + childMarginTop + childMarginBottom - : 0; - if (childHeight === 0 && childVisibility === 'hidden') { + for (let i = 0; i < child.childNodes.length; i += 1) { + const nestedChild = child.childNodes[i]; + if (nestedChild.tagName === 'IMG' && nestedChild.clientHeight === 0) { skip = true; - return; + break; } - // if there is a nested image that isn't rendered yet, masonry's height shouldn't be computed yet - for (let i = 0; i < child.childNodes.length; i += 1) { - const nestedChild = child.childNodes[i]; - if (nestedChild.tagName === 'IMG' && nestedChild.clientHeight === 0) { - skip = true; - break; - } - } - if (!skip) { - if (sequential) { - columnHeights[nextOrder - 1] += childHeight; - child.style.order = nextOrder; - nextOrder += 1; - if (nextOrder > currentNumberOfColumns) { - nextOrder = 1; - } - } else { - // find the current shortest column (where the current item will be placed) - const currentMinColumnIndex = columnHeights.indexOf(Math.min(...columnHeights)); - columnHeights[currentMinColumnIndex] += childHeight; - const order = currentMinColumnIndex + 1; - child.style.order = order; + } + if (!skip) { + if (sequential) { + columnHeights[nextOrder - 1] += childHeight; + child.style.order = nextOrder; + nextOrder += 1; + if (nextOrder > currentNumberOfColumns) { + nextOrder = 1; } + } else { + const currentMinColumnIndex = columnHeights.indexOf(Math.min(...columnHeights)); + columnHeights[currentMinColumnIndex] += childHeight; + const order = currentMinColumnIndex + 1; + child.style.order = order; } - }); - if (!skip) { - // In React 18, state updates in a ResizeObserver's callback are happening after the paint which causes flickering - // when doing some visual updates in it. Using flushSync ensures that the dom will be painted after the states updates happen - // Related issue - https://github.com/facebook/react/issues/24331 - ReactDOM.flushSync(() => { - setMaxColumnHeight(Math.max(...columnHeights)); - setNumberOfLineBreaks(currentNumberOfColumns > 0 ? currentNumberOfColumns - 1 : 0); - }); } - }, - [sequential], - ); + }); + if (!skip) { + ReactDOM.flushSync(() => { + setMaxColumnHeight(Math.max(...columnHeights)); + setNumberOfLineBreaks(currentNumberOfColumns > 0 ? currentNumberOfColumns - 1 : 0); + }); + } + }, [sequential]); useEnhancedEffect(() => { - // IE and old browsers are not supported - if (typeof ResizeObserver === 'undefined') { + if (typeof ResizeObserver === 'undefined' || typeof MutationObserver === 'undefined') { return undefined; } - let animationFrame; + const masonry = masonryRef.current; + if (!masonry) { + return undefined; + } - const resizeObserver = new ResizeObserver(() => { - // see https://github.com/mui/material-ui/issues/36909 - animationFrame = requestAnimationFrame(handleResize); + const resizeObserver = new ResizeObserver(handleResize); + // Observes for child additions or removals to update the layout. + const mutationObserver = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if (node instanceof HTMLElement && node.dataset.class !== 'line-break') { + resizeObserver.observe(node); + } + }); + mutation.removedNodes.forEach((node) => { + if (node instanceof HTMLElement && node.dataset.class !== 'line-break') { + resizeObserver.unobserve(node); + } + }); + }); + handleResize(); }); - if (masonryRef.current) { - masonryRef.current.childNodes.forEach((childNode) => { + Array.from(masonry.childNodes).forEach((childNode) => { + if (childNode instanceof HTMLElement && childNode.dataset.class !== 'line-break') { resizeObserver.observe(childNode); - }); - } + } + }); + + mutationObserver.observe(masonry, { + childList: true, + }); + + handleResize(); return () => { - if (animationFrame) { - cancelAnimationFrame(animationFrame); - } - if (resizeObserver) { - resizeObserver.disconnect(); - } + resizeObserver.disconnect(); + mutationObserver.disconnect(); }; - }, [columns, spacing, children, handleResize]); + }, [handleResize]); const handleRef = useForkRef(ref, masonryRef); - // columns are likely to have different heights and hence can start to merge; - // a line break at the end of each column prevents columns from merging + // A line break is added to the end of each column to prevent columns from merging. const lineBreaks = new Array(numberOfLineBreaks) .fill('') .map((_, index) => ( @@ -417,4 +429,4 @@ Masonry.propTypes /* remove-proptypes */ = { ]), }; -export default Masonry; +export default Masonry; \ No newline at end of file From 29b06d890a6c6893916da2cc91d02866d3f1f381 Mon Sep 17 00:00:00 2001 From: fanzzzd Date: Mon, 21 Jul 2025 16:29:48 +0800 Subject: [PATCH 3/6] style: format Masonry.js with prettier --- packages/mui-lab/src/Masonry/Masonry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mui-lab/src/Masonry/Masonry.js b/packages/mui-lab/src/Masonry/Masonry.js index c443749c54bd05..ff40a30c824a34 100644 --- a/packages/mui-lab/src/Masonry/Masonry.js +++ b/packages/mui-lab/src/Masonry/Masonry.js @@ -429,4 +429,4 @@ Masonry.propTypes /* remove-proptypes */ = { ]), }; -export default Masonry; \ No newline at end of file +export default Masonry; From 4f83b825a9f35b377a0aa913e3c0f29f33bb4905 Mon Sep 17 00:00:00 2001 From: fanzzzd Date: Thu, 24 Jul 2025 10:26:27 +0800 Subject: [PATCH 4/6] fix(lab, Masonry): defer flushSync to microtask to prevent warning The previous change using `flushSync` directly in the `handleResize` callback fixed a layout flicker but introduced a React warning: "flushSync was called from inside a lifecycle method". This commit wraps the `flushSync` call in a `Promise.resolve().then()` to move it to a microtask. This resolves the warning by ensuring React is not in the middle of a render cycle when `flushSync` is called, while still being fast enough to avoid the visual flicker that `requestAnimationFrame` could cause. An additional check for `masonryRef.current` is added inside the promise callback to guard against updates on an unmounted component. Refs #36673, #42611, #36909 --- packages/mui-lab/src/Masonry/Masonry.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/mui-lab/src/Masonry/Masonry.js b/packages/mui-lab/src/Masonry/Masonry.js index ff40a30c824a34..47ccef348048a4 100644 --- a/packages/mui-lab/src/Masonry/Masonry.js +++ b/packages/mui-lab/src/Masonry/Masonry.js @@ -284,9 +284,13 @@ const Masonry = React.forwardRef(function Masonry(inProps, ref) { } }); if (!skip) { - ReactDOM.flushSync(() => { - setMaxColumnHeight(Math.max(...columnHeights)); - setNumberOfLineBreaks(currentNumberOfColumns > 0 ? currentNumberOfColumns - 1 : 0); + Promise.resolve().then(() => { + if (masonryRef.current) { + ReactDOM.flushSync(() => { + setMaxColumnHeight(Math.max(...columnHeights)); + setNumberOfLineBreaks(currentNumberOfColumns > 0 ? currentNumberOfColumns - 1 : 0); + }); + } }); } }, [sequential]); From 686d1543423fa9ad65fcfca907f16d26f0266a93 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Tue, 9 Sep 2025 11:54:40 +0700 Subject: [PATCH 5/6] apply improvements --- packages/mui-lab/src/Masonry/Masonry.js | 34 +++++--- packages/mui-lab/src/Masonry/Masonry.test.js | 83 ++++++++++++++++++++ 2 files changed, 107 insertions(+), 10 deletions(-) diff --git a/packages/mui-lab/src/Masonry/Masonry.js b/packages/mui-lab/src/Masonry/Masonry.js index 47ccef348048a4..fb00ffd636e0e6 100644 --- a/packages/mui-lab/src/Masonry/Masonry.js +++ b/packages/mui-lab/src/Masonry/Masonry.js @@ -284,14 +284,21 @@ const Masonry = React.forwardRef(function Masonry(inProps, ref) { } }); if (!skip) { - Promise.resolve().then(() => { - if (masonryRef.current) { - ReactDOM.flushSync(() => { - setMaxColumnHeight(Math.max(...columnHeights)); - setNumberOfLineBreaks(currentNumberOfColumns > 0 ? currentNumberOfColumns - 1 : 0); - }); - } - }); + // Check if we're in a lifecycle where flushSync is safe + // ResizeObserver and MutationObserver callbacks are safe for flushSync + const isInObserverCallback = typeof queueMicrotask !== 'undefined'; + + if (isInObserverCallback && masonryRef.current) { + // Use flushSync directly in observer callbacks to prevent flicker + ReactDOM.flushSync(() => { + setMaxColumnHeight(Math.max(...columnHeights)); + setNumberOfLineBreaks(currentNumberOfColumns > 0 ? currentNumberOfColumns - 1 : 0); + }); + } else { + // Fallback for other contexts (initial render, prop updates) + setMaxColumnHeight(Math.max(...columnHeights)); + setNumberOfLineBreaks(currentNumberOfColumns > 0 ? currentNumberOfColumns - 1 : 0); + } } }, [sequential]); @@ -305,7 +312,13 @@ const Masonry = React.forwardRef(function Masonry(inProps, ref) { return undefined; } - const resizeObserver = new ResizeObserver(handleResize); + let resizeTimeout; + const debouncedHandleResize = () => { + clearTimeout(resizeTimeout); + resizeTimeout = setTimeout(handleResize, 16); // ~60fps + }; + + const resizeObserver = new ResizeObserver(debouncedHandleResize); // Observes for child additions or removals to update the layout. const mutationObserver = new MutationObserver((mutations) => { mutations.forEach((mutation) => { @@ -336,10 +349,11 @@ const Masonry = React.forwardRef(function Masonry(inProps, ref) { handleResize(); return () => { + clearTimeout(resizeTimeout); resizeObserver.disconnect(); mutationObserver.disconnect(); }; - }, [handleResize]); + }, [handleResize, columns, spacing, children]); const handleRef = useForkRef(ref, masonryRef); diff --git a/packages/mui-lab/src/Masonry/Masonry.test.js b/packages/mui-lab/src/Masonry/Masonry.test.js index d79510cbdd3d02..1e153d5cffae77 100644 --- a/packages/mui-lab/src/Masonry/Masonry.test.js +++ b/packages/mui-lab/src/Masonry/Masonry.test.js @@ -125,6 +125,89 @@ describe('', () => { }); }); + describe('hidden children', () => { + it('should handle hidden first child correctly', function test() { + if (/jsdom/.test(window.navigator.userAgent)) { + this.skip(); + } + + const { getByTestId } = render( + +
+
+
+ , + ); + + const masonry = getByTestId('masonry'); + const visibleChild1 = getByTestId('visible-child-1'); + const visibleChild2 = getByTestId('visible-child-2'); + const hiddenChild = getByTestId('hidden-child'); + + // Hidden child should not affect layout + expect(window.getComputedStyle(hiddenChild).order).to.equal(''); + + // Visible children should be properly ordered + expect(window.getComputedStyle(visibleChild1).order).to.not.equal(''); + expect(window.getComputedStyle(visibleChild2).order).to.not.equal(''); + + // Masonry should have correct columns (not collapsed to single column) + const masonryStyle = window.getComputedStyle(masonry); + expect(masonryStyle.display).to.equal('flex'); + expect(masonryStyle.flexDirection).to.equal('column'); + }); + + it('should handle dynamically hidden children', function test() { + if (/jsdom/.test(window.navigator.userAgent)) { + this.skip(); + } + + const { rerender, getByTestId } = render( + +
+
+ , + ); + + // Verify initial state - both children should have orders + expect(window.getComputedStyle(getByTestId('child-1')).order).to.not.equal(''); + expect(window.getComputedStyle(getByTestId('child-2')).order).to.not.equal(''); + + // Hide the first child + rerender( + +
+
+ , + ); + + // Layout should still work with hidden first child + const hiddenChildOrder = window.getComputedStyle(getByTestId('child-1')).order; + const visibleChildOrder = window.getComputedStyle(getByTestId('child-2')).order; + + // Hidden child should not have an order + expect(hiddenChildOrder).to.equal(''); + // Visible child should still have an order + expect(visibleChildOrder).to.not.equal(''); + }); + + it('should handle all children being hidden', function test() { + if (/jsdom/.test(window.navigator.userAgent)) { + this.skip(); + } + + const { container } = render( + +
+
+ , + ); + + // Should not throw an error + expect(() => container.querySelector('[class*=MuiMasonry]')).to.not.throw(); + }); + }); + describe('style attribute:', () => { it('should apply correct default styles', () => { const columns = 4; From bceedb9398b89c3b0df933248ec7b136e2823986 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Tue, 9 Sep 2025 14:50:24 +0700 Subject: [PATCH 6/6] fix flushSync error --- packages/mui-lab/src/Masonry/Masonry.js | 23 ++---- packages/mui-lab/src/Masonry/Masonry.test.js | 83 -------------------- 2 files changed, 8 insertions(+), 98 deletions(-) diff --git a/packages/mui-lab/src/Masonry/Masonry.js b/packages/mui-lab/src/Masonry/Masonry.js index fb00ffd636e0e6..a544c62d5a5a53 100644 --- a/packages/mui-lab/src/Masonry/Masonry.js +++ b/packages/mui-lab/src/Masonry/Masonry.js @@ -284,21 +284,14 @@ const Masonry = React.forwardRef(function Masonry(inProps, ref) { } }); if (!skip) { - // Check if we're in a lifecycle where flushSync is safe - // ResizeObserver and MutationObserver callbacks are safe for flushSync - const isInObserverCallback = typeof queueMicrotask !== 'undefined'; - - if (isInObserverCallback && masonryRef.current) { - // Use flushSync directly in observer callbacks to prevent flicker - ReactDOM.flushSync(() => { - setMaxColumnHeight(Math.max(...columnHeights)); - setNumberOfLineBreaks(currentNumberOfColumns > 0 ? currentNumberOfColumns - 1 : 0); - }); - } else { - // Fallback for other contexts (initial render, prop updates) - setMaxColumnHeight(Math.max(...columnHeights)); - setNumberOfLineBreaks(currentNumberOfColumns > 0 ? currentNumberOfColumns - 1 : 0); - } + queueMicrotask(() => { + if (masonryRef.current) { + ReactDOM.flushSync(() => { + setMaxColumnHeight(Math.max(...columnHeights)); + setNumberOfLineBreaks(currentNumberOfColumns > 0 ? currentNumberOfColumns - 1 : 0); + }); + } + }); } }, [sequential]); diff --git a/packages/mui-lab/src/Masonry/Masonry.test.js b/packages/mui-lab/src/Masonry/Masonry.test.js index 1e153d5cffae77..d79510cbdd3d02 100644 --- a/packages/mui-lab/src/Masonry/Masonry.test.js +++ b/packages/mui-lab/src/Masonry/Masonry.test.js @@ -125,89 +125,6 @@ describe('', () => { }); }); - describe('hidden children', () => { - it('should handle hidden first child correctly', function test() { - if (/jsdom/.test(window.navigator.userAgent)) { - this.skip(); - } - - const { getByTestId } = render( - -
-
-
- , - ); - - const masonry = getByTestId('masonry'); - const visibleChild1 = getByTestId('visible-child-1'); - const visibleChild2 = getByTestId('visible-child-2'); - const hiddenChild = getByTestId('hidden-child'); - - // Hidden child should not affect layout - expect(window.getComputedStyle(hiddenChild).order).to.equal(''); - - // Visible children should be properly ordered - expect(window.getComputedStyle(visibleChild1).order).to.not.equal(''); - expect(window.getComputedStyle(visibleChild2).order).to.not.equal(''); - - // Masonry should have correct columns (not collapsed to single column) - const masonryStyle = window.getComputedStyle(masonry); - expect(masonryStyle.display).to.equal('flex'); - expect(masonryStyle.flexDirection).to.equal('column'); - }); - - it('should handle dynamically hidden children', function test() { - if (/jsdom/.test(window.navigator.userAgent)) { - this.skip(); - } - - const { rerender, getByTestId } = render( - -
-
- , - ); - - // Verify initial state - both children should have orders - expect(window.getComputedStyle(getByTestId('child-1')).order).to.not.equal(''); - expect(window.getComputedStyle(getByTestId('child-2')).order).to.not.equal(''); - - // Hide the first child - rerender( - -
-
- , - ); - - // Layout should still work with hidden first child - const hiddenChildOrder = window.getComputedStyle(getByTestId('child-1')).order; - const visibleChildOrder = window.getComputedStyle(getByTestId('child-2')).order; - - // Hidden child should not have an order - expect(hiddenChildOrder).to.equal(''); - // Visible child should still have an order - expect(visibleChildOrder).to.not.equal(''); - }); - - it('should handle all children being hidden', function test() { - if (/jsdom/.test(window.navigator.userAgent)) { - this.skip(); - } - - const { container } = render( - -
-
- , - ); - - // Should not throw an error - expect(() => container.querySelector('[class*=MuiMasonry]')).to.not.throw(); - }); - }); - describe('style attribute:', () => { it('should apply correct default styles', () => { const columns = 4;