diff --git a/packages/main/src/components/AnalyticalTable/AnalyticalTable.cy.tsx b/packages/main/src/components/AnalyticalTable/AnalyticalTable.cy.tsx index a40eb4a4c3b..8a5aad9b6c8 100644 --- a/packages/main/src/components/AnalyticalTable/AnalyticalTable.cy.tsx +++ b/packages/main/src/components/AnalyticalTable/AnalyticalTable.cy.tsx @@ -2486,131 +2486,176 @@ describe('AnalyticalTable', () => { cy.findByText('A').shouldNotBeClickable(done); }); - it('render subcomponents', () => { - const renderRowSubComponentLarge = (row) => { - return ( -
- {`SubComponent ${row.index}`} -
- ); - }; - const renderRowSubComponent = () => { - return
SubComponent
; - }; + // `describe` is used to clean-up zoom level after test + describe('render subcomponents', () => { + let originalZoom: string; + before(() => { + originalZoom = document.documentElement.style.zoom; + }); + after(() => { + document.documentElement.style.zoom = originalZoom; + }); - const onlyFirstRowWithSubcomponent = (row) => { - if (row.id === '0') { - return
SingleSubComponent
; - } - }; - cy.mount(); + // ~700 callback invocations per mount at default zoom; broken fractional-zoom loop produces 5000+ + const LOOP_BUDGET_PER_MOUNT = 2000; - cy.findAllByTitle('Expand Node').should('have.length', 4); - cy.findAllByTitle('Collapse Node').should('not.exist'); + [ + { zoom: '1', label: 'default zoom' }, + { zoom: '1.1', label: 'fractional zoom (1.1)' }, + ].forEach(({ zoom, label }) => { + it(label, () => { + document.documentElement.style.zoom = zoom; + + let renderCallCount = 0; + const countCalls = (fn) => (row) => { + renderCallCount++; + return fn(row); + }; + const expectBoundedAndReset = () => { + cy.then(() => { + expect(renderCallCount).to.be.lessThan(LOOP_BUDGET_PER_MOUNT); + renderCallCount = 0; + }); + }; - cy.get('[aria-rowindex="2"] > [aria-colindex="1"] > [title="Expand Node"] > [ui5-button]').click(); + const renderRowSubComponentLarge = countCalls((row) => { + return ( +
+ {`SubComponent ${row.index}`} +
+ ); + }); + const renderRowSubComponent = countCalls(() => { + return
SubComponent
; + }); + const onlyFirstRowWithSubcomponent = countCalls((row) => { + if (row.id === '0') { + return
SingleSubComponent
; + } + }); - cy.findAllByTitle('Expand Node').should('have.length', 3); - cy.findAllByTitle('Collapse Node').should('have.length', 1); - cy.findByText('SubComponent').should('be.visible'); + cy.mount(); - cy.get('[aria-rowindex="3"] > [aria-colindex="1"] > [title="Expand Node"] > [ui5-button]').click(); + cy.findAllByTitle('Expand Node').should('have.length', 4); + cy.findAllByTitle('Collapse Node').should('not.exist'); - cy.findAllByTitle('Expand Node').should('have.length', 2); - cy.findAllByTitle('Collapse Node').should('have.length', 2); - cy.findAllByText('SubComponent').should('be.visible').should('have.length', 2); + cy.get('[aria-rowindex="2"] > [aria-colindex="1"] > [title="Expand Node"] > [ui5-button]').click(); - cy.mount(); + cy.findAllByTitle('Expand Node').should('have.length', 3); + cy.findAllByTitle('Collapse Node').should('have.length', 1); + cy.findByText('SubComponent').should('be.visible'); - cy.findAllByTitle('Expand Node').should('have.length', 1); - cy.findAllByTitle('Collapse Node').should('not.exist'); + cy.get('[aria-rowindex="3"] > [aria-colindex="1"] > [title="Expand Node"] > [ui5-button]').click(); - cy.get('[aria-rowindex="2"] > [aria-colindex="1"] > [title="Expand Node"] > [ui5-button]').click(); + cy.findAllByTitle('Expand Node').should('have.length', 2); + cy.findAllByTitle('Collapse Node').should('have.length', 2); + cy.findAllByText('SubComponent').should('be.visible').should('have.length', 2); + expectBoundedAndReset(); - cy.findAllByTitle('Expand Node').should('not.exist'); - cy.findAllByTitle('Collapse Node').should('have.length', 1); - cy.findByText('SingleSubComponent').should('be.visible'); - cy.get('[aria-rowindex="3"] > [aria-colindex="1"] > [title="Expand Node"] > [ui5-button]').should('not.exist'); + cy.mount( + , + ); - cy.mount( - , - ); - cy.findAllByText('SubComponent').should('be.visible').should('have.length', 4); - cy.findByTitle('Expand Node').should('not.exist'); - cy.findByTitle('Collapse Node').should('not.exist'); + cy.findAllByTitle('Expand Node').should('have.length', 1); + cy.findAllByTitle('Collapse Node').should('not.exist'); - cy.mount( - , - ); - cy.findByText('SingleSubComponent').should('be.visible').should('have.length', 1); - cy.findByTitle('Expand Node').should('not.exist'); - cy.findByTitle('Collapse Node').should('not.exist'); + cy.get('[aria-rowindex="2"] > [aria-colindex="1"] > [title="Expand Node"] > [ui5-button]').click(); - cy.mount( - , - ); - cy.wait(300); + cy.findAllByTitle('Expand Node').should('not.exist'); + cy.findAllByTitle('Collapse Node').should('have.length', 1); + cy.findByText('SingleSubComponent').should('be.visible'); + cy.get('[aria-rowindex="3"] > [aria-colindex="1"] > [title="Expand Node"] > [ui5-button]').should('not.exist'); + expectBoundedAndReset(); - cy.findByText('SubComponent 1').should('exist').and('not.be.visible'); - cy.findByTitle('Expand Node').should('not.exist'); - cy.findByTitle('Collapse Node').should('not.exist'); + cy.mount( + , + ); + cy.findAllByText('SubComponent').should('be.visible').should('have.length', 4); + cy.findByTitle('Expand Node').should('not.exist'); + cy.findByTitle('Collapse Node').should('not.exist'); + expectBoundedAndReset(); - cy.mount( - , - ); - cy.findByText('SubComponent 1').should('be.visible'); - cy.findByText('SubComponent 2').should('be.visible'); - cy.findByTitle('Expand Node').should('not.exist'); - cy.findByTitle('Collapse Node').should('not.exist'); + cy.mount( + , + ); + cy.findByText('SingleSubComponent').should('be.visible').should('have.length', 1); + cy.findByTitle('Expand Node').should('not.exist'); + cy.findByTitle('Collapse Node').should('not.exist'); + expectBoundedAndReset(); - const loadMore = cy.spy().as('more'); - cy.mount( - , - ); - cy.findByText('A').should('be.visible'); - cy.findByText('X').should('be.visible'); - cy.findByText('C').should('not.be.visible'); - cy.get('[aria-rowindex="2"] > [aria-colindex="1"] > [title="Expand Node"] > [ui5-button]').click(); - cy.findByText('A').should('be.visible'); - cy.findByText('X').should('be.visible'); - cy.findByText('C').should('not.be.visible'); - cy.get('[aria-rowindex="3"] > [aria-colindex="1"] > [title="Expand Node"] > [ui5-button]').click(); - cy.findByText('A').should('be.visible'); - cy.findByText('X').should('be.visible'); - cy.findByText('C').should('not.be.visible'); - cy.get('[data-component-name="AnalyticalTableBody"]').scrollTo('bottom'); - cy.get('@more').should('have.been.calledOnce'); + cy.mount( + , + ); + cy.wait(300); + + cy.findByText('SubComponent 1').should('exist').and('not.be.visible'); + cy.findByTitle('Expand Node').should('not.exist'); + cy.findByTitle('Collapse Node').should('not.exist'); + expectBoundedAndReset(); + + cy.mount( + , + ); + cy.findByText('SubComponent 1').should('be.visible'); + cy.findByText('SubComponent 2').should('be.visible'); + cy.findByTitle('Expand Node').should('not.exist'); + cy.findByTitle('Collapse Node').should('not.exist'); + expectBoundedAndReset(); + + const loadMore = cy.spy().as('more'); + cy.mount( + , + ); + cy.findByText('A').should('be.visible'); + cy.findByText('X').should('be.visible'); + cy.findByText('C').should('not.be.visible'); + cy.get('[aria-rowindex="2"] > [aria-colindex="1"] > [title="Expand Node"] > [ui5-button]').click(); + cy.findByText('A').should('be.visible'); + cy.findByText('X').should('be.visible'); + cy.findByText('C').should('not.be.visible'); + cy.get('[aria-rowindex="3"] > [aria-colindex="1"] > [title="Expand Node"] > [ui5-button]').click(); + cy.findByText('A').should('be.visible'); + cy.findByText('X').should('be.visible'); + cy.findByText('C').should('not.be.visible'); + if (zoom === '1') { + // Cypress' scrollTo('bottom') uses BCR-style coords and doesn't fire onLoadMore reliably under CSS zoom; verified at zoom=1 only + cy.get('[data-component-name="AnalyticalTableBody"]').scrollTo('bottom'); + cy.get('@more').should('have.been.calledOnce'); + } + expectBoundedAndReset(); + }); + }); }); if (reactVersion.startsWith('19')) { diff --git a/packages/main/src/components/AnalyticalTable/TableBody/RowSubComponent.tsx b/packages/main/src/components/AnalyticalTable/TableBody/RowSubComponent.tsx index ec10d51ea43..2f56055a2b9 100644 --- a/packages/main/src/components/AnalyticalTable/TableBody/RowSubComponent.tsx +++ b/packages/main/src/components/AnalyticalTable/TableBody/RowSubComponent.tsx @@ -40,7 +40,9 @@ export function RowSubComponent(props: RowSubComponentProps) { return; } - const measureAndDispatch = (height: number) => { + const measureAndDispatch = (rawHeight: number) => { + // ceil: avoid under-estimation (would overlap next row) and absorb sub-px precision diff between getComputedStyle and borderBoxSize + const height = Math.ceil(rawHeight); const prev: { rowId?: string; subComponentHeight?: number } = subComponentsHeight?.[virtualRow.index] ?? {}; if (height === 0 || (prev.subComponentHeight === height && prev.rowId === row.id)) { return; @@ -72,7 +74,8 @@ export function RowSubComponent(props: RowSubComponentProps) { } }; - measureAndDispatch(subComponentElement.getBoundingClientRect().height); + // sync initial read for first-paint correctness; blockSize matches borderBoxSize coords (getBoundingClientRect returns zoom-applied) + measureAndDispatch(parseFloat(getComputedStyle(subComponentElement).blockSize)); const observer = new ResizeObserver(([entry]) => { measureAndDispatch(entry.borderBoxSize[0].blockSize);