Skip to content

Commit

Permalink
Add tests and fix linter issues
Browse files Browse the repository at this point in the history
  • Loading branch information
GSS-Rishi committed Nov 24, 2023
1 parent 7d06304 commit 4b70da5
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 67 deletions.
14 changes: 14 additions & 0 deletions docs/data/material/components/masonry/Sequential.tsx.preview
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Masonry
columns={4}
spacing={2}
defaultHeight={450}
defaultColumns={4}
defaultSpacing={1}
sequential
>
{heights.map((height, index) => (
<Item key={index} sx={{ height }}>
{index + 1}
</Item>
))}
</Masonry>
134 changes: 67 additions & 67 deletions packages/mui-lab/src/Masonry/Masonry.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,83 +212,83 @@ const Masonry = React.forwardRef(function Masonry(inProps, ref) {

const classes = useUtilityClasses(ownerState);

const handleResize = (masonryChildren) => {
if (!masonryRef.current || !masonryChildren || masonryChildren.length === 0) {
return;
}
useEnhancedEffect(() => {
const handleResize = (masonryChildren) => {
if (!masonryRef.current || !masonryChildren || masonryChildren.length === 0) {
return;
}

const masonry = masonryRef.current;
const masonryFirstChild = masonryRef.current.firstChild;
const parentWidth = masonry.clientWidth;
const firstChildWidth = masonryFirstChild.clientWidth;
const masonry = masonryRef.current;
const masonryFirstChild = masonryRef.current.firstChild;
const parentWidth = masonry.clientWidth;
const firstChildWidth = masonryFirstChild.clientWidth;

if (parentWidth === 0 || firstChildWidth === 0) {
return;
}
if (parentWidth === 0 || firstChildWidth === 0) {
return;
}

const firstChildComputedStyle = window.getComputedStyle(masonryFirstChild);
const firstChildMarginLeft = parseToNumber(firstChildComputedStyle.marginLeft);
const firstChildMarginRight = parseToNumber(firstChildComputedStyle.marginRight);
const firstChildComputedStyle = window.getComputedStyle(masonryFirstChild);
const firstChildMarginLeft = parseToNumber(firstChildComputedStyle.marginLeft);
const firstChildMarginRight = parseToNumber(firstChildComputedStyle.marginRight);

const currentNumberOfColumns = Math.round(
parentWidth / (firstChildWidth + firstChildMarginLeft + firstChildMarginRight),
);
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 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) {
skip = true;
return;
}
// 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) {
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 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) {
skip = true;
break;
return;
}
}
if (!skip) {
if (sequential) {
columnHeights[nextOrder - 1] += childHeight;
child.style.order = nextOrder;
nextOrder += 1;
if (nextOrder > currentNumberOfColumns) {
nextOrder = 1;
// 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;
}
} 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) {
// 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);
});
}
};
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);
});
}
};

useEnhancedEffect(() => {
// IE and old browsers are not supported
if (typeof ResizeObserver === 'undefined') {
return undefined;
Expand All @@ -315,7 +315,7 @@ const Masonry = React.forwardRef(function Masonry(inProps, ref) {
resizeObserver.disconnect();
}
};
}, [columns, spacing, children]);
}, [columns, spacing, children, sequential]);

const handleRef = useForkRef(ref, masonryRef);

Expand Down
31 changes: 31 additions & 0 deletions packages/mui-lab/src/Masonry/Masonry.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -369,4 +369,35 @@ describe('<Masonry />', () => {
});
});
});

describe('prop: sequential', () => {
it('should place children in sequential order', function test() {
if (/jsdom/.test(window.navigator.userAgent)) {
// only run on browser
this.skip();
}
const firstChildHeight = 20;
const secondChildHeight = 10;
const thirdChildHeight = 10;

const { getByTestId } = render(
<Masonry columns={2} data-testid="sequential" sequential>
<div style={{ height: `${firstChildHeight}px` }} />
<div style={{ height: `${secondChildHeight}px` }} />
<div style={{ height: `${thirdChildHeight}px` }} />
</Masonry>,
);
const masonry = getByTestId('sequential');

expect(window.getComputedStyle(masonry).height).to.equal(
`${firstChildHeight + thirdChildHeight}px`,
);

expect(window.getComputedStyle(masonry.children[0]).style).to.equal(`order: 1;`);

expect(window.getComputedStyle(masonry.children[1]).style).to.equal(`order: 2;`);

expect(window.getComputedStyle(masonry.children[2]).style).to.equal(`order: 1;`);
});
});
});

0 comments on commit 4b70da5

Please sign in to comment.