From 667ec166d736dfb0ac49f67398a8b7a13db7d5ef Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 6 Mar 2023 12:13:17 +0000 Subject: [PATCH] Remove Enzyme tests in favour of React testing-library (#10289) --- scripts/make-react-component.js | 14 +- src/components/views/dialogs/ExportDialog.tsx | 13 +- test/components/structures/RoomView-test.tsx | 18 +- .../structures/TimelinePanel-test.tsx | 10 +- .../views/context_menus/ContextMenu-test.tsx | 95 +- .../context_menus/MessageContextMenu-test.tsx | 174 +- .../views/dialogs/ExportDialog-test.tsx | 141 +- .../__snapshots__/ExportDialog-test.tsx.snap | 3047 +---------------- .../right_panel/PinnedMessagesCard-test.tsx | 79 +- test/test-utils/wrappers.tsx | 4 +- 10 files changed, 432 insertions(+), 3163 deletions(-) diff --git a/scripts/make-react-component.js b/scripts/make-react-component.js index 544a4f4f31..40eb331785 100755 --- a/scripts/make-react-component.js +++ b/scripts/make-react-component.js @@ -31,19 +31,19 @@ const %%ComponentName%%: React.FC = () => { export default %%ComponentName%%; `, TEST: ` -import React from 'react'; -import { mount } from 'enzyme'; +import React from "react"; +import { render } from "@testing-library/react"; import %%ComponentName%% from '%%RelativeComponentPath%%'; -describe('<%%ComponentName%% />', () => { +describe("<%%ComponentName%% />", () => { const defaultProps = {}; const getComponent = (props = {}) => - mount(<%%ComponentName%% {...defaultProps} {...props} />); + render(<%%ComponentName%% {...defaultProps} {...props} />); - it('renders', () => { - const component = getComponent(); - expect(component).toBeTruthy(); + it("matches snapshot", () => { + const { asFragment } = getComponent(); + expect(asFragment()).toMatchSnapshot()(); }); }); `, diff --git a/src/components/views/dialogs/ExportDialog.tsx b/src/components/views/dialogs/ExportDialog.tsx index e7b93c50f4..84afb0a4cc 100644 --- a/src/components/views/dialogs/ExportDialog.tsx +++ b/src/components/views/dialogs/ExportDialog.tsx @@ -110,11 +110,14 @@ const ExportDialog: React.FC = ({ room, onFinished }) => { const [displayCancel, setCancelWarning] = useState(false); const [exportCancelled, setExportCancelled] = useState(false); const [exportSuccessful, setExportSuccessful] = useState(false); - const [exporter, setExporter] = useStateCallback(null, async (exporter: Exporter): Promise => { - await exporter?.export().then(() => { - if (!exportCancelled) setExportSuccessful(true); - }); - }); + const [exporter, setExporter] = useStateCallback( + null, + async (exporter: Exporter | null): Promise => { + await exporter?.export().then(() => { + if (!exportCancelled) setExportSuccessful(true); + }); + }, + ); const startExport = async (): Promise => { const exportOptions = { diff --git a/test/components/structures/RoomView-test.tsx b/test/components/structures/RoomView-test.tsx index 6b1196ce1e..f7425072ef 100644 --- a/test/components/structures/RoomView-test.tsx +++ b/test/components/structures/RoomView-test.tsx @@ -14,16 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; -// eslint-disable-next-line deprecate/import -import { mount, ReactWrapper } from "enzyme"; +import React, { createRef, RefObject } from "react"; import { mocked, MockedObject } from "jest-mock"; import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/client"; import { Room, RoomEvent } from "matrix-js-sdk/src/models/room"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { EventType, RoomStateEvent } from "matrix-js-sdk/src/matrix"; import { MEGOLM_ALGORITHM } from "matrix-js-sdk/src/crypto/olmlib"; -import { fireEvent, render, screen } from "@testing-library/react"; +import { fireEvent, render, screen, RenderResult } from "@testing-library/react"; import { stubClient, @@ -98,7 +96,7 @@ describe("RoomView", () => { jest.restoreAllMocks(); }); - const mountRoomView = async (): Promise => { + const mountRoomView = async (ref?: RefObject<_RoomView>): Promise => { if (stores.roomViewStore.getRoomId() !== room.roomId) { const switchedRoom = new Promise((resolve) => { const subFn = () => { @@ -119,7 +117,7 @@ describe("RoomView", () => { await switchedRoom; } - const roomView = mount( + const roomView = render( { threepidInvite={undefined as any} resizeNotifier={new ResizeNotifier()} forceTimeline={false} + wrappedRef={ref as any} /> , ); @@ -170,8 +169,11 @@ describe("RoomView", () => { await flushPromises(); return roomView; }; - const getRoomViewInstance = async (): Promise<_RoomView> => - (await mountRoomView()).find(_RoomView).instance() as _RoomView; + const getRoomViewInstance = async (): Promise<_RoomView> => { + const ref = createRef<_RoomView>(); + await mountRoomView(ref); + return ref.current!; + }; it("when there is no room predecessor, getHiddenHighlightCount should return 0", async () => { const instance = await getRoomViewInstance(); diff --git a/test/components/structures/TimelinePanel-test.tsx b/test/components/structures/TimelinePanel-test.tsx index be56639714..1b8a0c4a9a 100644 --- a/test/components/structures/TimelinePanel-test.tsx +++ b/test/components/structures/TimelinePanel-test.tsx @@ -15,8 +15,6 @@ limitations under the License. */ import { render, waitFor, screen } from "@testing-library/react"; -// eslint-disable-next-line deprecate/import -import { mount, ReactWrapper } from "enzyme"; import { ReceiptType } from "matrix-js-sdk/src/@types/read_receipts"; import { EventTimelineSet, @@ -38,7 +36,7 @@ import { ThreadEvent, ThreadFilterType, } from "matrix-js-sdk/src/models/thread"; -import React from "react"; +import React, { createRef } from "react"; import TimelinePanel from "../../../src/components/structures/TimelinePanel"; import MatrixClientContext from "../../../src/contexts/MatrixClientContext"; @@ -137,15 +135,17 @@ describe("TimelinePanel", () => { // Create a TimelinePanel with ev0 already present const timelineSet = new EventTimelineSet(room, {}); timelineSet.addLiveEvent(ev0); - const component: ReactWrapper = mount( + const ref = createRef(); + render( , ); - const timelinePanel = component.instance() as TimelinePanel; + const timelinePanel = ref.current!; // An event arrived, and we read it timelineSet.addLiveEvent(ev1); diff --git a/test/components/views/context_menus/ContextMenu-test.tsx b/test/components/views/context_menus/ContextMenu-test.tsx index f97842ffc4..56c45d499b 100644 --- a/test/components/views/context_menus/ContextMenu-test.tsx +++ b/test/components/views/context_menus/ContextMenu-test.tsx @@ -15,8 +15,7 @@ limitations under the License. */ import React from "react"; -// eslint-disable-next-line deprecate/import -import { mount } from "enzyme"; +import { render } from "@testing-library/react"; import ContextMenu, { ChevronFace } from "../../../../src/components/structures/ContextMenu"; import UIStore from "../../../../src/stores/UIStore"; @@ -41,11 +40,11 @@ describe("", () => { const targetChevronOffset = 25; - describe("near top edge of window", () => { + it("near top edge of window", () => { const targetY = -50; const onFinished = jest.fn(); - const wrapper = mount( + render( ", () => { chevronOffset={targetChevronOffset} />, ); - const chevron = wrapper.find(".mx_ContextualMenu_chevron_left"); + const chevron = document.querySelector(".mx_ContextualMenu_chevron_left")!; - const bottomStyle = parseInt(wrapper.getDOMNode().style.getPropertyValue("bottom")); + const bottomStyle = parseInt( + document.querySelector(".mx_ContextualMenu_wrapper")!.style.getPropertyValue("bottom"), + ); const actualY = windowSize - bottomStyle - menuSize; - const actualChevronOffset = parseInt(chevron.getDOMNode().style.getPropertyValue("top")); + const actualChevronOffset = parseInt(chevron.style.getPropertyValue("top")); - it("stays within the window", () => { - expect(actualY).toBeGreaterThanOrEqual(0); - }); - it("positions the chevron correctly", () => { - expect(actualChevronOffset).toEqual(targetChevronOffset + targetY - actualY); - }); + // stays within the window + expect(actualY).toBeGreaterThanOrEqual(0); + // positions the chevron correctly + expect(actualChevronOffset).toEqual(targetChevronOffset + targetY - actualY); }); - describe("near right edge of window", () => { + it("near right edge of window", () => { const targetX = windowSize - menuSize + 50; const onFinished = jest.fn(); - const wrapper = mount( + render( ", () => { chevronOffset={targetChevronOffset} />, ); - const chevron = wrapper.find(".mx_ContextualMenu_chevron_top"); + const chevron = document.querySelector(".mx_ContextualMenu_chevron_top")!; - const actualX = parseInt(wrapper.getDOMNode().style.getPropertyValue("left")); - const actualChevronOffset = parseInt(chevron.getDOMNode().style.getPropertyValue("left")); + const actualX = parseInt( + document.querySelector(".mx_ContextualMenu_wrapper")!.style.getPropertyValue("left"), + ); + const actualChevronOffset = parseInt(chevron.style.getPropertyValue("left")); - it("stays within the window", () => { - expect(actualX + menuSize).toBeLessThanOrEqual(windowSize); - }); - it("positions the chevron correctly", () => { - expect(actualChevronOffset).toEqual(targetChevronOffset + targetX - actualX); - }); + // stays within the window + expect(actualX + menuSize).toBeLessThanOrEqual(windowSize); + // positions the chevron correctly + expect(actualChevronOffset).toEqual(targetChevronOffset + targetX - actualX); }); - describe("near bottom edge of window", () => { + it("near bottom edge of window", () => { const targetY = windowSize - menuSize + 50; const onFinished = jest.fn(); - const wrapper = mount( + render( ", () => { chevronOffset={targetChevronOffset} />, ); - const chevron = wrapper.find(".mx_ContextualMenu_chevron_right"); + const chevron = document.querySelector(".mx_ContextualMenu_chevron_right")!; - const actualY = parseInt(wrapper.getDOMNode().style.getPropertyValue("top")); - const actualChevronOffset = parseInt(chevron.getDOMNode().style.getPropertyValue("top")); + const actualY = parseInt( + document.querySelector(".mx_ContextualMenu_wrapper")!.style.getPropertyValue("top"), + ); + const actualChevronOffset = parseInt(chevron.style.getPropertyValue("top")); - it("stays within the window", () => { - expect(actualY + menuSize).toBeLessThanOrEqual(windowSize); - }); - it("positions the chevron correctly", () => { - expect(actualChevronOffset).toEqual(targetChevronOffset + targetY - actualY); - }); + // stays within the window + expect(actualY + menuSize).toBeLessThanOrEqual(windowSize); + // positions the chevron correctly + expect(actualChevronOffset).toEqual(targetChevronOffset + targetY - actualY); }); - describe("near left edge of window", () => { + it("near left edge of window", () => { const targetX = -50; const onFinished = jest.fn(); - const wrapper = mount( + render( ", () => { chevronOffset={targetChevronOffset} />, ); - const chevron = wrapper.find(".mx_ContextualMenu_chevron_bottom"); + const chevron = document.querySelector(".mx_ContextualMenu_chevron_bottom")!; - const rightStyle = parseInt(wrapper.getDOMNode().style.getPropertyValue("right")); + const rightStyle = parseInt( + document.querySelector(".mx_ContextualMenu_wrapper")!.style.getPropertyValue("right"), + ); const actualX = windowSize - rightStyle - menuSize; - const actualChevronOffset = parseInt(chevron.getDOMNode().style.getPropertyValue("left")); + const actualChevronOffset = parseInt(chevron.style.getPropertyValue("left")); - it("stays within the window", () => { - expect(actualX).toBeGreaterThanOrEqual(0); - }); - it("positions the chevron correctly", () => { - expect(actualChevronOffset).toEqual(targetChevronOffset + targetX - actualX); - }); + // stays within the window + expect(actualX).toBeGreaterThanOrEqual(0); + // positions the chevron correctly + expect(actualChevronOffset).toEqual(targetChevronOffset + targetX - actualX); }); it("should automatically close when a modal is opened", () => { const targetX = -50; const onFinished = jest.fn(); - mount( + render( ", () => { const onFinished = jest.fn(); Modal.createDialog(BaseDialog); - mount( + render( { const props = { link: "https://google.com/", }; - const menu = createMenuWithContent(eventContent, props); - const copyLinkButton = menu.find('a[aria-label="Copy link"]'); - expect(copyLinkButton).toHaveLength(1); - expect(copyLinkButton.props().href).toBe(props.link); + createMenuWithContent(eventContent, props); + const copyLinkButton = document.querySelector('a[aria-label="Copy link"]'); + expect(copyLinkButton).toHaveAttribute("href", props.link); }); it("does not show copy link button when not supplied a link", () => { const eventContent = createMessageEventContent("hello"); - const menu = createMenuWithContent(eventContent); - const copyLinkButton = menu.find('a[aria-label="Copy link"]'); - expect(copyLinkButton).toHaveLength(0); + createMenuWithContent(eventContent); + const copyLinkButton = document.querySelector('a[aria-label="Copy link"]'); + expect(copyLinkButton).toBeFalsy(); }); describe("message pinning", () => { @@ -100,9 +97,9 @@ describe("MessageContextMenu", () => { // mock permission to disallow adding pinned messages to room jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(false); - const menu = createMenu(event, {}, {}, undefined, room); + createMenu(event, {}, {}, undefined, room); - expect(menu.find('div[aria-label="Pin"]')).toHaveLength(0); + expect(document.querySelector('div[aria-label="Pin"]')).toBeFalsy(); }); it("does not show pin option for beacon_info event", () => { @@ -112,9 +109,9 @@ describe("MessageContextMenu", () => { // mock permission to allow adding pinned messages to room jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true); - const menu = createMenu(deadBeaconEvent, {}, {}, undefined, room); + createMenu(deadBeaconEvent, {}, {}, undefined, room); - expect(menu.find('div[aria-label="Pin"]')).toHaveLength(0); + expect(document.querySelector('div[aria-label="Pin"]')).toBeFalsy(); }); it("does not show pin option when pinning feature is disabled", () => { @@ -131,9 +128,9 @@ describe("MessageContextMenu", () => { // disable pinning feature jest.spyOn(SettingsStore, "getValue").mockReturnValue(false); - const menu = createMenu(pinnableEvent, {}, {}, undefined, room); + createMenu(pinnableEvent, {}, {}, undefined, room); - expect(menu.find('div[aria-label="Pin"]')).toHaveLength(0); + expect(document.querySelector('div[aria-label="Pin"]')).toBeFalsy(); }); it("shows pin option when pinning feature is enabled", () => { @@ -148,9 +145,9 @@ describe("MessageContextMenu", () => { // mock permission to allow adding pinned messages to room jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true); - const menu = createMenu(pinnableEvent, {}, {}, undefined, room); + createMenu(pinnableEvent, {}, {}, undefined, room); - expect(menu.find('div[aria-label="Pin"]')).toHaveLength(1); + expect(document.querySelector('div[aria-label="Pin"]')).toBeTruthy(); }); it("pins event on pin option click", () => { @@ -172,11 +169,9 @@ describe("MessageContextMenu", () => { const pinsAccountData = new MatrixEvent({ content: { event_ids: ["!1", "!2"] } }); jest.spyOn(room, "getAccountData").mockReturnValue(pinsAccountData); - const menu = createMenu(pinnableEvent, { onFinished }, {}, undefined, room); + createMenu(pinnableEvent, { onFinished }, {}, undefined, room); - act(() => { - menu.find('div[aria-label="Pin"]').simulate("click"); - }); + fireEvent.click(document.querySelector('div[aria-label="Pin"]')!); // added to account data expect(client.setRoomAccountData).toHaveBeenCalledWith(roomId, ReadPinsEventId, { @@ -228,11 +223,9 @@ describe("MessageContextMenu", () => { const pinsAccountData = new MatrixEvent({ content: { event_ids: ["!1", "!2"] } }); jest.spyOn(room, "getAccountData").mockReturnValue(pinsAccountData); - const menu = createMenu(pinnableEvent, {}, {}, undefined, room); + createMenu(pinnableEvent, {}, {}, undefined, room); - act(() => { - menu.find('div[aria-label="Unpin"]').simulate("click"); - }); + fireEvent.click(document.querySelector('div[aria-label="Unpin"]')!); expect(client.setRoomAccountData).not.toHaveBeenCalled(); @@ -250,14 +243,14 @@ describe("MessageContextMenu", () => { describe("message forwarding", () => { it("allows forwarding a room message", () => { const eventContent = createMessageEventContent("hello"); - const menu = createMenuWithContent(eventContent); - expect(menu.find('div[aria-label="Forward"]')).toHaveLength(1); + createMenuWithContent(eventContent); + expect(document.querySelector('div[aria-label="Forward"]')).toBeTruthy(); }); it("does not allow forwarding a poll", () => { const eventContent = PollStartEvent.from("why?", ["42"], M_POLL_KIND_DISCLOSED); - const menu = createMenuWithContent(eventContent); - expect(menu.find('div[aria-label="Forward"]')).toHaveLength(0); + createMenuWithContent(eventContent); + expect(document.querySelector('div[aria-label="Forward"]')).toBeFalsy(); }); it("should not allow forwarding a voice broadcast", () => { @@ -267,8 +260,8 @@ describe("MessageContextMenu", () => { "@user:example.com", "ABC123", ); - const menu = createMenu(broadcastStartEvent); - expect(menu.find('div[aria-label="Forward"]')).toHaveLength(0); + createMenu(broadcastStartEvent); + expect(document.querySelector('div[aria-label="Forward"]')).toBeFalsy(); }); describe("forwarding beacons", () => { @@ -279,8 +272,8 @@ describe("MessageContextMenu", () => { const beacon = new Beacon(deadBeaconEvent); const beacons = new Map(); beacons.set(getBeaconInfoIdentifier(deadBeaconEvent), beacon); - const menu = createMenu(deadBeaconEvent, {}, {}, beacons); - expect(menu.find('div[aria-label="Forward"]')).toHaveLength(0); + createMenu(deadBeaconEvent, {}, {}, beacons); + expect(document.querySelector('div[aria-label="Forward"]')).toBeFalsy(); }); it("does not allow forwarding a beacon that is not live but has a latestLocation", () => { @@ -294,8 +287,8 @@ describe("MessageContextMenu", () => { beacon._latestLocationEvent = beaconLocation; const beacons = new Map(); beacons.set(getBeaconInfoIdentifier(deadBeaconEvent), beacon); - const menu = createMenu(deadBeaconEvent, {}, {}, beacons); - expect(menu.find('div[aria-label="Forward"]')).toHaveLength(0); + createMenu(deadBeaconEvent, {}, {}, beacons); + expect(document.querySelector('div[aria-label="Forward"]')).toBeFalsy(); }); it("does not allow forwarding a live beacon that does not have a latestLocation", () => { @@ -304,8 +297,8 @@ describe("MessageContextMenu", () => { const beacon = new Beacon(beaconEvent); const beacons = new Map(); beacons.set(getBeaconInfoIdentifier(beaconEvent), beacon); - const menu = createMenu(beaconEvent, {}, {}, beacons); - expect(menu.find('div[aria-label="Forward"]')).toHaveLength(0); + createMenu(beaconEvent, {}, {}, beacons); + expect(document.querySelector('div[aria-label="Forward"]')).toBeFalsy(); }); it("allows forwarding a live beacon that has a location", () => { @@ -319,8 +312,8 @@ describe("MessageContextMenu", () => { beacon._latestLocationEvent = beaconLocation; const beacons = new Map(); beacons.set(getBeaconInfoIdentifier(liveBeaconEvent), beacon); - const menu = createMenu(liveBeaconEvent, {}, {}, beacons); - expect(menu.find('div[aria-label="Forward"]')).toHaveLength(1); + createMenu(liveBeaconEvent, {}, {}, beacons); + expect(document.querySelector('div[aria-label="Forward"]')).toBeTruthy(); }); it("opens forward dialog with correct event", () => { @@ -335,11 +328,9 @@ describe("MessageContextMenu", () => { beacon._latestLocationEvent = beaconLocation; const beacons = new Map(); beacons.set(getBeaconInfoIdentifier(liveBeaconEvent), beacon); - const menu = createMenu(liveBeaconEvent, {}, {}, beacons); + createMenu(liveBeaconEvent, {}, {}, beacons); - act(() => { - menu.find('div[aria-label="Forward"]').simulate("click"); - }); + fireEvent.click(document.querySelector('div[aria-label="Forward"]')!); // called with forwardableEvent, not beaconInfo event expect(dispatchSpy).toHaveBeenCalledWith( @@ -354,8 +345,8 @@ describe("MessageContextMenu", () => { describe("open as map link", () => { it("does not allow opening a plain message in open street maps", () => { const eventContent = createMessageEventContent("hello"); - const menu = createMenuWithContent(eventContent); - expect(menu.find('a[aria-label="Open in OpenStreetMap"]')).toHaveLength(0); + createMenuWithContent(eventContent); + expect(document.querySelector('a[aria-label="Open in OpenStreetMap"]')).toBeFalsy(); }); it("does not allow opening a beacon that does not have a shareable location event", () => { @@ -363,15 +354,16 @@ describe("MessageContextMenu", () => { const beacon = new Beacon(deadBeaconEvent); const beacons = new Map(); beacons.set(getBeaconInfoIdentifier(deadBeaconEvent), beacon); - const menu = createMenu(deadBeaconEvent, {}, {}, beacons); - expect(menu.find('a[aria-label="Open in OpenStreetMap"]')).toHaveLength(0); + createMenu(deadBeaconEvent, {}, {}, beacons); + expect(document.querySelector('a[aria-label="Open in OpenStreetMap"]')).toBeFalsy(); }); it("allows opening a location event in open street map", () => { const locationEvent = makeLocationEvent("geo:50,50"); - const menu = createMenu(locationEvent); + createMenu(locationEvent); // exists with a href with the lat/lon from the location event - expect(menu.find('a[aria-label="Open in OpenStreetMap"]').at(0).props().href).toEqual( + expect(document.querySelector('a[aria-label="Open in OpenStreetMap"]')).toHaveAttribute( + "href", "https://www.openstreetmap.org/?mlat=50&mlon=50#map=16/50/50", ); }); @@ -387,9 +379,10 @@ describe("MessageContextMenu", () => { beacon._latestLocationEvent = beaconLocation; const beacons = new Map(); beacons.set(getBeaconInfoIdentifier(liveBeaconEvent), beacon); - const menu = createMenu(liveBeaconEvent, {}, {}, beacons); + createMenu(liveBeaconEvent, {}, {}, beacons); // exists with a href with the lat/lon from the location event - expect(menu.find('a[aria-label="Open in OpenStreetMap"]').at(0).props().href).toEqual( + expect(document.querySelector('a[aria-label="Open in OpenStreetMap"]')).toHaveAttribute( + "href", "https://www.openstreetmap.org/?mlat=51&mlon=41#map=16/51/41", ); }); @@ -401,9 +394,9 @@ describe("MessageContextMenu", () => { const eventContent = createMessageEventContent(text); mocked(getSelectedText).mockReturnValue(text); - const menu = createRightClickMenuWithContent(eventContent); - const copyButton = menu.find('div[aria-label="Copy"]'); - copyButton.simulate("mousedown"); + createRightClickMenuWithContent(eventContent); + const copyButton = document.querySelector('div[aria-label="Copy"]')!; + fireEvent.mouseDown(copyButton); expect(copyPlaintext).toHaveBeenCalledWith(text); }); @@ -412,27 +405,27 @@ describe("MessageContextMenu", () => { const eventContent = createMessageEventContent(text); mocked(getSelectedText).mockReturnValue(""); - const menu = createRightClickMenuWithContent(eventContent); - const copyButton = menu.find('div[aria-label="Copy"]'); - expect(copyButton).toHaveLength(0); + createRightClickMenuWithContent(eventContent); + const copyButton = document.querySelector('div[aria-label="Copy"]'); + expect(copyButton).toBeFalsy(); }); it("shows edit button when we can edit", () => { const eventContent = createMessageEventContent("hello"); mocked(canEditContent).mockReturnValue(true); - const menu = createRightClickMenuWithContent(eventContent); - const editButton = menu.find('div[aria-label="Edit"]'); - expect(editButton).toHaveLength(1); + createRightClickMenuWithContent(eventContent); + const editButton = document.querySelector('div[aria-label="Edit"]'); + expect(editButton).toBeTruthy(); }); it("does not show edit button when we cannot edit", () => { const eventContent = createMessageEventContent("hello"); mocked(canEditContent).mockReturnValue(false); - const menu = createRightClickMenuWithContent(eventContent); - const editButton = menu.find('div[aria-label="Edit"]'); - expect(editButton).toHaveLength(0); + createRightClickMenuWithContent(eventContent); + const editButton = document.querySelector('div[aria-label="Edit"]'); + expect(editButton).toBeFalsy(); }); it("shows reply button when we can reply", () => { @@ -441,9 +434,9 @@ describe("MessageContextMenu", () => { canSendMessages: true, }; - const menu = createRightClickMenuWithContent(eventContent, context); - const replyButton = menu.find('div[aria-label="Reply"]'); - expect(replyButton).toHaveLength(1); + createRightClickMenuWithContent(eventContent, context); + const replyButton = document.querySelector('div[aria-label="Reply"]'); + expect(replyButton).toBeTruthy(); }); it("does not show reply button when we cannot reply", () => { @@ -455,9 +448,9 @@ describe("MessageContextMenu", () => { // queued messages are not actionable unsentMessage.setStatus(EventStatus.QUEUED); - const menu = createMenu(unsentMessage, {}, context); - const replyButton = menu.find('div[aria-label="Reply"]'); - expect(replyButton).toHaveLength(0); + createMenu(unsentMessage, {}, context); + const replyButton = document.querySelector('div[aria-label="Reply"]'); + expect(replyButton).toBeFalsy(); }); it("shows react button when we can react", () => { @@ -466,9 +459,9 @@ describe("MessageContextMenu", () => { canReact: true, }; - const menu = createRightClickMenuWithContent(eventContent, context); - const reactButton = menu.find('div[aria-label="React"]'); - expect(reactButton).toHaveLength(1); + createRightClickMenuWithContent(eventContent, context); + const reactButton = document.querySelector('div[aria-label="React"]'); + expect(reactButton).toBeTruthy(); }); it("does not show react button when we cannot react", () => { @@ -477,9 +470,9 @@ describe("MessageContextMenu", () => { canReact: false, }; - const menu = createRightClickMenuWithContent(eventContent, context); - const reactButton = menu.find('div[aria-label="React"]'); - expect(reactButton).toHaveLength(0); + createRightClickMenuWithContent(eventContent, context); + const reactButton = document.querySelector('div[aria-label="React"]'); + expect(reactButton).toBeFalsy(); }); it("shows view in room button when the event is a thread root", () => { @@ -493,17 +486,17 @@ describe("MessageContextMenu", () => { timelineRenderingType: TimelineRenderingType.Thread, }; - const menu = createMenu(mxEvent, props, context); - const reactButton = menu.find('div[aria-label="View in room"]'); - expect(reactButton).toHaveLength(1); + createMenu(mxEvent, props, context); + const reactButton = document.querySelector('div[aria-label="View in room"]'); + expect(reactButton).toBeTruthy(); }); it("does not show view in room button when the event is not a thread root", () => { const eventContent = createMessageEventContent("hello"); - const menu = createRightClickMenuWithContent(eventContent); - const reactButton = menu.find('div[aria-label="View in room"]'); - expect(reactButton).toHaveLength(0); + createRightClickMenuWithContent(eventContent); + const reactButton = document.querySelector('div[aria-label="View in room"]'); + expect(reactButton).toBeFalsy(); }); it("creates a new thread on reply in thread click", () => { @@ -516,11 +509,10 @@ describe("MessageContextMenu", () => { }; jest.spyOn(SettingsStore, "getValue").mockReturnValue(true); - const menu = createRightClickMenu(mxEvent, context); + createRightClickMenu(mxEvent, context); - const replyInThreadButton = menu.find('div[aria-label="Reply in thread"]'); - expect(replyInThreadButton).toHaveLength(1); - replyInThreadButton.simulate("click"); + const replyInThreadButton = document.querySelector('div[aria-label="Reply in thread"]')!; + fireEvent.click(replyInThreadButton); expect(dispatcher.dispatch).toHaveBeenCalledWith({ action: Action.ShowThread, @@ -531,11 +523,11 @@ describe("MessageContextMenu", () => { }); }); -function createRightClickMenuWithContent(eventContent: object, context?: Partial): ReactWrapper { +function createRightClickMenuWithContent(eventContent: object, context?: Partial): RenderResult { return createMenuWithContent(eventContent, { rightClick: true }, context); } -function createRightClickMenu(mxEvent: MatrixEvent, context?: Partial): ReactWrapper { +function createRightClickMenu(mxEvent: MatrixEvent, context?: Partial): RenderResult { return createMenu(mxEvent, { rightClick: true }, context); } @@ -543,7 +535,7 @@ function createMenuWithContent( eventContent: object, props?: Partial>, context?: Partial, -): ReactWrapper { +): RenderResult { // XXX: We probably shouldn't be assuming all events are going to be message events, but considering this // test is for the Message context menu, it's a fairly safe assumption. const mxEvent = new MatrixEvent({ type: EventType.RoomMessage, content: eventContent }); @@ -562,7 +554,7 @@ function createMenu( context: Partial = {}, beacons: Map = new Map(), room: Room = makeDefaultRoom(), -): ReactWrapper { +): RenderResult { const client = MatrixClientPeg.get(); // @ts-ignore illegally set private prop @@ -573,7 +565,7 @@ function createMenu( client.getUserId = jest.fn().mockReturnValue("@user:example.com"); client.getRoom = jest.fn().mockReturnValue(room); - return mount( + return render( , diff --git a/test/components/views/dialogs/ExportDialog-test.tsx b/test/components/views/dialogs/ExportDialog-test.tsx index 401dcf8770..7ac59d2428 100644 --- a/test/components/views/dialogs/ExportDialog-test.tsx +++ b/test/components/views/dialogs/ExportDialog-test.tsx @@ -15,11 +15,8 @@ limitations under the License. */ import React from "react"; -// eslint-disable-next-line deprecate/import -import { mount, ReactWrapper } from "enzyme"; +import { fireEvent, render, RenderResult, waitFor } from "@testing-library/react"; import { mocked } from "jest-mock"; -// eslint-disable-next-line deprecate/import -import { act } from "react-dom/test-utils"; import { Room } from "matrix-js-sdk/src/matrix"; import ExportDialog from "../../../../src/components/views/dialogs/ExportDialog"; @@ -59,49 +56,28 @@ describe("", () => { onFinished: jest.fn(), }; - const getComponent = (props = {}) => mount(); + const getComponent = (props = {}) => render(); - const getSizeInput = (component: ReactWrapper) => component.find('input[id="size-limit"]'); - const getExportTypeInput = (component: ReactWrapper) => component.find('select[id="export-type"]'); - const getAttachmentsCheckbox = (component: ReactWrapper) => component.find('input[id="include-attachments"]'); - const getMessageCountInput = (component: ReactWrapper) => component.find('input[id="message-count"]'); - const getExportFormatInput = (component: ReactWrapper, format: ExportFormat) => - component.find(`input[id="exportFormat-${format}"]`); - const getPrimaryButton = (component: ReactWrapper) => component.find('[data-testid="dialog-primary-button"]'); - const getSecondaryButton = (component: ReactWrapper) => component.find('[data-testid="dialog-cancel-button"]'); + const getSizeInput = ({ container }: RenderResult) => container.querySelector('input[id="size-limit"]')!; + const getExportTypeInput = ({ container }: RenderResult) => container.querySelector('select[id="export-type"]')!; + const getAttachmentsCheckbox = ({ container }: RenderResult) => + container.querySelector('input[id="include-attachments"]')!; + const getMessageCountInput = ({ container }: RenderResult) => container.querySelector('input[id="message-count"]')!; + const getExportFormatInput = ({ container }: RenderResult, format: ExportFormat) => + container.querySelector(`input[id="exportFormat-${format}"]`)!; + const getPrimaryButton = ({ getByTestId }: RenderResult) => getByTestId("dialog-primary-button")!; + const getSecondaryButton = ({ getByTestId }: RenderResult) => getByTestId("dialog-cancel-button")!; - const submitForm = async (component: ReactWrapper) => - act(async () => { - getPrimaryButton(component).simulate("click"); - component.setProps({}); - }); - const selectExportFormat = async (component: ReactWrapper, format: ExportFormat) => - act(async () => { - getExportFormatInput(component, format).simulate("change"); - component.setProps({}); - }); - const selectExportType = async (component: ReactWrapper, type: ExportType) => - act(async () => { - getExportTypeInput(component).simulate("change", { target: { value: type } }); - component.setProps({}); - }); - const setMessageCount = async (component: ReactWrapper, count: number) => - act(async () => { - getMessageCountInput(component).simulate("change", { target: { value: count } }); - component.setProps({}); - }); + const submitForm = async (component: RenderResult) => fireEvent.click(getPrimaryButton(component)); + const selectExportFormat = async (component: RenderResult, format: ExportFormat) => + fireEvent.click(getExportFormatInput(component, format)); + const selectExportType = async (component: RenderResult, type: ExportType) => + fireEvent.change(getExportTypeInput(component), { target: { value: type } }); + const setMessageCount = async (component: RenderResult, count: number) => + fireEvent.change(getMessageCountInput(component), { target: { value: count } }); - const setSizeLimit = async (component: ReactWrapper, limit: number) => - act(async () => { - getSizeInput(component).simulate("change", { target: { value: limit } }); - component.setProps({}); - }); - - const setIncludeAttachments = async (component: ReactWrapper, checked: boolean) => - act(async () => { - getAttachmentsCheckbox(component).simulate("change", { target: { checked } }); - component.setProps({}); - }); + const setSizeLimit = async (component: RenderResult, limit: number) => + fireEvent.change(getSizeInput(component), { target: { value: limit } }); beforeEach(() => { HTMLExporterMock.mockClear().mockImplementation(jest.fn().mockReturnValue(htmlExporterInstance)); @@ -115,15 +91,13 @@ describe("", () => { it("renders export dialog", () => { const component = getComponent(); - expect(component.find(".mx_ExportDialog")).toMatchSnapshot(); + expect(component.container.querySelector(".mx_ExportDialog")).toMatchSnapshot(); }); it("calls onFinished when cancel button is clicked", () => { const onFinished = jest.fn(); const component = getComponent({ onFinished }); - act(() => { - getSecondaryButton(component).simulate("click"); - }); + fireEvent.click(getSecondaryButton(component)); expect(onFinished).toHaveBeenCalledWith(false); }); @@ -131,17 +105,19 @@ describe("", () => { const component = getComponent(); await submitForm(component); - // 4th arg is an component function - const exportConstructorProps = HTMLExporterMock.mock.calls[0].slice(0, 3); - expect(exportConstructorProps).toEqual([ - defaultProps.room, - ExportType.Timeline, - { - attachmentsIncluded: false, - maxSize: 8388608, // 8MB to bytes - numberOfMessages: 100, - }, - ]); + await waitFor(() => { + // 4th arg is an component function + const exportConstructorProps = HTMLExporterMock.mock.calls[0].slice(0, 3); + expect(exportConstructorProps).toEqual([ + defaultProps.room, + ExportType.Timeline, + { + attachmentsIncluded: false, + maxSize: 8388608, // 8MB to bytes + numberOfMessages: 100, + }, + ]); + }); expect(htmlExporterInstance.export).toHaveBeenCalled(); }); @@ -173,29 +149,28 @@ describe("", () => { it("renders success screen when export is finished", async () => { const component = getComponent(); await submitForm(component); - component.setProps({}); jest.runAllTimers(); - expect(component.find(".mx_InfoDialog .mx_Dialog_content")).toMatchSnapshot(); + expect(component.container.querySelector(".mx_InfoDialog .mx_Dialog_content")).toMatchSnapshot(); }); describe("export format", () => { it("renders export format with html selected by default", () => { const component = getComponent(); - expect(getExportFormatInput(component, ExportFormat.Html).props().checked).toBeTruthy(); + expect(getExportFormatInput(component, ExportFormat.Html)).toBeChecked(); }); it("sets export format on radio button click", async () => { const component = getComponent(); await selectExportFormat(component, ExportFormat.PlainText); - expect(getExportFormatInput(component, ExportFormat.PlainText).props().checked).toBeTruthy(); - expect(getExportFormatInput(component, ExportFormat.Html).props().checked).toBeFalsy(); + expect(getExportFormatInput(component, ExportFormat.PlainText)).toBeChecked(); + expect(getExportFormatInput(component, ExportFormat.Html)).not.toBeChecked(); }); it("hides export format input when format is valid in ForceRoomExportParameters", () => { const component = getComponent(); - expect(getExportFormatInput(component, ExportFormat.Html).props().checked).toBeTruthy(); + expect(getExportFormatInput(component, ExportFormat.Html)).toBeChecked(); }); it("does not render export format when set in ForceRoomExportParameters", () => { @@ -203,20 +178,20 @@ describe("", () => { format: ExportFormat.PlainText, }); const component = getComponent(); - expect(getExportFormatInput(component, ExportFormat.Html).length).toBeFalsy(); + expect(getExportFormatInput(component, ExportFormat.Html)).toBeFalsy(); }); }); describe("export type", () => { it("renders export type with timeline selected by default", () => { const component = getComponent(); - expect(getExportTypeInput(component).props().value).toEqual(ExportType.Timeline); + expect(getExportTypeInput(component)).toHaveValue(ExportType.Timeline); }); it("sets export type on change", async () => { const component = getComponent(); await selectExportType(component, ExportType.Beginning); - expect(getExportTypeInput(component).props().value).toEqual(ExportType.Beginning); + expect(getExportTypeInput(component)).toHaveValue(ExportType.Beginning); }); it("does not render export type when set in ForceRoomExportParameters", () => { @@ -224,25 +199,25 @@ describe("", () => { range: ExportType.Beginning, }); const component = getComponent(); - expect(getExportTypeInput(component).length).toBeFalsy(); + expect(getExportTypeInput(component)).toBeFalsy(); }); it("does not render message count input", async () => { const component = getComponent(); - expect(getMessageCountInput(component).length).toBeFalsy(); + expect(getMessageCountInput(component)).toBeFalsy(); }); it("renders message count input with default value 100 when export type is lastNMessages", async () => { const component = getComponent(); await selectExportType(component, ExportType.LastNMessages); - expect(getMessageCountInput(component).props().value).toEqual("100"); + expect(getMessageCountInput(component)).toHaveValue(100); }); it("sets message count on change", async () => { const component = getComponent(); await selectExportType(component, ExportType.LastNMessages); await setMessageCount(component, 10); - expect(getMessageCountInput(component).props().value).toEqual("10"); + expect(getMessageCountInput(component)).toHaveValue(10); }); it("does not export when export type is lastNMessages and message count is falsy", async () => { @@ -270,20 +245,22 @@ describe("", () => { await selectExportType(component, ExportType.Timeline); await submitForm(component); - expect(htmlExporterInstance.export).toHaveBeenCalled(); + await waitFor(() => { + expect(htmlExporterInstance.export).toHaveBeenCalled(); + }); }); }); describe("size limit", () => { it("renders size limit input with default value", () => { const component = getComponent(); - expect(getSizeInput(component).props().value).toEqual("8"); + expect(getSizeInput(component)).toHaveValue(8); }); it("updates size limit on change", async () => { const component = getComponent(); await setSizeLimit(component, 20); - expect(getSizeInput(component).props().value).toEqual("20"); + expect(getSizeInput(component)).toHaveValue(20); }); it("does not export when size limit is falsy", async () => { @@ -307,7 +284,9 @@ describe("", () => { await setSizeLimit(component, 2000); await submitForm(component); - expect(htmlExporterInstance.export).toHaveBeenCalled(); + await waitFor(() => { + expect(htmlExporterInstance.export).toHaveBeenCalled(); + }); }); it("does not render size limit input when set in ForceRoomExportParameters", () => { @@ -315,7 +294,7 @@ describe("", () => { sizeMb: 10000, }); const component = getComponent(); - expect(getSizeInput(component).length).toBeFalsy(); + expect(getSizeInput(component)).toBeFalsy(); }); /** @@ -335,13 +314,13 @@ describe("", () => { describe("include attachments", () => { it("renders input with default value of false", () => { const component = getComponent(); - expect(getAttachmentsCheckbox(component).props().checked).toEqual(false); + expect(getAttachmentsCheckbox(component)).not.toBeChecked(); }); it("updates include attachments on change", async () => { const component = getComponent(); - await setIncludeAttachments(component, true); - expect(getAttachmentsCheckbox(component).props().checked).toEqual(true); + fireEvent.click(getAttachmentsCheckbox(component)); + expect(getAttachmentsCheckbox(component)).toBeChecked(); }); it("does not render input when set in ForceRoomExportParameters", () => { @@ -349,7 +328,7 @@ describe("", () => { includeAttachments: false, }); const component = getComponent(); - expect(getAttachmentsCheckbox(component).length).toBeFalsy(); + expect(getAttachmentsCheckbox(component)).toBeFalsy(); }); }); }); diff --git a/test/components/views/dialogs/__snapshots__/ExportDialog-test.tsx.snap b/test/components/views/dialogs/__snapshots__/ExportDialog-test.tsx.snap index 48502f719b..722baf1cbd 100644 --- a/test/components/views/dialogs/__snapshots__/ExportDialog-test.tsx.snap +++ b/test/components/views/dialogs/__snapshots__/ExportDialog-test.tsx.snap @@ -1,2905 +1,206 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[` renders export dialog 1`] = ` -[ - +
- - - -
- -
-

- Export Chat -

-
-
-

- Select from the options below to export chats from your timeline -

-
- - Format - -