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);