I had a scenario where certain presentation aspects depended on the scroll position. To make tests clearer, I defined the following mocks in test setup:
1. Mocks that ensure programmatic scrolls trigger the appropriate events:
const scrollMock = (leftOrOptions, top) => {
  let left;
  if (typeof (leftOrOptions) === 'function') {
    // eslint-disable-next-line no-param-reassign
    ({ top, left } = leftOrOptions);
  } else {
    left = leftOrOptions;
  }
  Object.assign(document.body, {
    scrollLeft: left,
    scrollTop: top,
  });
  Object.assign(window, {
    scrollX: left,
    scrollY: top,
    scrollLeft: left,
    scrollTop: top,
  }).dispatchEvent(new window.Event('scroll'));
};
const scrollByMock = function scrollByMock(left, top) { scrollMock(window.screenX + left, window.screenY + top); };
const resizeMock = (width, height) => {
  Object.defineProperties(document.body, {
    scrollHeight: { value: 1000, writable: false },
    scrollWidth: { value: 1000, writable: false },
  });
  Object.assign(window, {
    innerWidth: width,
    innerHeight: height,
    outerWidth: width,
    outerHeight: height,
  }).dispatchEvent(new window.Event('resize'));
};
const scrollIntoViewMock = function scrollIntoViewMock() {
  const [left, top] = this.getBoundingClientRect();
  window.scrollTo(left, top);
};
const getBoundingClientRectMock = function getBoundingClientRectMock() {
  let offsetParent = this;
  const result = new DOMRect(0, 0, this.offsetWidth, this.offsetHeight);
  while (offsetParent) {
    result.x += offsetParent.offsetX;
    result.y += offsetParent.offsetY;
    offsetParent = offsetParent.offsetParent;
  }
  return result;
};
function mockGlobal(key, value) {
  mockedGlobals[key] = global[key]; // this is just to be able to reset the mocks after the tests
  global[key] = value;
}
beforeAll(async () => {
  mockGlobal('scroll', scrollMock);
  mockGlobal('scrollTo', scrollMock);
  mockGlobal('scrollBy', scrollByMock);
  mockGlobal('resizeTo', resizeMock);
  Object.defineProperty(HTMLElement.prototype, 'scrollIntoView', { value: scrollIntoViewMock, writable: false });
  Object.defineProperty(HTMLElement.prototype, 'getBoundingClientRect', { value: getBoundingClientRectMock, writable: false });
  Object.defineProperty(HTMLElement.prototype, 'offsetWidth', { value: 250, writable: false });
  Object.defineProperty(HTMLElement.prototype, 'offsetHeight', { value: 250, writable: false });
  
});
The above ensures that, after a programmatic scroll takes place, the appropriate ScrollEvent will be published, and the window properties are updated accordingly.
2. Mocks that setup a basic layout for a collection of siblings
export function getPosition(element) {
  return element?.getClientRects()[0];
}
export function scrollToElement(element, [extraX = 0, extraY = 0]) {
  const { x, y } = getPosition(element);
  window.scrollTo(x + extraX, y + extraY);
}
export const layoutTypes = {
  column: 'column',
  row: 'row',
};
function* getLayoutBoxIterator(type, { defaultElementSize }) {
  const [width, height] = defaultElementSize;
  let offset = 0;
  while (true) {
    let left = 0;
    let top = 0;
    if (type === layoutTypes.column) {
      top += offset;
      offset += height;
    } else if (type === layoutTypes.row) {
      left += offset;
      offset += width;
    }
    yield new DOMRect(left, top, width, height);
  }
}
function getLayoutProps(element, layoutBox) {
  return {
    offsetX: layoutBox.x,
    offsetY: layoutBox.y,
    offsetWidth: layoutBox.width,
    offsetHeight: layoutBox.height,
    scrollWidth: layoutBox.width,
    scrollHeight: layoutBox.height,
  };
}
function defineReadonlyProperties(child, props) {
  let readonlyProps = Object.entries(props).reduce((accumulator, [key, value]) => {
    accumulator[key] = {
      value,
      writable: false,
    }; return accumulator;
  }, {});
  Object.defineProperties(child, readonlyProps);
}
export function mockLayout(parent, type, options = { defaultElementSize: [250, 250] }) {
  const layoutBoxIterator = getLayoutBoxIterator(type, options);
  const parentLayoutBox = new DOMRect(parent.offsetX, parent.offsetY, parent.offsetWidth, parent.offsetHeight);
  let maxBottom = 0;
  let maxRight = 0;
  Array.prototype.slice.call(parent.children).forEach((child) => {
    let layoutBox = layoutBoxIterator.next().value;
    // eslint-disable-next-line no-return-assign
    defineReadonlyProperties(child, getLayoutProps(child, layoutBox));
    maxBottom = Math.max(maxBottom, layoutBox.bottom);
    maxRight = Math.max(maxRight, layoutBox.right);
  });
  parentLayoutBox.width = Math.max(parentLayoutBox.width, maxRight);
  parentLayoutBox.height = Math.max(parentLayoutBox.height, maxBottom);
  defineReadonlyProperties(parent, getLayoutProps(parent, parentLayoutBox));
}
With those two in place, I would write my tests like this:
// given
mockLayout(/* put the common, direct parent of the siblings here */, layoutTypes.column);
// when
Simulate.click(document.querySelector('#nextStepButton')); // trigger the event that causes programmatic scroll
const scrolledElementPosition = ...; // get offsetX of the component that was scrolled programmatically
// then
expect(window.scrollX).toEqual(scrolledElementPosition.x); // verify that the programmatically scrolled element is now at the top of the page, or some other desired outcome
The idea here is that you give all siblings at a given level sensible, uniform widths and heights, as if they were rendered as a column / row, thus imposing a simple layout structure that the table component will 'see' when calculating which children to show / hide.
Note that in your scenario, the common parent of the sibling elements might not be the root HTML element rendered by table, but some element nested inside. Check the generated HTML to see how to best obtain a handle.
Your use case is a little different, in that you're triggering the event yourself, rather than having it bound to a specific action (a button click, for instance). Therefore, you might not need the first part in its entirety.