From 501c0d6445b9500fe185751ec37ca12c4453328a Mon Sep 17 00:00:00 2001 From: Tristan Watanabe Date: Thu, 24 Mar 2022 14:53:52 -0700 Subject: [PATCH 1/5] convert to testing library --- .../components/Breadcrumb/Breadcrumb.test.tsx | 111 +- .../__snapshots__/Breadcrumb.test.tsx.snap | 4341 ++++++++--------- 2 files changed, 2110 insertions(+), 2342 deletions(-) diff --git a/packages/react/src/components/Breadcrumb/Breadcrumb.test.tsx b/packages/react/src/components/Breadcrumb/Breadcrumb.test.tsx index a9e0bef43883b5..56f91058c3f7bd 100644 --- a/packages/react/src/components/Breadcrumb/Breadcrumb.test.tsx +++ b/packages/react/src/components/Breadcrumb/Breadcrumb.test.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import { mount, ReactWrapper } from 'enzyme'; -import * as renderer from 'react-test-renderer'; +import { render } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { Breadcrumb } from './index'; import { Icon } from '../../Icon'; import { isConformant } from '../../common/isConformant'; @@ -15,84 +15,54 @@ describe('Breadcrumb', () => { { text: 'TestText4', key: 'TestKey4' }, ]; - let component: renderer.ReactTestRenderer | undefined; - let wrapper: ReactWrapper | undefined; - beforeEach(() => { resetIds(); }); - afterEach(() => { - if (component) { - component.unmount(); - component = undefined; - } - if (wrapper) { - wrapper.unmount(); - wrapper = undefined; - } - }); - it('renders empty breadcrumb', () => { - component = renderer.create(); + const { container } = render(); - const tree = component.toJSON(); - - expect(tree).toMatchSnapshot(); + expect(container).toMatchSnapshot(); }); describe('rendering', () => { it('renders correctly', () => { - component = renderer.create(); - - const tree = component.toJSON(); - expect(tree).toMatchSnapshot(); + const { container } = render(); + expect(container).toMatchSnapshot(); }); it('renders correctly with overflow', () => { - component = renderer.create(); - - const tree = component.toJSON(); - expect(tree).toMatchSnapshot(); + const { container } = render(); + expect(container).toMatchSnapshot(); }); it('renders correctly with custom divider', () => { const divider = () => *; - component = renderer.create(); - - const tree = component.toJSON(); - expect(tree).toMatchSnapshot(); + const { container } = render(); + expect(container).toMatchSnapshot(); }); it('renders correctly with overflow and overflowIndex', () => { - component = renderer.create(); - - const tree = component.toJSON(); - expect(tree).toMatchSnapshot(); + const { container } = render(); + expect(container).toMatchSnapshot(); }); it('renders correctly with maxDisplayedItems and overflowIndex', () => { - component = renderer.create(); - - const tree = component.toJSON(); - expect(tree).toMatchSnapshot(); + const { container } = render(); + expect(container).toMatchSnapshot(); }); it('renders correctly with maxDisplayedItems and overflowIndex as 0', () => { - component = renderer.create(); - - const tree = component.toJSON(); - expect(tree).toMatchSnapshot(); + const { container } = render(); + expect(container).toMatchSnapshot(); }); it('renders correctly with custom overflow icon', () => { const overflowIcon = () => ; - component = renderer.create( + const { container } = render( , ); - - const tree = component.toJSON(); - expect(tree).toMatchSnapshot(); + expect(container).toMatchSnapshot(); }); }); @@ -111,18 +81,18 @@ describe('Breadcrumb', () => { { text: 'Test3', key: 'Test3', as: 'h1' }, ]; - wrapper = mount(); + const { container } = render(); // get the first child of each list item (the actual item content) - const renderedItems = wrapper.find('.ms-Breadcrumb-listItem > *:first-child'); + const renderedItems = container.querySelectorAll('.ms-Breadcrumb-listItem > *:first-child'); expect(renderedItems).toHaveLength(3); // should be a link since it has a href (even though it also has onclick) - expect(renderedItems.at(0).getDOMNode().tagName).toBe('A'); + expect(renderedItems[0].tagName).toBe('A'); // should be a button since it doesn't have a href // (can't use a link without a href because it won't respond to key events) - expect(renderedItems.at(1).getDOMNode().tagName).toBe('BUTTON'); + expect(renderedItems[1].tagName).toBe('BUTTON'); // specified type of h1 overrides default - expect(renderedItems.at(2).getDOMNode().tagName).toBe('H1'); + expect(renderedItems[2].tagName).toBe('H1'); }); it('calls the callback when an item is clicked', () => { @@ -137,35 +107,34 @@ describe('Breadcrumb', () => { { text: 'Test2', key: 'Test2', onClick: clickCallback }, ]; - wrapper = mount(); + const { container } = render(); - const renderedItems = wrapper.find('.ms-Breadcrumb-itemLink').hostNodes(); + const renderedItems = container.querySelectorAll('.ms-Breadcrumb-itemLink'); - renderedItems.at(0).simulate('click'); + userEvent.click(renderedItems[0]); expect(callbackValue).toEqual('Test1'); - renderedItems.at(1).simulate('click'); + userEvent.click(renderedItems[1]); expect(callbackValue).toEqual('Test2'); }); it('moves items to overflow in the correct order', () => { - wrapper = mount(); + const { container } = render(); - expect(wrapper.find('.ms-Breadcrumb-item').first().text()).toEqual('TestText3'); + expect(container.querySelectorAll('.ms-Breadcrumb-item')[0].textContent).toEqual('TestText3'); }); it('supports native props on the root element', () => { - wrapper = mount(); + const { getByRole } = render(); - expect(wrapper.find('.ms-Breadcrumb').prop('role')).toEqual('region'); + expect(getByRole('region', { hidden: true })).toBeTruthy(); }); it('opens the overflow menu on click', () => { - wrapper = mount(); + const { container } = render(); - const overflowButton = wrapper.find('.ms-Breadcrumb-overflowButton'); - // without hostNodes it returns the same element x4 - overflowButton.hostNodes().simulate('click'); + const overflowButton = container.querySelector('.ms-Breadcrumb-overflowButton'); + userEvent.click(overflowButton!); const overfowItems = document.querySelectorAll('.ms-ContextualMenu-item'); expect(overfowItems).toHaveLength(2); @@ -184,10 +153,10 @@ describe('Breadcrumb', () => { }, ]; - wrapper = mount(); + const { getByRole } = render(); - const item = wrapper.find('LinkBase'); - expect(item.prop('aria-label')).toEqual("I'm an aria prop"); + const item = getByRole('link', { hidden: true }); + expect(item.getAttribute('aria-label')).toEqual("I'm an aria prop"); }); it('for Tag', () => { @@ -199,10 +168,10 @@ describe('Breadcrumb', () => { }, ]; - wrapper = mount(); + const { container } = render(); - const item = wrapper.find('.ms-Breadcrumb-item'); - expect(item.prop('aria-label')).toEqual("I'm an aria prop"); + const item = container.querySelector('.ms-Breadcrumb-item'); + expect(item!.getAttribute('aria-label')).toEqual("I'm an aria prop"); }); }); }); diff --git a/packages/react/src/components/Breadcrumb/__snapshots__/Breadcrumb.test.tsx.snap b/packages/react/src/components/Breadcrumb/__snapshots__/Breadcrumb.test.tsx.snap index e18014c6e9c437..18434aa1170666 100644 --- a/packages/react/src/components/Breadcrumb/__snapshots__/Breadcrumb.test.tsx.snap +++ b/packages/react/src/components/Breadcrumb/__snapshots__/Breadcrumb.test.tsx.snap @@ -2,399 +2,373 @@ exports[`Breadcrumb rendering renders correctly with custom overflow icon 1`] = `
-
+
- - -  - - -
  • - - - - -  - -
  • -
  • - +
  • +
  • -
    - TestText4 -
    - -
  • - +
    + TestText4 +
    + + + +
    @@ -404,413 +378,383 @@ exports[`Breadcrumb rendering renders correctly with custom overflow icon 1`] = exports[`Breadcrumb rendering renders correctly 1`] = `
    -
    +
    + + + +
  • -
    - TestText2 -
    - - -  - -
  • -
  • - + TestText2 +
  • + + + +
  • -
    - TestText3 -
    - - -  - -
  • -
  • - + TestText3 +
  • + + + +
  • -
    - TestText4 -
    - -
  • - +
    + TestText4 +
    + + + +
    @@ -820,332 +764,302 @@ exports[`Breadcrumb rendering renders correctly 1`] = ` exports[`Breadcrumb rendering renders correctly with custom divider 1`] = `
    -
    +
    + + + * + + +
  • -
    - TestText2 -
    - - - * - -
  • -
  • - + TestText2 +
  • + + + * + + +
  • -
    - TestText3 -
    - - - * - -
  • -
  • - + TestText3 +
  • + + + * + + +
  • -
    - TestText4 -
    - -
  • - +
    + TestText4 +
    + + + +
    @@ -1155,324 +1069,299 @@ exports[`Breadcrumb rendering renders correctly with custom divider 1`] = ` exports[`Breadcrumb rendering renders correctly with maxDisplayedItems and overflowIndex 1`] = `
    -
    +
    + + + +
  • + -
  • - + + + + + +
    @@ -1482,230 +1371,210 @@ exports[`Breadcrumb rendering renders correctly with maxDisplayedItems and overf exports[`Breadcrumb rendering renders correctly with maxDisplayedItems and overflowIndex as 0 1`] = `
    -
    +
    @@ -1715,418 +1584,388 @@ exports[`Breadcrumb rendering renders correctly with maxDisplayedItems and overf exports[`Breadcrumb rendering renders correctly with overflow 1`] = `
    -
    +
    - - -  - - -
  • - - - - +
  • +
  • -  - -
  • -
  • - -
    - TestText4 -
    -
    -
  • - +
    + TestText4 +
    + + + +
    @@ -2136,418 +1975,388 @@ exports[`Breadcrumb rendering renders correctly with overflow 1`] = ` exports[`Breadcrumb rendering renders correctly with overflow and overflowIndex 1`] = `
    -
    +
    - - +  + + +
  • -  - -
  • -
  • -
  • +
  • -
    - TestText4 -
    - -
  • - +
    + TestText4 +
    + + + +
    @@ -2557,66 +2366,56 @@ exports[`Breadcrumb rendering renders correctly with overflow and overflowIndex exports[`Breadcrumb renders empty breadcrumb 1`] = `
    -
    +
    From 0c31e09a33c6558c5b0cf007972e941b5b00b751 Mon Sep 17 00:00:00 2001 From: Tristan Watanabe Date: Mon, 28 Mar 2022 16:40:35 -0700 Subject: [PATCH 2/5] remove usage of queryselector --- .../components/Breadcrumb/Breadcrumb.test.tsx | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/packages/react/src/components/Breadcrumb/Breadcrumb.test.tsx b/packages/react/src/components/Breadcrumb/Breadcrumb.test.tsx index 56f91058c3f7bd..90952cfa4f6dc5 100644 --- a/packages/react/src/components/Breadcrumb/Breadcrumb.test.tsx +++ b/packages/react/src/components/Breadcrumb/Breadcrumb.test.tsx @@ -81,18 +81,17 @@ describe('Breadcrumb', () => { { text: 'Test3', key: 'Test3', as: 'h1' }, ]; - const { container } = render(); + const { getAllByRole } = render(); - // get the first child of each list item (the actual item content) - const renderedItems = container.querySelectorAll('.ms-Breadcrumb-listItem > *:first-child'); + const renderedItems = getAllByRole('listitem', { hidden: true }); expect(renderedItems).toHaveLength(3); // should be a link since it has a href (even though it also has onclick) - expect(renderedItems[0].tagName).toBe('A'); + expect(renderedItems[0].firstElementChild!.tagName).toBe('A'); // should be a button since it doesn't have a href // (can't use a link without a href because it won't respond to key events) - expect(renderedItems[1].tagName).toBe('BUTTON'); + expect(renderedItems[1].firstElementChild!.tagName).toBe('BUTTON'); // specified type of h1 overrides default - expect(renderedItems[2].tagName).toBe('H1'); + expect(renderedItems[2].firstElementChild!.tagName).toBe('H1'); }); it('calls the callback when an item is clicked', () => { @@ -107,21 +106,19 @@ describe('Breadcrumb', () => { { text: 'Test2', key: 'Test2', onClick: clickCallback }, ]; - const { container } = render(); + const { getByRole } = render(); - const renderedItems = container.querySelectorAll('.ms-Breadcrumb-itemLink'); - - userEvent.click(renderedItems[0]); + userEvent.click(getByRole('link', { hidden: true })); expect(callbackValue).toEqual('Test1'); - userEvent.click(renderedItems[1]); + userEvent.click(getByRole('button', { hidden: true })); expect(callbackValue).toEqual('Test2'); }); it('moves items to overflow in the correct order', () => { - const { container } = render(); - - expect(container.querySelectorAll('.ms-Breadcrumb-item')[0].textContent).toEqual('TestText3'); + const { getAllByRole } = render(); + const firstListItem = getAllByRole('listitem', { hidden: true })[1].firstElementChild; + expect(firstListItem!.textContent).toEqual('TestText3'); }); it('supports native props on the root element', () => { @@ -131,12 +128,12 @@ describe('Breadcrumb', () => { }); it('opens the overflow menu on click', () => { - const { container } = render(); + const { getByRole, getAllByRole } = render(); - const overflowButton = container.querySelector('.ms-Breadcrumb-overflowButton'); + const overflowButton = getByRole('button', { hidden: true }); userEvent.click(overflowButton!); - const overfowItems = document.querySelectorAll('.ms-ContextualMenu-item'); + const overfowItems = getAllByRole('menuitem'); expect(overfowItems).toHaveLength(2); expect(overfowItems[0].textContent).toEqual('TestText1'); expect(overfowItems[1].textContent).toEqual('TestText2'); @@ -168,9 +165,9 @@ describe('Breadcrumb', () => { }, ]; - const { container } = render(); + const { getByRole } = render(); - const item = container.querySelector('.ms-Breadcrumb-item'); + const item = getByRole('listitem', { hidden: true }).firstElementChild; expect(item!.getAttribute('aria-label')).toEqual("I'm an aria prop"); }); }); From ca26a83daa6d8f489eae962be9ca816658a073f6 Mon Sep 17 00:00:00 2001 From: Tristan Watanabe Date: Tue, 29 Mar 2022 17:33:10 -0700 Subject: [PATCH 3/5] update breadcrumb snapshots --- .../__snapshots__/Breadcrumb.test.tsx.snap | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/packages/react/src/components/Breadcrumb/__snapshots__/Breadcrumb.test.tsx.snap b/packages/react/src/components/Breadcrumb/__snapshots__/Breadcrumb.test.tsx.snap index 18434aa1170666..5d4a20435b6376 100644 --- a/packages/react/src/components/Breadcrumb/__snapshots__/Breadcrumb.test.tsx.snap +++ b/packages/react/src/components/Breadcrumb/__snapshots__/Breadcrumb.test.tsx.snap @@ -275,6 +275,13 @@ exports[`Breadcrumb rendering renders correctly with custom overflow icon 1`] = role="none" > TestText3 +
    TestText4 +
    @@ -483,6 +497,13 @@ exports[`Breadcrumb rendering renders correctly 1`] = ` role="none" > TestText1 +
    TestText2 +
    TestText3 +
    TestText4 +
    @@ -869,6 +911,13 @@ exports[`Breadcrumb rendering renders correctly with custom divider 1`] = ` role="none" > TestText1 +
    @@ -931,6 +980,13 @@ exports[`Breadcrumb rendering renders correctly with custom divider 1`] = ` role="none" > TestText2 + @@ -993,6 +1049,13 @@ exports[`Breadcrumb rendering renders correctly with custom divider 1`] = ` role="none" > TestText3 + @@ -1055,6 +1118,13 @@ exports[`Breadcrumb rendering renders correctly with custom divider 1`] = ` role="none" > TestText4 + @@ -1174,6 +1244,13 @@ exports[`Breadcrumb rendering renders correctly with maxDisplayedItems and overf role="none" > TestText1 + TestText3 + TestText4 + @@ -2080,6 +2171,13 @@ exports[`Breadcrumb rendering renders correctly with overflow and overflowIndex role="none" > TestText1 + TestText4 + From 6429df7354fa507843b1394d93c24a413fffe578 Mon Sep 17 00:00:00 2001 From: Tristan Watanabe Date: Wed, 30 Mar 2022 12:35:31 -0700 Subject: [PATCH 4/5] use jest timers --- .../components/Breadcrumb/Breadcrumb.test.tsx | 58 ++++++++++++++++--- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/packages/react/src/components/Breadcrumb/Breadcrumb.test.tsx b/packages/react/src/components/Breadcrumb/Breadcrumb.test.tsx index 155b23565a7f89..51dad72207382a 100644 --- a/packages/react/src/components/Breadcrumb/Breadcrumb.test.tsx +++ b/packages/react/src/components/Breadcrumb/Breadcrumb.test.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { render } from '@testing-library/react'; +import { act, render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { Breadcrumb } from './index'; import { Icon } from '../../Icon'; @@ -19,6 +19,12 @@ describe('Breadcrumb', () => { resetIds(); }); + afterEach(() => { + if ((setTimeout as any).mock) { + jest.useRealTimers(); + } + }); + it('renders empty breadcrumb', () => { const { container } = render(); @@ -75,6 +81,8 @@ describe('Breadcrumb', () => { }); it('renders items with expected element type', () => { + jest.useFakeTimers(); + const items2: IBreadcrumbItem[] = [ { text: 'Test1', key: 'Test1', href: 'http://bing.com', onClick: () => undefined }, { text: 'Test2', key: 'Test2', onClick: () => undefined }, @@ -82,8 +90,11 @@ describe('Breadcrumb', () => { ]; const { getAllByRole } = render(); + act(() => { + jest.runAllTimers(); + }); - const renderedItems = getAllByRole('listitem', { hidden: true }); + const renderedItems = getAllByRole('listitem'); expect(renderedItems).toHaveLength(3); // should be a link since it has a href (even though it also has onclick) expect(renderedItems[0].firstElementChild!.tagName).toBe('A'); @@ -95,6 +106,8 @@ describe('Breadcrumb', () => { }); it('calls the callback when an item is clicked', () => { + jest.useFakeTimers(); + let callbackValue; const clickCallback = (ev: React.MouseEvent, item: IBreadcrumbItem) => { ev.preventDefault(); // in case it's a navigation event @@ -107,30 +120,49 @@ describe('Breadcrumb', () => { ]; const { getByRole } = render(); + act(() => { + jest.runAllTimers(); + }); - userEvent.click(getByRole('link', { hidden: true })); + userEvent.click(getByRole('link')); expect(callbackValue).toEqual('Test1'); - userEvent.click(getByRole('button', { hidden: true })); + userEvent.click(getByRole('button')); expect(callbackValue).toEqual('Test2'); }); it('moves items to overflow in the correct order', () => { + jest.useFakeTimers(); + const { getAllByRole } = render(); - const firstListItem = getAllByRole('listitem', { hidden: true })[1].firstElementChild; + act(() => { + jest.runAllTimers(); + }); + + const firstListItem = getAllByRole('listitem')[1].firstElementChild; expect(firstListItem!.textContent).toContain('TestText3'); }); it('supports native props on the root element', () => { + jest.useFakeTimers(); + const { getByRole } = render(); + act(() => { + jest.runAllTimers(); + }); - expect(getByRole('region', { hidden: true })).toBeTruthy(); + expect(getByRole('region')).toBeTruthy(); }); it('opens the overflow menu on click', () => { + jest.useFakeTimers(); + const { getByRole, getAllByRole } = render(); + act(() => { + jest.runAllTimers(); + }); - const overflowButton = getByRole('button', { hidden: true }); + const overflowButton = getByRole('button'); userEvent.click(overflowButton!); const overfowItems = getAllByRole('menuitem'); @@ -141,6 +173,8 @@ describe('Breadcrumb', () => { describe('ARIA prop propagation to breadcrumb items', () => { it('for Link', () => { + jest.useFakeTimers(); + const itemsWithAdditionalProps: IBreadcrumbItem[] = [ { key: 'ItemKey1', @@ -151,12 +185,17 @@ describe('Breadcrumb', () => { ]; const { getByRole } = render(); + act(() => { + jest.runAllTimers(); + }); - const item = getByRole('link', { hidden: true }); + const item = getByRole('link'); expect(item.getAttribute('aria-label')).toEqual("I'm an aria prop"); }); it('for Tag', () => { + jest.useFakeTimers(); + const itemsWithAdditionalProps: IBreadcrumbItem[] = [ { key: 'ItemKey1', @@ -166,6 +205,9 @@ describe('Breadcrumb', () => { ]; const { getByRole } = render(); + act(() => { + jest.runAllTimers(); + }); const item = getByRole('listitem', { hidden: true }).firstElementChild; expect(item!.getAttribute('aria-label')).toEqual("I'm an aria prop"); From 33166784a116c4dff77b95d052dde3db7111c9c2 Mon Sep 17 00:00:00 2001 From: Tristan Watanabe Date: Wed, 30 Mar 2022 12:39:22 -0700 Subject: [PATCH 5/5] Add comments on why using jest timers --- .../src/components/Breadcrumb/Breadcrumb.test.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/react/src/components/Breadcrumb/Breadcrumb.test.tsx b/packages/react/src/components/Breadcrumb/Breadcrumb.test.tsx index 51dad72207382a..de8c52dfc73fb0 100644 --- a/packages/react/src/components/Breadcrumb/Breadcrumb.test.tsx +++ b/packages/react/src/components/Breadcrumb/Breadcrumb.test.tsx @@ -90,6 +90,8 @@ describe('Breadcrumb', () => { ]; const { getAllByRole } = render(); + //Initial rendering of component is "hidden" while measurements are made + // therefore we need to wait a bit before getting roles. act(() => { jest.runAllTimers(); }); @@ -120,6 +122,8 @@ describe('Breadcrumb', () => { ]; const { getByRole } = render(); + //Initial rendering of component is "hidden" while measurements are made + // therefore we need to wait a bit before getting roles. act(() => { jest.runAllTimers(); }); @@ -135,6 +139,8 @@ describe('Breadcrumb', () => { jest.useFakeTimers(); const { getAllByRole } = render(); + //Initial rendering of component is "hidden" while measurements are made + // therefore we need to wait a bit before getting roles. act(() => { jest.runAllTimers(); }); @@ -147,6 +153,8 @@ describe('Breadcrumb', () => { jest.useFakeTimers(); const { getByRole } = render(); + //Initial rendering of component is "hidden" while measurements are made + // therefore we need to wait a bit before getting roles. act(() => { jest.runAllTimers(); }); @@ -158,6 +166,8 @@ describe('Breadcrumb', () => { jest.useFakeTimers(); const { getByRole, getAllByRole } = render(); + //Initial rendering of component is "hidden" while measurements are made + // therefore we need to wait a bit before getting roles. act(() => { jest.runAllTimers(); }); @@ -185,6 +195,8 @@ describe('Breadcrumb', () => { ]; const { getByRole } = render(); + //Initial rendering of component is "hidden" while measurements are made + // therefore we need to wait a bit before getting roles. act(() => { jest.runAllTimers(); }); @@ -205,6 +217,8 @@ describe('Breadcrumb', () => { ]; const { getByRole } = render(); + //Initial rendering of component is "hidden" while measurements are made + // therefore we need to wait a bit before getting roles. act(() => { jest.runAllTimers(); });