diff --git a/packages/ui-voip/package.json b/packages/ui-voip/package.json index 3bf8cc37151a..f1e79d3352ed 100644 --- a/packages/ui-voip/package.json +++ b/packages/ui-voip/package.json @@ -26,6 +26,7 @@ "devDependencies": { "@babel/core": "~7.25.8", "@faker-js/faker": "~8.0.2", + "@react-spectrum/test-utils": "~1.0.0-alpha.2", "@rocket.chat/css-in-js": "~0.31.25", "@rocket.chat/eslint-config": "workspace:^", "@rocket.chat/fuselage": "^0.59.3", @@ -49,6 +50,7 @@ "@types/jest": "~29.5.12", "@types/jest-axe": "~3.5.9", "@types/react": "~17.0.80", + "@types/react-dom": "~17.0.25", "eslint": "~8.45.0", "eslint-plugin-react": "~7.32.2", "eslint-plugin-react-hooks": "~4.6.2", diff --git a/packages/ui-voip/src/components/VoipContactId/VoipContactId.spec.tsx b/packages/ui-voip/src/components/VoipContactId/VoipContactId.spec.tsx index 595c6897eeec..43d55c2c125f 100644 --- a/packages/ui-voip/src/components/VoipContactId/VoipContactId.spec.tsx +++ b/packages/ui-voip/src/components/VoipContactId/VoipContactId.spec.tsx @@ -2,6 +2,7 @@ import { faker } from '@faker-js/faker'; import { mockAppRoot } from '@rocket.chat/mock-providers'; import { composeStories } from '@storybook/react'; import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { axe } from 'jest-axe'; import VoipContactId from './VoipContactId'; @@ -9,57 +10,55 @@ import * as stories from './VoipContactId.stories'; const testCases = Object.values(composeStories(stories)).map((story) => [story.storyName || 'Story', story]); -describe('VoipContactId', () => { - beforeAll(() => { - Object.assign(navigator, { - clipboard: { - writeText: jest.fn(), - }, - }); +beforeAll(() => { + Object.assign(navigator, { + clipboard: { + writeText: jest.fn(), + }, }); +}); - test.each(testCases)(`renders %s without crashing`, async (_storyname, Story) => { - const tree = render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); - expect(tree.baseElement).toMatchSnapshot(); - }); +test.each(testCases)(`renders %s without crashing`, async (_storyname, Story) => { + const tree = render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); + expect(tree.baseElement).toMatchSnapshot(); +}); - test.each(testCases)('%s should have no a11y violations', async (_storyname, Story) => { - const { container } = render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); +test.each(testCases)('%s should have no a11y violations', async (_storyname, Story) => { + const { container } = render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); + + const results = await axe(container); + expect(results).toHaveNoViolations(); +}); - const results = await axe(container); - expect(results).toHaveNoViolations(); +it('should display avatar and name when username is available', () => { + render(, { + wrapper: mockAppRoot().build(), + legacyRoot: true, }); - it('should display avatar and name when username is available', () => { - render(, { - wrapper: mockAppRoot().build(), - legacyRoot: true, - }); + expect(screen.getByRole('img', { hidden: true })).toHaveAttribute('title', 'john.doe'); + expect(screen.getByText('John Doe')).toBeInTheDocument(); +}); - expect(screen.getByRole('img', { hidden: true })).toHaveAttribute('title', 'john.doe'); - expect(screen.getByText('John Doe')).toBeInTheDocument(); +it('should display transferedBy information when available', () => { + render(, { + wrapper: mockAppRoot().build(), + legacyRoot: true, }); - it('should display transferedBy information when available', () => { - render(, { - wrapper: mockAppRoot().build(), - legacyRoot: true, - }); + expect(screen.getByText('From: Jane Doe')).toBeInTheDocument(); +}); - expect(screen.getByText('From: Jane Doe')).toBeInTheDocument(); +it('should display copy button when username isnt available', async () => { + const phone = faker.phone.number(); + render(, { + wrapper: mockAppRoot().build(), + legacyRoot: true, }); - it('should display copy button when username isnt available', async () => { - const phone = faker.phone.number(); - render(, { - wrapper: mockAppRoot().build(), - legacyRoot: true, - }); + const copyButton = screen.getByRole('button', { name: 'Copy_phone_number' }); + expect(copyButton).toBeInTheDocument(); - const copyButton = screen.getByRole('button', { name: 'Copy_phone_number' }); - expect(copyButton).toBeInTheDocument(); - - copyButton.click(); - await waitFor(() => expect(navigator.clipboard.writeText).toHaveBeenCalledWith(phone)); - }); + await userEvent.click(copyButton); + await waitFor(() => expect(navigator.clipboard.writeText).toHaveBeenCalledWith(phone)); }); diff --git a/packages/ui-voip/src/components/VoipContactId/__snapshots__/VoipContactId.spec.tsx.snap b/packages/ui-voip/src/components/VoipContactId/__snapshots__/VoipContactId.spec.tsx.snap index e8d94cbfdde4..46980fbdccaf 100644 --- a/packages/ui-voip/src/components/VoipContactId/__snapshots__/VoipContactId.spec.tsx.snap +++ b/packages/ui-voip/src/components/VoipContactId/__snapshots__/VoipContactId.spec.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`VoipContactId renders Loading without crashing 1`] = ` +exports[`renders Loading without crashing 1`] = `
`; -exports[`VoipContactId renders WithPhoneNumber without crashing 1`] = ` +exports[`renders WithPhoneNumber without crashing 1`] = `
`; -exports[`VoipContactId renders WithTransfer without crashing 1`] = ` +exports[`renders WithTransfer without crashing 1`] = `
`; -exports[`VoipContactId renders WithUsername without crashing 1`] = ` +exports[`renders WithUsername without crashing 1`] = `
{ - beforeEach(() => { - jest.useFakeTimers(); - }); +const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); - afterEach(() => { - jest.clearAllTimers(); - }); +installPointerEvent(); - it('should not be editable by default', () => { - render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); +beforeEach(() => { + jest.useFakeTimers(); +}); - expect(screen.getByLabelText('Phone_number')).toHaveAttribute('readOnly'); - }); +afterEach(() => { + jest.clearAllTimers(); +}); - it('should enable input when editable', () => { - render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); +it('should not be editable by default', async () => { + render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); - expect(screen.getByLabelText('Phone_number')).not.toHaveAttribute('readOnly'); - }); + expect(screen.getByLabelText('Phone_number')).toHaveAttribute('readOnly'); +}); - it('should disable backspace button when input is empty', () => { - render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); +it('should enable input when editable', async () => { + render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); - expect(screen.getByTestId('dial-paid-input-backspace')).toBeDisabled(); - }); + expect(screen.getByLabelText('Phone_number')).not.toHaveAttribute('readOnly'); +}); - it('should enable backspace button when input has value', () => { - render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); +it('should disable backspace button when input is empty', async () => { + render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); - expect(screen.getByTestId('dial-paid-input-backspace')).toBeEnabled(); - }); + expect(screen.getByTestId('dial-paid-input-backspace')).toBeDisabled(); +}); - it('should remove last character when backspace is clicked', () => { - const fn = jest.fn(); - render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); +it('should enable backspace button when input has value', async () => { + render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); - expect(screen.getByLabelText('Phone_number')).toHaveValue('123'); + expect(screen.getByTestId('dial-paid-input-backspace')).toBeEnabled(); +}); - screen.getByTestId('dial-paid-input-backspace').click(); +it('should remove last character when backspace is clicked', async () => { + const fn = jest.fn(); + render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); - expect(fn).toHaveBeenCalledWith('12'); - }); + expect(screen.getByLabelText('Phone_number')).toHaveValue('123'); - it('should call onChange when number is clicked', () => { - const fn = jest.fn(); - render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); + await user.click(screen.getByTestId('dial-paid-input-backspace')); + + expect(fn).toHaveBeenCalledWith('12'); +}); + +it('should call onChange when number is clicked', async () => { + const fn = jest.fn(); + render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); + + for (const digit of ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']) { + await user.click(screen.getByTestId(`dial-pad-button-${digit}`)); + expect(fn).toHaveBeenCalledWith(`123${digit}`, digit); + } +}); - ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'].forEach((digit) => { - screen.getByTestId(`dial-pad-button-${digit}`).click(); - expect(fn).toHaveBeenCalledWith(`123${digit}`, digit); - }); - }); +it('should call onChange with + when 0 pressed and held', async () => { + const fn = jest.fn(); + render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); - xit('should call onChange with + when 0 pressed and held', () => { - const fn = jest.fn(); - render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); + const button = screen.getByTestId('dial-pad-button-0'); - const button = screen.getByTestId('dial-pad-button-0'); + await user.click(button); - button.click(); - expect(fn).toHaveBeenCalledWith('1230', '0'); + expect(fn).toHaveBeenCalledWith('1230', '0'); - fireEvent.pointerDown(button); - jest.runOnlyPendingTimers(); - fireEvent.pointerUp(button); + await triggerLongPress({ element: button, advanceTimer: (time = 800) => jest.advanceTimersByTime(time) }); - expect(fn).toHaveBeenCalledWith('123+', '+'); - }); + expect(fn).toHaveBeenCalledWith('123+', '+'); }); diff --git a/packages/ui-voip/src/components/VoipPopup/VoipPopup.spec.tsx b/packages/ui-voip/src/components/VoipPopup/VoipPopup.spec.tsx index 6ebf1e6fb038..eac2e495c6d9 100644 --- a/packages/ui-voip/src/components/VoipPopup/VoipPopup.spec.tsx +++ b/packages/ui-voip/src/components/VoipPopup/VoipPopup.spec.tsx @@ -18,11 +18,10 @@ jest.mock('../../hooks/useVoipDialer', () => ({ })); const mockedUseVoipSession = jest.mocked(useVoipSession); -const appRoot = mockAppRoot(); it('should properly render incoming popup', async () => { mockedUseVoipSession.mockImplementationOnce(() => createMockVoipSession({ type: 'INCOMING' })); - render(, { wrapper: appRoot.build(), legacyRoot: true }); + render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); expect(screen.getByTestId('vc-popup-incoming')).toBeInTheDocument(); }); @@ -30,7 +29,7 @@ it('should properly render incoming popup', async () => { it('should properly render ongoing popup', async () => { mockedUseVoipSession.mockImplementationOnce(() => createMockVoipSession({ type: 'ONGOING' })); - render(, { wrapper: appRoot.build(), legacyRoot: true }); + render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); expect(screen.getByTestId('vc-popup-ongoing')).toBeInTheDocument(); }); @@ -38,7 +37,7 @@ it('should properly render ongoing popup', async () => { it('should properly render outgoing popup', async () => { mockedUseVoipSession.mockImplementationOnce(() => createMockVoipSession({ type: 'OUTGOING' })); - render(, { wrapper: appRoot.build(), legacyRoot: true }); + render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); expect(screen.getByTestId('vc-popup-outgoing')).toBeInTheDocument(); }); @@ -46,13 +45,13 @@ it('should properly render outgoing popup', async () => { it('should properly render error popup', async () => { mockedUseVoipSession.mockImplementationOnce(() => createMockVoipSession({ type: 'ERROR' })); - render(, { wrapper: appRoot.build(), legacyRoot: true }); + render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); expect(screen.getByTestId('vc-popup-error')).toBeInTheDocument(); }); it('should properly render dialer popup', async () => { - render(, { wrapper: appRoot.build(), legacyRoot: true }); + render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); expect(screen.getByTestId('vc-popup-dialer')).toBeInTheDocument(); }); @@ -60,7 +59,7 @@ it('should properly render dialer popup', async () => { it('should prioritize session over dialer', async () => { mockedUseVoipSession.mockImplementationOnce(() => createMockVoipSession({ type: 'INCOMING' })); - render(, { wrapper: appRoot.build(), legacyRoot: true }); + render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); expect(screen.queryByTestId('vc-popup-dialer')).not.toBeInTheDocument(); expect(screen.getByTestId('vc-popup-incoming')).toBeInTheDocument(); @@ -69,12 +68,12 @@ it('should prioritize session over dialer', async () => { const testCases = Object.values(composeStories(stories)).map((story) => [story.storyName || 'Story', story]); test.each(testCases)(`renders %s without crashing`, async (_storyname, Story) => { - const tree = render(, { wrapper: appRoot.build(), legacyRoot: true }); + const tree = render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); expect(replaceReactAriaIds(tree.baseElement)).toMatchSnapshot(); }); test.each(testCases)('%s should have no a11y violations', async (_storyname, Story) => { - const { container } = render(, { wrapper: appRoot.build(), legacyRoot: true }); + const { container } = render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); const results = await axe(container); expect(results).toHaveNoViolations(); diff --git a/packages/ui-voip/src/components/VoipPopup/components/VoipPopupHeader.spec.tsx b/packages/ui-voip/src/components/VoipPopup/components/VoipPopupHeader.spec.tsx index 07e455260522..2b9bae29b5c0 100644 --- a/packages/ui-voip/src/components/VoipPopup/components/VoipPopupHeader.spec.tsx +++ b/packages/ui-voip/src/components/VoipPopup/components/VoipPopupHeader.spec.tsx @@ -1,44 +1,43 @@ import { mockAppRoot } from '@rocket.chat/mock-providers'; import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import VoipPopupHeader from './VoipPopupHeader'; -const appRoot = mockAppRoot(); - it('should render title', () => { - render(voice call header title, { wrapper: appRoot.build(), legacyRoot: true }); + render(voice call header title, { wrapper: mockAppRoot().build(), legacyRoot: true }); expect(screen.getByText('voice call header title')).toBeInTheDocument(); }); it('should not render close button when onClose is not provided', () => { - render(, { wrapper: appRoot.build(), legacyRoot: true }); + render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); expect(screen.queryByRole('button', { name: 'Close' })).not.toBeInTheDocument(); }); it('should render close button when onClose is provided', () => { - render(, { wrapper: appRoot.build(), legacyRoot: true }); + render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); expect(screen.getByRole('button', { name: 'Close' })).toBeInTheDocument(); }); -it('should call onClose when close button is clicked', () => { +it('should call onClose when close button is clicked', async () => { const closeFn = jest.fn(); - render(, { wrapper: appRoot.build(), legacyRoot: true }); + render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); - screen.getByRole('button', { name: 'Close' }).click(); + await userEvent.click(screen.getByRole('button', { name: 'Close' })); expect(closeFn).toHaveBeenCalled(); }); it('should render settings button by default', () => { - render(, { wrapper: appRoot.build(), legacyRoot: true }); + render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); expect(screen.getByRole('button', { name: /Device_settings/ })).toBeInTheDocument(); }); it('should not render settings button when hideSettings is true', () => { - render(text, { wrapper: appRoot.build(), legacyRoot: true }); + render(text, { wrapper: mockAppRoot().build(), legacyRoot: true }); expect(screen.queryByRole('button', { name: /Device_settings/ })).not.toBeInTheDocument(); }); diff --git a/packages/ui-voip/src/components/VoipPopup/views/VoipDialerView.spec.tsx b/packages/ui-voip/src/components/VoipPopup/views/VoipDialerView.spec.tsx index 3add6e463982..f2ef82edf36c 100644 --- a/packages/ui-voip/src/components/VoipPopup/views/VoipDialerView.spec.tsx +++ b/packages/ui-voip/src/components/VoipPopup/views/VoipDialerView.spec.tsx @@ -7,14 +7,12 @@ import VoipDialerView from './VoipDialerView'; const makeCall = jest.fn(); const closeDialer = jest.fn(); -const appRoot = mockAppRoot(); - jest.mock('../../../hooks/useVoipAPI', () => ({ useVoipAPI: jest.fn(() => ({ makeCall, closeDialer })), })); it('should look good', async () => { - render(, { wrapper: appRoot.build(), legacyRoot: true }); + render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); expect(screen.getByText('New_Call')).toBeInTheDocument(); expect(screen.getByRole('button', { name: /Device_settings/ })).toBeInTheDocument(); @@ -22,7 +20,7 @@ it('should look good', async () => { }); it('should only enable call button if input has value (keyboard)', async () => { - render(, { wrapper: appRoot.build(), legacyRoot: true }); + render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); expect(screen.getByRole('button', { name: /Call/i })).toBeDisabled(); await userEvent.type(screen.getByLabelText('Phone_number'), '123'); @@ -30,21 +28,22 @@ it('should only enable call button if input has value (keyboard)', async () => { }); it('should only enable call button if input has value (mouse)', async () => { - render(, { wrapper: appRoot.build(), legacyRoot: true }); + render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); expect(screen.getByRole('button', { name: /Call/i })).toBeDisabled(); - screen.getByTestId(`dial-pad-button-1`).click(); - screen.getByTestId(`dial-pad-button-2`).click(); - screen.getByTestId(`dial-pad-button-3`).click(); + + await userEvent.click(screen.getByTestId(`dial-pad-button-1`)); + await userEvent.click(screen.getByTestId(`dial-pad-button-2`)); + await userEvent.click(screen.getByTestId(`dial-pad-button-3`)); expect(screen.getByRole('button', { name: /Call/i })).toBeEnabled(); }); it('should call methods makeCall and closeDialer when call button is clicked', async () => { - render(, { wrapper: appRoot.build(), legacyRoot: true }); + render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); await userEvent.type(screen.getByLabelText('Phone_number'), '123'); - screen.getByTestId(`dial-pad-button-1`).click(); - screen.getByRole('button', { name: /Call/i }).click(); + await userEvent.click(screen.getByTestId(`dial-pad-button-1`)); + await userEvent.click(screen.getByRole('button', { name: /Call/i })); expect(makeCall).toHaveBeenCalledWith('1231'); expect(closeDialer).toHaveBeenCalled(); }); diff --git a/packages/ui-voip/src/components/VoipPopup/views/VoipErrorView.spec.tsx b/packages/ui-voip/src/components/VoipPopup/views/VoipErrorView.spec.tsx index 426b284f5102..eba09a99f895 100644 --- a/packages/ui-voip/src/components/VoipPopup/views/VoipErrorView.spec.tsx +++ b/packages/ui-voip/src/components/VoipPopup/views/VoipErrorView.spec.tsx @@ -1,5 +1,6 @@ import { mockAppRoot } from '@rocket.chat/mock-providers'; import { render, screen, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { createMockFreeSwitchExtensionDetails, createMockVoipErrorSession } from '../../../tests/mocks'; import VoipErrorView from './VoipErrorView'; @@ -26,11 +27,11 @@ it('should only enable error actions', () => { expect(screen.getByRole('button', { name: 'End_call' })).toBeEnabled(); }); -it('should properly interact with the voice call session', () => { +it('should properly interact with the voice call session', async () => { const errorSession = createMockVoipErrorSession({ error: { status: -1, reason: '' } }); render(, { wrapper: appRoot.build(), legacyRoot: true }); - screen.getByRole('button', { name: 'End_call' }).click(); + await userEvent.click(screen.getByRole('button', { name: 'End_call' })); expect(errorSession.end).toHaveBeenCalled(); }); @@ -39,7 +40,7 @@ it('should properly render unknown error calls', async () => { render(, { wrapper: appRoot.build(), legacyRoot: true }); expect(screen.getByText('Unable_to_complete_call')).toBeInTheDocument(); - screen.getByRole('button', { name: 'End_call' }).click(); + await userEvent.click(screen.getByRole('button', { name: 'End_call' })); expect(session.end).toHaveBeenCalled(); }); @@ -49,7 +50,7 @@ it('should properly render error for unavailable calls', async () => { expect(screen.getByText('Temporarily_unavailable')).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'End_call' })).toBeEnabled(); - screen.getByRole('button', { name: 'End_call' }).click(); + await userEvent.click(screen.getByRole('button', { name: 'End_call' })); expect(session.end).toHaveBeenCalled(); }); @@ -59,7 +60,7 @@ it('should properly render error for busy calls', async () => { expect(screen.getByText('Caller_is_busy')).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'End_call' })).toBeEnabled(); - screen.getByRole('button', { name: 'End_call' }).click(); + await userEvent.click(screen.getByRole('button', { name: 'End_call' })); expect(session.end).toHaveBeenCalled(); }); @@ -69,6 +70,6 @@ it('should properly render error for terminated calls', async () => { expect(screen.getByText('Call_terminated')).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'End_call' })).toBeEnabled(); - screen.getByRole('button', { name: 'End_call' }).click(); + await userEvent.click(screen.getByRole('button', { name: 'End_call' })); expect(session.end).toHaveBeenCalled(); }); diff --git a/packages/ui-voip/src/components/VoipPopup/views/VoipIncomingView.spec.tsx b/packages/ui-voip/src/components/VoipPopup/views/VoipIncomingView.spec.tsx index 67e1bdc131f0..88f2f79da5aa 100644 --- a/packages/ui-voip/src/components/VoipPopup/views/VoipIncomingView.spec.tsx +++ b/packages/ui-voip/src/components/VoipPopup/views/VoipIncomingView.spec.tsx @@ -1,5 +1,6 @@ import { mockAppRoot } from '@rocket.chat/mock-providers'; import { render, screen, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { createMockFreeSwitchExtensionDetails, createMockVoipIncomingSession } from '../../../tests/mocks'; import VoipIncomingView from './VoipIncomingView'; @@ -27,11 +28,11 @@ it('should only enable incoming actions', () => { expect(screen.getByRole('button', { name: 'Accept' })).toBeEnabled(); }); -it('should properly interact with the voice call session', () => { +it('should properly interact with the voice call session', async () => { render(, { wrapper: appRoot.build(), legacyRoot: true }); - screen.getByRole('button', { name: 'Decline' }).click(); - screen.getByRole('button', { name: 'Accept' }).click(); + await userEvent.click(screen.getByRole('button', { name: 'Decline' })); + await userEvent.click(screen.getByRole('button', { name: 'Accept' })); expect(incomingSession.end).toHaveBeenCalled(); expect(incomingSession.accept).toHaveBeenCalled(); diff --git a/packages/ui-voip/src/components/VoipPopup/views/VoipOngoingView.spec.tsx b/packages/ui-voip/src/components/VoipPopup/views/VoipOngoingView.spec.tsx index 397e369d6bbb..d1d99f5fa0c7 100644 --- a/packages/ui-voip/src/components/VoipPopup/views/VoipOngoingView.spec.tsx +++ b/packages/ui-voip/src/components/VoipPopup/views/VoipOngoingView.spec.tsx @@ -1,5 +1,6 @@ import { mockAppRoot } from '@rocket.chat/mock-providers'; import { render, screen, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { createMockFreeSwitchExtensionDetails, createMockVoipOngoingSession } from '../../../tests/mocks'; import VoipOngoingView from './VoipOngoingView'; @@ -8,65 +9,65 @@ const wrapper = mockAppRoot().withEndpoint('GET', '/v1/voip-freeswitch.extension const ongoingSession = createMockVoipOngoingSession(); -describe('VoipOngoingView', () => { - beforeEach(() => { - jest.useFakeTimers(); - }); +const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); - afterEach(() => { - jest.clearAllTimers(); - }); +beforeEach(() => { + jest.useFakeTimers(); +}); + +afterEach(() => { + jest.clearAllTimers(); +}); - it('should properly render ongoing view', async () => { - render(, { wrapper: wrapper.build(), legacyRoot: true }); +it('should properly render ongoing view', async () => { + render(, { wrapper: wrapper.build(), legacyRoot: true }); - expect(screen.getByText('00:00')).toBeInTheDocument(); - expect(screen.getByRole('button', { name: /Device_settings/ })).toBeInTheDocument(); - expect(await screen.findByText('Administrator')).toBeInTheDocument(); - expect(screen.queryByText('On_Hold')).not.toBeInTheDocument(); - expect(screen.queryByText('Muted')).not.toBeInTheDocument(); - }); + expect(screen.getByText('00:00')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /Device_settings/ })).toBeInTheDocument(); + expect(await screen.findByText('Administrator')).toBeInTheDocument(); + expect(screen.queryByText('On_Hold')).not.toBeInTheDocument(); + expect(screen.queryByText('Muted')).not.toBeInTheDocument(); +}); - it('should display on hold and muted', () => { - ongoingSession.isMuted = true; - ongoingSession.isHeld = true; +it('should display on hold and muted', () => { + ongoingSession.isMuted = true; + ongoingSession.isHeld = true; - render(, { wrapper: wrapper.build(), legacyRoot: true }); + render(, { wrapper: wrapper.build(), legacyRoot: true }); - expect(screen.getByText('On_Hold')).toBeInTheDocument(); - expect(screen.getByText('Muted')).toBeInTheDocument(); + expect(screen.getByText('On_Hold')).toBeInTheDocument(); + expect(screen.getByText('Muted')).toBeInTheDocument(); - ongoingSession.isMuted = false; - ongoingSession.isHeld = false; - }); + ongoingSession.isMuted = false; + ongoingSession.isHeld = false; +}); - it('should only enable ongoing call actions', () => { - render(, { wrapper: wrapper.build(), legacyRoot: true }); +it('should only enable ongoing call actions', () => { + render(, { wrapper: wrapper.build(), legacyRoot: true }); - expect(within(screen.getByTestId('vc-popup-footer')).queryAllByRole('button')).toHaveLength(5); - expect(screen.getByRole('button', { name: 'Turn_off_microphone' })).toBeEnabled(); - expect(screen.getByRole('button', { name: 'Hold' })).toBeEnabled(); - expect(screen.getByRole('button', { name: 'Open_Dialpad' })).toBeEnabled(); - expect(screen.getByRole('button', { name: 'Transfer_call' })).toBeEnabled(); - expect(screen.getByRole('button', { name: 'End_call' })).toBeEnabled(); - }); + expect(within(screen.getByTestId('vc-popup-footer')).queryAllByRole('button')).toHaveLength(5); + expect(screen.getByRole('button', { name: 'Turn_off_microphone' })).toBeEnabled(); + expect(screen.getByRole('button', { name: 'Hold' })).toBeEnabled(); + expect(screen.getByRole('button', { name: 'Open_Dialpad' })).toBeEnabled(); + expect(screen.getByRole('button', { name: 'Transfer_call' })).toBeEnabled(); + expect(screen.getByRole('button', { name: 'End_call' })).toBeEnabled(); +}); - it('should properly interact with the voice call session', () => { - render(, { wrapper: wrapper.build(), legacyRoot: true }); +it('should properly interact with the voice call session', async () => { + render(, { wrapper: wrapper.build(), legacyRoot: true }); - screen.getByRole('button', { name: 'Turn_off_microphone' }).click(); - expect(ongoingSession.mute).toHaveBeenCalled(); + await user.click(screen.getByRole('button', { name: 'Turn_off_microphone' })); + expect(ongoingSession.mute).toHaveBeenCalled(); - screen.getByRole('button', { name: 'Hold' }).click(); - expect(ongoingSession.hold).toHaveBeenCalled(); + await user.click(screen.getByRole('button', { name: 'Hold' })); + expect(ongoingSession.hold).toHaveBeenCalled(); - screen.getByRole('button', { name: 'Open_Dialpad' }).click(); - screen.getByTestId('dial-pad-button-1').click(); - expect(screen.getByRole('textbox', { name: 'Phone_number' })).toHaveValue('1'); - expect(ongoingSession.dtmf).toHaveBeenCalledWith('1'); + await user.click(screen.getByRole('button', { name: 'Open_Dialpad' })); + await user.click(screen.getByTestId('dial-pad-button-1')); + expect(screen.getByRole('textbox', { name: 'Phone_number' })).toHaveValue('1'); + expect(ongoingSession.dtmf).toHaveBeenCalledWith('1'); - expect(screen.getByRole('button', { name: 'End_call' })).toBeEnabled(); - screen.getByRole('button', { name: 'End_call' }).click(); - expect(ongoingSession.end).toHaveBeenCalled(); - }); + expect(screen.getByRole('button', { name: 'End_call' })).toBeEnabled(); + await user.click(screen.getByRole('button', { name: 'End_call' })); + expect(ongoingSession.end).toHaveBeenCalled(); }); diff --git a/packages/ui-voip/src/components/VoipPopup/views/VoipOutgoingView.spec.tsx b/packages/ui-voip/src/components/VoipPopup/views/VoipOutgoingView.spec.tsx index 2f5f3b5840f4..e733bb324c22 100644 --- a/packages/ui-voip/src/components/VoipPopup/views/VoipOutgoingView.spec.tsx +++ b/packages/ui-voip/src/components/VoipPopup/views/VoipOutgoingView.spec.tsx @@ -1,5 +1,6 @@ import { mockAppRoot } from '@rocket.chat/mock-providers'; import { render, screen, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { createMockFreeSwitchExtensionDetails, createMockVoipOutgoingSession } from '../../../tests/mocks'; import VoipOutgoingView from './VoipOutgoingView'; @@ -27,9 +28,9 @@ it('should only enable outgoing actions', () => { expect(screen.getByRole('button', { name: 'End_call' })).toBeEnabled(); }); -it('should properly interact with the voice call session', () => { +it('should properly interact with the voice call session', async () => { render(, { wrapper: wrapper.build(), legacyRoot: true }); - screen.getByRole('button', { name: 'End_call' }).click(); + await userEvent.click(screen.getByRole('button', { name: 'End_call' })); expect(outgoingSession.end).toHaveBeenCalled(); }); diff --git a/packages/ui-voip/src/components/VoipTimer/VoipTimer.spec.tsx b/packages/ui-voip/src/components/VoipTimer/VoipTimer.spec.tsx index 7121a0f1176e..f57338b5bb32 100644 --- a/packages/ui-voip/src/components/VoipTimer/VoipTimer.spec.tsx +++ b/packages/ui-voip/src/components/VoipTimer/VoipTimer.spec.tsx @@ -3,38 +3,34 @@ import { act, render, screen } from '@testing-library/react'; import VoipTimer from './VoipTimer'; -const appRoot = mockAppRoot(); - -describe('VoipTimer', () => { - beforeEach(() => { - jest.useFakeTimers(); - }); +beforeEach(() => { + jest.useFakeTimers(); +}); - afterEach(() => { - jest.clearAllTimers(); - }); +afterEach(() => { + jest.clearAllTimers(); +}); - it('should display the initial time correctly', () => { - render(, { wrapper: appRoot.build(), legacyRoot: true }); +it('should display the initial time correctly', () => { + render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); - expect(screen.getByText('00:00')).toBeInTheDocument(); - }); - - it('should update the time after a few seconds', () => { - render(, { wrapper: appRoot.build(), legacyRoot: true }); + expect(screen.getByText('00:00')).toBeInTheDocument(); +}); - act(() => { - jest.advanceTimersByTime(5000); - }); +it('should update the time after a few seconds', () => { + render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); - expect(screen.getByText('00:05')).toBeInTheDocument(); + act(() => { + jest.advanceTimersByTime(5000); }); - it('should start with a minute on the timer', () => { - const startTime = new Date(); - startTime.setMinutes(startTime.getMinutes() - 1); - render(, { wrapper: appRoot.build(), legacyRoot: true }); + expect(screen.getByText('00:05')).toBeInTheDocument(); +}); - expect(screen.getByText('01:00')).toBeInTheDocument(); - }); +it('should start with a minute on the timer', () => { + const startTime = new Date(); + startTime.setMinutes(startTime.getMinutes() - 1); + render(, { wrapper: mockAppRoot().build(), legacyRoot: true }); + + expect(screen.getByText('01:00')).toBeInTheDocument(); }); diff --git a/packages/ui-voip/src/components/VoipTransferModal/VoipTransferModal.spec.tsx b/packages/ui-voip/src/components/VoipTransferModal/VoipTransferModal.spec.tsx index ddec476bcf77..f51ca43da639 100644 --- a/packages/ui-voip/src/components/VoipTransferModal/VoipTransferModal.spec.tsx +++ b/packages/ui-voip/src/components/VoipTransferModal/VoipTransferModal.spec.tsx @@ -2,7 +2,7 @@ import { mockAppRoot } from '@rocket.chat/mock-providers'; import { act, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import VoipTransferModal from '.'; +import VoipTransferModal from './VoipTransferModal'; it('should be able to select transfer target', async () => { const confirmFn = jest.fn(); @@ -57,19 +57,19 @@ it('should be able to select transfer target', async () => { expect(hangUpAnTransferButton).toBeDisabled(); - screen.getByLabelText('Transfer_to').focus(); await act(async () => { + await userEvent.type(screen.getByRole('textbox', { name: 'Transfer_to' }), 'Jane Doe'); const userOption = await screen.findByRole('option', { name: 'Jane Doe' }); await userEvent.click(userOption); }); expect(hangUpAnTransferButton).toBeEnabled(); - hangUpAnTransferButton.click(); + await userEvent.click(hangUpAnTransferButton); expect(confirmFn).toHaveBeenCalledWith({ extension: '1011', name: 'Jane Doe' }); }); -it('should call onCancel when Cancel is clicked', () => { +it('should call onCancel when Cancel is clicked', async () => { const confirmFn = jest.fn(); const cancelFn = jest.fn(); render(, { @@ -77,12 +77,12 @@ it('should call onCancel when Cancel is clicked', () => { legacyRoot: true, }); - screen.getByText('Cancel').click(); + await userEvent.click(screen.getByRole('button', { name: 'Cancel' })); expect(cancelFn).toHaveBeenCalled(); }); -it('should call onCancel when X is clicked', () => { +it('should call onCancel when X is clicked', async () => { const confirmFn = jest.fn(); const cancelFn = jest.fn(); render(, { @@ -90,7 +90,7 @@ it('should call onCancel when X is clicked', () => { legacyRoot: true, }); - screen.getByLabelText('Close').click(); + await userEvent.click(screen.getByRole('button', { name: 'Close' })); expect(cancelFn).toHaveBeenCalled(); }); diff --git a/packages/ui-voip/src/hooks/useVoipEffect.tsx b/packages/ui-voip/src/hooks/useVoipEffect.tsx index e412959ff923..b7c67672fc92 100644 --- a/packages/ui-voip/src/hooks/useVoipEffect.tsx +++ b/packages/ui-voip/src/hooks/useVoipEffect.tsx @@ -1,4 +1,4 @@ -import { useContext, useMemo, useRef } from 'react'; +import { useCallback, useContext, useRef } from 'react'; import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { VoipContext } from '../contexts/VoipContext'; @@ -6,25 +6,23 @@ import type VoIPClient from '../lib/VoipClient'; export const useVoipEffect = (transform: (voipClient: VoIPClient) => T, initialValue: T) => { const { voipClient } = useContext(VoipContext); - const initValue = useRef(initialValue); + const stateRef = useRef(initialValue); const transformFn = useRef(transform); - const [subscribe, getSnapshot] = useMemo(() => { - let state: T = initValue.current; + const getSnapshot = useCallback(() => stateRef.current, []); - const getSnapshot = (): T => state; - const subscribe = (cb: () => void) => { + const subscribe = useCallback( + (cb: () => void) => { if (!voipClient) return () => undefined; - state = transformFn.current(voipClient); + stateRef.current = transformFn.current(voipClient); return voipClient.on('stateChanged', (): void => { - state = transformFn.current(voipClient); + stateRef.current = transformFn.current(voipClient); cb(); }); - }; - - return [subscribe, getSnapshot]; - }, [voipClient]); + }, + [voipClient], + ); return useSyncExternalStore(subscribe, getSnapshot); }; diff --git a/yarn.lock b/yarn.lock index b4ed3718e3db..d4c42cf92a83 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6705,6 +6705,20 @@ __metadata: languageName: node linkType: hard +"@react-aria/test-utils@npm:1.0.0-alpha.2": + version: 1.0.0-alpha.2 + resolution: "@react-aria/test-utils@npm:1.0.0-alpha.2" + dependencies: + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + "@testing-library/react": ^15.0.7 + "@testing-library/user-event": ^13.0.0 || ^14.0.0 + jest: ^29.5.0 + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + checksum: 10/b4f802492aced144df72c4910a815b04830cdf40817aff13b5106a9760813f6e5d80f2b3b600383900bc6cc6ccbc7fec1f1b4c622b9e618f00e9e3df328567a8 + languageName: node + linkType: hard + "@react-aria/textfield@npm:3.0.0-nightly.1779+afb946c4a": version: 3.0.0-nightly.1779 resolution: "@react-aria/textfield@npm:3.0.0-nightly.1779" @@ -7022,6 +7036,21 @@ __metadata: languageName: node linkType: hard +"@react-spectrum/test-utils@npm:~1.0.0-alpha.2": + version: 1.0.0-alpha.2 + resolution: "@react-spectrum/test-utils@npm:1.0.0-alpha.2" + dependencies: + "@react-aria/test-utils": "npm:1.0.0-alpha.2" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + "@testing-library/react": ^15.0.7 + "@testing-library/user-event": ^13.0.0 || ^14.0.0 + jest: ^29.5.0 + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + checksum: 10/f9444cf7b4881b3c86c9142894fd6a5c4790aa5a9aab61a59da270b3b2a959f211aa5bf130e8a9baf4695d01b27a319d1f5bcabb2a14c8fe68be78eaac758fc0 + languageName: node + linkType: hard + "@react-spring/animated@npm:~9.7.3": version: 9.7.3 resolution: "@react-spring/animated@npm:9.7.3" @@ -10093,6 +10122,7 @@ __metadata: dependencies: "@babel/core": "npm:~7.25.8" "@faker-js/faker": "npm:~8.0.2" + "@react-spectrum/test-utils": "npm:~1.0.0-alpha.2" "@rocket.chat/css-in-js": "npm:~0.31.25" "@rocket.chat/emitter": "npm:~0.31.25" "@rocket.chat/eslint-config": "workspace:^" @@ -10118,6 +10148,7 @@ __metadata: "@types/jest": "npm:~29.5.12" "@types/jest-axe": "npm:~3.5.9" "@types/react": "npm:~17.0.80" + "@types/react-dom": "npm:~17.0.25" eslint: "npm:~8.45.0" eslint-plugin-react: "npm:~7.32.2" eslint-plugin-react-hooks: "npm:~4.6.2"