reactjs 在具有React测试库的React窗口列表中进行测试滚动

68de4m5k  于 2023-03-29  发布在  React
关注(0)|答案(2)|浏览(157)

我在这里遇到了一个测试,我想验证在滚动通过一个列表组件后,由react-window导入,不同的项目正在呈现。列表位于一个表组件中,该组件在React上下文中保存滚动位置,这就是为什么我需要测试整个表组件。
不幸的是,滚动事件似乎没有任何效果,列表仍然显示相同的项目。
测试看起来像这样:

render(
  <SomeProvider>
    <Table />
  </SomeProvider>
)

describe('Table', () => {
  it('scrolls and renders different items', () => {
    const table = screen.getByTestId('table')

    expect(table.textContent?.includes('item_A')).toBeTruthy() // --> true
    expect(table.textContent?.includes('item_Z')).toBeFalsy() // --> true

    // getting the list which is a child component of table
    const list = table.children[0]

    fireEvent.scroll(list, {target: {scrollY: 100}})

    expect(table.textContent?.includes('item_A')).toBeFalsy() // --> false
    expect(table.textContent?.includes('item_Z')).toBeTruthy() // --> false
  })
})

任何帮助都将不胜感激。

vybvopom

vybvopom1#

我有一个场景,其中某些演示方面取决于滚动位置。为了使测试更清晰,我在测试设置中定义了以下模拟:

1.保证程序化滚动触发相应事件的模拟:

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

以上确保了在进行编程滚动后,将发布相应的ScrollEvent,并相应地更新window属性。

2.为同级集合设置基本布局的Mock

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

有了这两个,我会像这样写我的测试:

// 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

这里的想法是,在给定级别上为所有兄弟节点提供合理的、统一的宽度和高度,就好像它们被呈现为列/行一样,从而强加一个简单的布局结构,table组件在计算显示/隐藏哪些子节点时将“看到”。
请注意,在您的场景中,兄弟元素的公共父元素可能不是table呈现的根HTML元素,而是嵌套在其中的某个元素。检查生成的HTML以了解如何最好地获取句柄。
您的用例有点不同,因为您自己触发事件,而不是将其绑定到特定的操作(例如,按钮单击)。因此,您可能不需要完整的第一部分。

lzfw57am

lzfw57am2#

默认情况下,react-testing-library在jsdom环境中呈现组件,而不是在浏览器中。基本上,它只生成html标记,但不知道组件的位置,它们的滚动偏移量等。
例如,参见此问题。
可能的解决方案包括:

  • 使用Cypress
  • 或者覆盖react-window用来度量容器中的滚动偏移量的任何原生属性(hacky)。例如,让我们假设react-window使用container.scrollHeight:
// artificially setting container scroll height to 200
Object.defineProperty(containerRef, 'scrollHeight', { value: 200 })

相关问题