From 6721aa4c3458ced6bad180648ea071f70b39745d Mon Sep 17 00:00:00 2001 From: Bernardo Sunderhus Date: Tue, 25 Aug 2026 09:42:16 +0200 Subject: [PATCH] test: cover overflow menu convergence loop (#36612) --- .../src/overflowManager.test.ts | 45 ++++++++++ .../library/src/Overflow.cy.tsx | 83 ++++++++++++++++++- 2 files changed, 127 insertions(+), 1 deletion(-) diff --git a/packages/react-components/priority-overflow/src/overflowManager.test.ts b/packages/react-components/priority-overflow/src/overflowManager.test.ts index 70299140d146c..3be8a99ca9fcd 100644 --- a/packages/react-components/priority-overflow/src/overflowManager.test.ts +++ b/packages/react-components/priority-overflow/src/overflowManager.test.ts @@ -304,6 +304,51 @@ describe('overflowManager', () => { expect(getVisibleIds(manager)).toEqual(['a', 'b']); }); + it('should converge when a conditional overflow menu changes item widths', () => { + const container = createContainer(110); + const menu = createElementWithSize('button', 30); + const invisibleItemCounts: number[] = []; + let menuAttached = false; + + const manager = createOverflowManager( + createObserveOptions({ + onUpdateOverflow: () => { + const invisibleItemCount = manager.getSnapshot().invisibleItemCount; + invisibleItemCounts.push(invisibleItemCount); + + // Cap a regression so this test fails with the oscillating states instead of recursing indefinitely. + if (invisibleItemCounts.length >= 6) { + return; + } + + if (invisibleItemCount > 0 && !menuAttached) { + menuAttached = true; + manager.addOverflowMenu(menu); + } else if (invisibleItemCount === 0 && menuAttached) { + menuAttached = false; + manager.removeOverflowMenu(); + } + }, + }), + ); + + const createResponsiveItem = () => { + const item = document.createElement('button'); + Object.defineProperty(item, 'offsetWidth', { + configurable: true, + get: () => (menuAttached ? 35 : 60), + }); + return item; + }; + + manager.addItem({ element: createResponsiveItem(), id: 'a', priority: 1 }); + manager.addItem({ element: createResponsiveItem(), id: 'b', priority: 0 }); + manager.observe(container, { forceUpdate: true }); + + expect(invisibleItemCounts).toEqual([1, 0]); + expect(getVisibleIds(manager)).toEqual(['a', 'b']); + }); + it('should recompute when the overflow menu is removed with hidden items', () => { const manager = createOverflowManager(createObserveOptions()); const container = createContainer(140); diff --git a/packages/react-components/react-overflow/library/src/Overflow.cy.tsx b/packages/react-components/react-overflow/library/src/Overflow.cy.tsx index faa43c2931366..278343ce588b7 100644 --- a/packages/react-components/react-overflow/library/src/Overflow.cy.tsx +++ b/packages/react-components/react-overflow/library/src/Overflow.cy.tsx @@ -108,7 +108,7 @@ const Menu: React.FC<{ width?: number }> = ({ width }) => { // No need to actually render a menu, we're testing state return ( <> - @@ -254,6 +254,87 @@ describe('Overflow', () => { }); }); + it('should keep the menu visible when one phone number overflows next to a divider', () => { + cy.on('uncaught:exception', error => { + if (error.message.includes('ResizeObserver loop completed with undelivered notifications')) { + return false; + } + }); + + const PhoneNumbers = () => { + const [width, setWidth] = React.useState(280); + + return ( + <> + {[40, 180, 250, 280].map(nextWidth => ( + + ))} + + + + +4790981948 + + + + + + + + +47 (2) 3011302 + + + + + + ); + }; + + const setPhoneContainerWidth = (width: number) => { + cy.get(`[data-test-width="${width}"]`).click(); + }; + + mount(); + cy.get(`[${selectors.container}]`).should('have.css', 'width', '280px'); + cy.window().then(win => new Cypress.Promise(resolve => win.requestAnimationFrame(() => resolve()))); + + setPhoneContainerWidth(40); + cy.get(`[${selectors.menu}]`).should('have.text', '+2'); + + setPhoneContainerWidth(180); + cy.get(`[${selectors.item}="primary-phone"]`).should('not.have.attr', 'data-overflowing'); + cy.get(`[${selectors.divider}="primary-phone-group"]`).should('not.have.attr', 'data-overflowing'); + cy.get(`[${selectors.item}="secondary-phone"]`).should('have.attr', 'data-overflowing'); + cy.get(`[${selectors.menu}]`).should('have.text', '+1'); + + setPhoneContainerWidth(250); + cy.get(`[${selectors.item}="primary-phone"]`).should('not.have.attr', 'data-overflowing'); + cy.get(`[${selectors.divider}="primary-phone-group"]`).should('not.have.attr', 'data-overflowing'); + cy.get(`[${selectors.item}="secondary-phone"]`).should('have.attr', 'data-overflowing'); + cy.get(`[${selectors.menu}]`).should('have.text', '+1'); + + setPhoneContainerWidth(280); + cy.get(`[${selectors.item}="primary-phone"]`).should('not.have.attr', 'data-overflowing'); + cy.get(`[${selectors.divider}="primary-phone-group"]`).should('not.have.attr', 'data-overflowing'); + cy.get(`[${selectors.item}="secondary-phone"]`).should('not.have.attr', 'data-overflowing'); + cy.get(`[${selectors.menu}]`).should('not.exist'); + }); + it(`should overflow items when there's more than one child element`, () => { const mapHelper = new Array(10).fill(0).map((_, i) => i); const overflowElementIndex = 6;