diff --git a/.vscode/settings.json b/.vscode/settings.json index 73e6a5b81..f54856a5b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -78,6 +78,7 @@ "paystrings", "stylelint", "svgr", + "testid", "topojson", "VITE", "xchain", diff --git a/package-lock.json b/package-lock.json index f7f82efa3..dd4ab9df8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@rollup/plugin-inject": "^5.0.5", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^12.1.5", + "@testing-library/user-event": "^14.5.2", "@vitejs/plugin-react": "^4.2.1", "@xrplf/isomorphic": "^1.0.0-beta.1", "@xrplf/prettier-config": "^1.9.1", @@ -6338,6 +6339,18 @@ "react-dom": "<18.0.0" } }, + "node_modules/@testing-library/user-event": { + "version": "14.5.2", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.2.tgz", + "integrity": "sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "dev": true, @@ -32972,6 +32985,12 @@ "@types/react-dom": "<18.0.0" } }, + "@testing-library/user-event": { + "version": "14.5.2", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.2.tgz", + "integrity": "sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==", + "requires": {} + }, "@tootallnate/once": { "version": "1.1.2", "dev": true, diff --git a/package.json b/package.json index f3572e258..f3c6023b7 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "@rollup/plugin-inject": "^5.0.5", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^12.1.5", + "@testing-library/user-event": "^14.5.2", "@vitejs/plugin-react": "^4.2.1", "@xrplf/isomorphic": "^1.0.0-beta.1", "@xrplf/prettier-config": "^1.9.1", diff --git a/src/containers/Accounts/AMM/AMMAccounts/AMMAccountHeader/AMMAccountHeader.tsx b/src/containers/Accounts/AMM/AMMAccounts/AMMAccountHeader/AMMAccountHeader.tsx index 9ba394ff4..4fafda453 100644 --- a/src/containers/Accounts/AMM/AMMAccounts/AMMAccountHeader/AMMAccountHeader.tsx +++ b/src/containers/Accounts/AMM/AMMAccounts/AMMAccountHeader/AMMAccountHeader.tsx @@ -54,11 +54,11 @@ export const AMMAccountHeader = (props: { data: AmmDataType }) => { } return ( -
+
Account ID -
+
{ const TEST_ACCOUNT_ID = 'rTEST_ACCOUNT' - const createWrapper = (state: AmmDataType) => - mount( + const renderComponent = (state: AmmDataType) => + render( @@ -21,6 +21,8 @@ describe('AMM Account Header', () => { , ) + afterEach(cleanup) + it('renders AMM account page', async () => { const state: AmmDataType = { balance: { currency: 'XRP', amount: 1000, issuer: 'hi' }, @@ -31,19 +33,17 @@ describe('AMM Account Header', () => { language: 'en-US', } - const wrapper = createWrapper(state) + const { container } = renderComponent(state) await flushPromises() - wrapper.update() - expect(wrapper.find(AMMAccountHeader).length).toBe(1) - expect(wrapper.find('.box-header .title').length).toBe(1) - expect(wrapper.find('.currency-pair').length).toBe(1) - expect(wrapper.text().includes('500')).toBe(true) - expect(wrapper.text().includes('0.01%')).toBe(true) - expect(wrapper.text().includes('XRP.hi/USD.hi')).toBe(true) - expect(wrapper.text().includes('\uE9001,000')).toBe(true) - expect(wrapper.text().includes('9,000')).toBe(true) - expect(wrapper.text().includes('rTEST_ACCOUNT')).toBe(true) - wrapper.unmount() + expect(screen.queryAllByTestId('amm-header')).toHaveLength(1) + expect(screen.queryAllByText('Account ID')).toHaveLength(1) + expect(screen.getAllByTestId('currency-pair')).toHaveLength(1) + expect(container).toHaveTextContent('500') + expect(container).toHaveTextContent('0.01%') + expect(container).toHaveTextContent('XRP.hi/USD.hi') + expect(container).toHaveTextContent('\uE9001,000') + expect(container).toHaveTextContent('9,000') + expect(container).toHaveTextContent('rTEST_ACCOUNT') }) }) diff --git a/src/containers/Accounts/AMM/AMMAccounts/test/AMMAccounts.test.tsx b/src/containers/Accounts/AMM/AMMAccounts/test/AMMAccounts.test.tsx index 8f3a4f260..ea7e65433 100644 --- a/src/containers/Accounts/AMM/AMMAccounts/test/AMMAccounts.test.tsx +++ b/src/containers/Accounts/AMM/AMMAccounts/test/AMMAccounts.test.tsx @@ -1,135 +1,134 @@ -import { mount } from 'enzyme' +import { render, screen, cleanup } from '@testing-library/react' import { Route } from 'react-router' import i18n from '../../../../../i18n/testConfig' import * as rippled from '../../../../../rippled/lib/rippled' -import NoMatch from '../../../../NoMatch' -import { AMMAccountHeader } from '../AMMAccountHeader/AMMAccountHeader' -import { AccountTransactionTable } from '../../../AccountTransactionTable' import { AMMAccounts } from '../index' import { flushPromises, QuickHarness } from '../../../../test/utils' import { ACCOUNT_ROUTE } from '../../../../App/routes' -function setSpy(accountInfo: any, getLedgerEntry: any, ammInfo: any) { - const spyAccountInfo = jest.spyOn(rippled, 'getAccountInfo') - const spyLedgerEntry = jest.spyOn(rippled, 'getLedgerEntry') - const spyInfo = jest.spyOn(rippled, 'getAMMInfo') - spyAccountInfo.mockReturnValue( - new Promise((resolve) => { - resolve(accountInfo) - }), - ) - spyLedgerEntry.mockReturnValue( - new Promise((resolve) => { - resolve(getLedgerEntry) - }), - ) - spyInfo.mockReturnValue( - new Promise((resolve) => { - resolve(ammInfo) - }), - ) +const TEST_ACCOUNT_ID = 'rTEST_ACCOUNT' +const ACCOUNT_INFO: any = { + AMMID: '0017D8D412779284FDA6A63CEBEADD43BC2FEF37181C3C234ADAC9EFBB5FDB53', + Account: 'rJbt6ryq1TzikBuvVQDaxVLqL77eJeibsj', + Balance: '10000000', + Flags: 26214400, + LedgerEntryType: 'AccountRoot', + OwnerCount: 1, + PreviousTxnID: + '2A9F2B8D74CBECFF339BBD5CD9E42468984D3D8AA5D521B9610F31B014629DC2', + PreviousTxnLgrSeq: 58180, + Sequence: 58180, + index: '115CA30FD281E3265AA22F563B4ADE4BD15A6107F1E5105056F191882BE78FC4', } -describe('AMM Account Page', () => { - const TEST_ACCOUNT_ID = 'rTEST_ACCOUNT' - const accountInfo: any = { - AMMID: '0017D8D412779284FDA6A63CEBEADD43BC2FEF37181C3C234ADAC9EFBB5FDB53', +const LEDGER_ENTRY: any = { + index: '0017D8D412779284FDA6A63CEBEADD43BC2FEF37181C3C234ADAC9EFBB5FDB53', + ledger_hash: + '6C1914FF5966D2FD060B92B07A30A303369F28132DB5E8D73BED4FFC8A372EF2', + ledger_index: 285601, + node: { Account: 'rJbt6ryq1TzikBuvVQDaxVLqL77eJeibsj', - Balance: '10000000', - Flags: 26214400, - LedgerEntryType: 'AccountRoot', - OwnerCount: 1, - PreviousTxnID: - '2A9F2B8D74CBECFF339BBD5CD9E42468984D3D8AA5D521B9610F31B014629DC2', - PreviousTxnLgrSeq: 58180, - Sequence: 58180, - index: '115CA30FD281E3265AA22F563B4ADE4BD15A6107F1E5105056F191882BE78FC4', - } - - const ledgerEntry: any = { - index: '0017D8D412779284FDA6A63CEBEADD43BC2FEF37181C3C234ADAC9EFBB5FDB53', - ledger_hash: - '6C1914FF5966D2FD060B92B07A30A303369F28132DB5E8D73BED4FFC8A372EF2', - ledger_index: 285601, - node: { - Account: 'rJbt6ryq1TzikBuvVQDaxVLqL77eJeibsj', - Asset: { - currency: 'XRP', - }, - Asset2: { - currency: 'USD', - issuer: 'rJd9Ti4TF2Mrn268LW7sSw8E16J4hYzMiD', - }, - AuctionSlot: { - Account: 'rJd9Ti4TF2Mrn268LW7sSw8E16J4hYzMiD', - Expiration: 745719332, - Price: { - currency: '03930D02208264E2E40EC1B0C09E4DB96EE197B1', - issuer: 'rJbt6ryq1TzikBuvVQDaxVLqL77eJeibsj', - value: '0', - }, - }, - Flags: 0, - LPTokenBalance: { + Asset: { + currency: 'XRP', + }, + Asset2: { + currency: 'USD', + issuer: 'rJd9Ti4TF2Mrn268LW7sSw8E16J4hYzMiD', + }, + AuctionSlot: { + Account: 'rJd9Ti4TF2Mrn268LW7sSw8E16J4hYzMiD', + Expiration: 745719332, + Price: { currency: '03930D02208264E2E40EC1B0C09E4DB96EE197B1', issuer: 'rJbt6ryq1TzikBuvVQDaxVLqL77eJeibsj', - value: '10000', + value: '0', }, - LedgerEntryType: 'AMM', - VoteSlots: [ - { - VoteEntry: { - Account: 'rJd9Ti4TF2Mrn268LW7sSw8E16J4hYzMiD', - VoteWeight: 100000, - }, - }, - ], - index: '0017D8D412779284FDA6A63CEBEADD43BC2FEF37181C3C234ADAC9EFBB5FDB53', }, - validated: true, - } - - const ammInfo: any = { - amm: { - account: 'rJbt6ryq1TzikBuvVQDaxVLqL77eJeibsj', - amount: '10000000', - amount2: { - currency: 'USD', - issuer: 'rJd9Ti4TF2Mrn268LW7sSw8E16J4hYzMiD', - value: '10', - }, - asset2_frozen: false, - auction_slot: { - account: 'rJd9Ti4TF2Mrn268LW7sSw8E16J4hYzMiD', - discounted_fee: 0, - expiration: '2023-08-19T00:15:32+0000', - price: { - currency: '03930D02208264E2E40EC1B0C09E4DB96EE197B1', - issuer: 'rJbt6ryq1TzikBuvVQDaxVLqL77eJeibsj', - value: '0', + Flags: 0, + LPTokenBalance: { + currency: '03930D02208264E2E40EC1B0C09E4DB96EE197B1', + issuer: 'rJbt6ryq1TzikBuvVQDaxVLqL77eJeibsj', + value: '10000', + }, + LedgerEntryType: 'AMM', + VoteSlots: [ + { + VoteEntry: { + Account: 'rJd9Ti4TF2Mrn268LW7sSw8E16J4hYzMiD', + VoteWeight: 100000, }, - time_interval: 20, }, - lp_token: { + ], + index: '0017D8D412779284FDA6A63CEBEADD43BC2FEF37181C3C234ADAC9EFBB5FDB53', + }, + validated: true, +} + +const AMM_INFO: any = { + amm: { + account: 'rJbt6ryq1TzikBuvVQDaxVLqL77eJeibsj', + amount: '10000000', + amount2: { + currency: 'USD', + issuer: 'rJd9Ti4TF2Mrn268LW7sSw8E16J4hYzMiD', + value: '10', + }, + asset2_frozen: false, + auction_slot: { + account: 'rJd9Ti4TF2Mrn268LW7sSw8E16J4hYzMiD', + discounted_fee: 0, + expiration: '2023-08-19T00:15:32+0000', + price: { currency: '03930D02208264E2E40EC1B0C09E4DB96EE197B1', issuer: 'rJbt6ryq1TzikBuvVQDaxVLqL77eJeibsj', - value: '10000', + value: '0', }, - trading_fee: 0, - vote_slots: [ - { - account: 'rJd9Ti4TF2Mrn268LW7sSw8E16J4hYzMiD', - trading_fee: 0, - vote_weight: 100000, - }, - ], + time_interval: 20, + }, + lp_token: { + currency: '03930D02208264E2E40EC1B0C09E4DB96EE197B1', + issuer: 'rJbt6ryq1TzikBuvVQDaxVLqL77eJeibsj', + value: '10000', }, - ledger_current_index: 285641, - validated: false, + trading_fee: 0, + vote_slots: [ + { + account: 'rJd9Ti4TF2Mrn268LW7sSw8E16J4hYzMiD', + trading_fee: 0, + vote_weight: 100000, + }, + ], + }, + ledger_current_index: 285641, + validated: false, +} + +describe('AMM Account Page', () => { + afterAll(cleanup) + + function setSpy(accountInfo: any, getLedgerEntry: any, ammInfo: any) { + const spyAccountInfo = jest.spyOn(rippled, 'getAccountInfo') + const spyLedgerEntry = jest.spyOn(rippled, 'getLedgerEntry') + const spyInfo = jest.spyOn(rippled, 'getAMMInfo') + spyAccountInfo.mockReturnValue( + new Promise((resolve) => { + resolve(accountInfo) + }), + ) + spyLedgerEntry.mockReturnValue( + new Promise((resolve) => { + resolve(getLedgerEntry) + }), + ) + spyInfo.mockReturnValue( + new Promise((resolve) => { + resolve(ammInfo) + }), + ) } - const createWrapper = () => - mount( + const renderComponent = () => + render( { ) it('renders AMM account page', async () => { - setSpy(accountInfo, ledgerEntry, ammInfo) + setSpy(ACCOUNT_INFO, LEDGER_ENTRY, AMM_INFO) - const wrapper = createWrapper() + renderComponent() await flushPromises() - wrapper.update() - expect(wrapper.find(AMMAccountHeader).length).toBe(1) - expect(wrapper.find(AccountTransactionTable).length).toBe(1) - wrapper.unmount() + + expect(screen.queryByTestId('amm-header')).toBeDefined() + expect(screen.queryByTitle('transaction-table')).toBeDefined() }) it('shows error when amm info data is formatted incorrectly', async () => { - setSpy(accountInfo, ledgerEntry, 'ammInfo') + setSpy(ACCOUNT_INFO, LEDGER_ENTRY, 'ammInfo') - const wrapper = await createWrapper() + await renderComponent() await flushPromises() - wrapper.update() - expect(wrapper.find(AMMAccountHeader).length).toBe(0) - expect(wrapper.find(AccountTransactionTable).length).toBe(0) - expect(wrapper.find(NoMatch).length).toBe(1) - wrapper.unmount() + + expect(screen.queryByTestId('amm-header')).toBeNull() + expect(screen.queryByTitle('transaction-table')).toBeNull() + expect(screen.queryByText('uh-oh')).toBeDefined() }) it('shows error when account_info has no AMMID', async () => { const badAccountInfo: any = { - ...accountInfo, + ...ACCOUNT_INFO, } delete badAccountInfo.AMMID @@ -188,14 +185,13 @@ describe('AMM Account Page', () => { ], } - setSpy(badAccountInfo, badLedgerEntry, ammInfo) + setSpy(badAccountInfo, badLedgerEntry, AMM_INFO) - const wrapper = createWrapper() + renderComponent() await flushPromises() - wrapper.update() - expect(wrapper.find(AMMAccountHeader).length).toBe(0) - expect(wrapper.find(AccountTransactionTable).length).toBe(0) - expect(wrapper.find(NoMatch).length).toBe(1) - wrapper.unmount() + + expect(screen.queryByTestId('amm-header')).toBeNull() + expect(screen.queryByTitle('transaction-table')).toBeNull() + expect(screen.queryByText('uh-oh')).toBeDefined() }) }) diff --git a/src/containers/Accounts/AccountHeader/index.tsx b/src/containers/Accounts/AccountHeader/index.tsx index 3c04aff64..fc671c294 100644 --- a/src/containers/Accounts/AccountHeader/index.tsx +++ b/src/containers/Accounts/AccountHeader/index.tsx @@ -350,7 +350,7 @@ const AccountHeader = (props: AccountHeaderProps) => { const { xAddress, hasBridge } = data return ( -
+
Account ID diff --git a/src/containers/Accounts/AccountMPTTable/test/AccountMPTRow.test.tsx b/src/containers/Accounts/AccountMPTTable/test/AccountMPTRow.test.tsx index e8165c5a6..a80663110 100644 --- a/src/containers/Accounts/AccountMPTTable/test/AccountMPTRow.test.tsx +++ b/src/containers/Accounts/AccountMPTTable/test/AccountMPTRow.test.tsx @@ -1,6 +1,6 @@ import { I18nextProvider } from 'react-i18next' import { BrowserRouter } from 'react-router-dom' -import { mount } from 'enzyme' +import { render, screen, cleanup } from '@testing-library/react' import { QueryClientProvider } from 'react-query' import { AccountMPTRow } from '../AccountMPTTable' import i18n from '../../../../i18n/testConfig' @@ -26,8 +26,8 @@ const mptData = { } describe('AccountMPTRow', () => { - const createWrapper = (component: JSX.Element) => - mount( + const renderComponent = (component: JSX.Element) => + render( {component} @@ -35,6 +35,7 @@ describe('AccountMPTRow', () => { , ) + afterEach(cleanup) it('handles Account MPT row', async () => { const issuanceData = { node: { @@ -48,16 +49,17 @@ describe('AccountMPTRow', () => { Promise.resolve({ ...issuanceData }), ) - const wrapper = createWrapper() + renderComponent() await flushPromises() - wrapper.update() - expect(wrapper.find('td').at(0).html()).toBe( + + const elements = screen.getAllByRole('cell') + + expect(elements[0].outerHTML).toBe( '000017C2CE76E3E3328AE9E0D80CDD68BA72CC8D8D053DB6', ) - expect(wrapper.find('td').at(1).html()).toBe( + expect(elements[1].outerHTML).toBe( '', ) - expect(wrapper.find('td').at(2).html()).toBe('0.100') - wrapper.unmount() + expect(elements[2].outerHTML).toBe('0.100') }) }) diff --git a/src/containers/Accounts/AccountMPTTable/test/AccountMPTTable.test.tsx b/src/containers/Accounts/AccountMPTTable/test/AccountMPTTable.test.tsx index 5ad642fb2..c0a3b71fb 100644 --- a/src/containers/Accounts/AccountMPTTable/test/AccountMPTTable.test.tsx +++ b/src/containers/Accounts/AccountMPTTable/test/AccountMPTTable.test.tsx @@ -1,11 +1,10 @@ -import { mount } from 'enzyme' +import { render, screen, cleanup, fireEvent } from '@testing-library/react' import { QueryClientProvider } from 'react-query' import { I18nextProvider } from 'react-i18next' import { BrowserRouter } from 'react-router-dom' import { getAccountMPTs } from '../../../../rippled/lib/rippled' import { AccountMPTTable } from '../AccountMPTTable' import i18n from '../../../../i18n/testConfig' -import { EmptyMessageTableRow } from '../../../shared/EmptyMessageTableRow' import { testQueryClient } from '../../../test/QueryClient' import { flushPromises } from '../../../test/utils' @@ -42,8 +41,10 @@ const data = { describe('AccountMPTTable component', () => { const TEST_ACCOUNT_ID = 'rTEST_ACCOUNT' - const createWrapper = () => - mount( + afterEach(cleanup) + + const renderComponent = () => + render( @@ -62,11 +63,9 @@ describe('AccountMPTTable component', () => { mockedGetAccountMPTs.mockImplementation(() => Promise.resolve(data)) - const wrapper = createWrapper() + renderComponent() await flushPromises() - wrapper.update() - expect(wrapper.find('.load-more-btn')).not.toExist() - wrapper.unmount() + expect(screen.queryByRole('button')).toBeNull() }) it('should handle load more', async () => { @@ -79,14 +78,13 @@ describe('AccountMPTTable component', () => { }), ) - const wrapper = createWrapper() + renderComponent() await flushPromises() - wrapper.update() - expect(wrapper.find('.load-more-btn')).toExist() - wrapper.find('.load-more-btn').simulate('click') + expect(screen.queryByRole('button')).toBeDefined() + const button = screen.getByRole('button') + fireEvent.click(button) expect(mockedGetAccountMPTs.mock.calls[1][2]).toEqual('hello') - wrapper.unmount() }) it(`should handle no results`, async () => { @@ -104,11 +102,9 @@ describe('AccountMPTTable component', () => { }), ) - const wrapper = createWrapper() + renderComponent() await flushPromises() - wrapper.update() - expect(wrapper.find(EmptyMessageTableRow)).toExist() - wrapper.unmount() + expect(screen.queryByTestId('empty-message')).toBeDefined() }) }) diff --git a/src/containers/Accounts/AccountNFTTable/test/AccountNFTtable.test.tsx b/src/containers/Accounts/AccountNFTTable/test/AccountNFTTable.test.tsx similarity index 73% rename from src/containers/Accounts/AccountNFTTable/test/AccountNFTtable.test.tsx rename to src/containers/Accounts/AccountNFTTable/test/AccountNFTTable.test.tsx index 1ba9a9609..b66cccbcd 100644 --- a/src/containers/Accounts/AccountNFTTable/test/AccountNFTtable.test.tsx +++ b/src/containers/Accounts/AccountNFTTable/test/AccountNFTTable.test.tsx @@ -1,11 +1,10 @@ -import { mount } from 'enzyme' +import { render, screen, cleanup, fireEvent } from '@testing-library/react' import { QueryClientProvider } from 'react-query' import { I18nextProvider } from 'react-i18next' import { BrowserRouter } from 'react-router-dom' import { getAccountNFTs } from '../../../../rippled/lib/rippled' import { AccountNFTTable } from '../AccountNFTTable' import i18n from '../../../../i18n/testConfig' -import { EmptyMessageTableRow } from '../../../shared/EmptyMessageTableRow' import { testQueryClient } from '../../../test/QueryClient' import { flushPromises } from '../../../test/utils' import Mock = jest.Mock @@ -36,8 +35,8 @@ const data = { describe('AccountNFTTable component', () => { const TEST_ACCOUNT_ID = 'rTEST_ACCOUNT' - const createWrapper = () => - mount( + const renderComponent = () => + render( @@ -49,6 +48,7 @@ describe('AccountNFTTable component', () => { afterEach(() => { mockedGetAccountNFTs.mockReset() + cleanup() }) it('should render a table of nfts', async () => { @@ -56,13 +56,11 @@ describe('AccountNFTTable component', () => { mockedGetAccountNFTs.mockImplementation(() => Promise.resolve(data)) - const wrapper = createWrapper() + renderComponent() await flushPromises() - wrapper.update() - expect(wrapper.find('tbody tr td').length).toEqual(3) - expect(wrapper.find('.load-more-btn')).not.toExist() - wrapper.unmount() + expect(screen.getAllByRole('cell').length).toEqual(3) + expect(screen.queryByRole('button')).toBeDefined() }) it('should handle load more', async () => { @@ -75,20 +73,19 @@ describe('AccountNFTTable component', () => { }), ) - const wrapper = createWrapper() + renderComponent() await flushPromises() - wrapper.update() - const columns = wrapper.find('tbody tr td') - expect(columns.at(0).text()).toEqual( + const columns = screen.getAllByRole('cell') + expect(columns[0]).toHaveTextContent( `00000000AF460F0D7BC0F05B626E19498DF690E00C97080B0000099B00000000`, ) - expect(columns.at(1).text()).toEqual(`rGymBL8Huct6euA8jtEcLagYXpRgQKh6EC`) - expect(columns.at(2).text()).toEqual(`0`) - expect(wrapper.find('.load-more-btn')).toExist() - wrapper.find('.load-more-btn').simulate('click') + expect(columns[1]).toHaveTextContent(`rGymBL8Huct6euA8jtEcLagYXpRgQKh6EC`) + expect(columns[2]).toHaveTextContent(`0`) + expect(screen.getByRole('button')).toBeDefined() + fireEvent.click(screen.getByRole('button')) + expect(mockedGetAccountNFTs).toHaveBeenCalledTimes(2) expect(mockedGetAccountNFTs.mock.calls[1][2]).toEqual('hello') - wrapper.unmount() }) it(`should handle no results`, async () => { @@ -104,11 +101,9 @@ describe('AccountNFTTable component', () => { }), ) - const wrapper = createWrapper() + renderComponent() await flushPromises() - wrapper.update() - expect(wrapper.find(EmptyMessageTableRow)).toExist() - wrapper.unmount() + expect(screen.queryByTestId('empty-message')).toBeDefined() }) }) diff --git a/src/containers/Accounts/AccountTransactionTable/test/AccountTransactionTable.test.tsx b/src/containers/Accounts/AccountTransactionTable/test/AccountTransactionTable.test.tsx index 71c57c766..b7d5af6e8 100644 --- a/src/containers/Accounts/AccountTransactionTable/test/AccountTransactionTable.test.tsx +++ b/src/containers/Accounts/AccountTransactionTable/test/AccountTransactionTable.test.tsx @@ -1,5 +1,4 @@ -import { mount } from 'enzyme' -import { Link } from 'react-router-dom' +import { cleanup, fireEvent, render, screen } from '@testing-library/react' import i18n from '../../../../i18n/testConfig' import { AccountTransactionTable } from '../index' import TEST_TRANSACTIONS_DATA from './mockTransactions.json' @@ -19,7 +18,9 @@ describe('AccountTransactionsTable container', () => { jest.resetModules() }) - const createWrapper = ( + afterEach(cleanup) + + const renderComponent = ( getAccountTransactionsImpl: () => Promise = () => new Promise(() => {}), currencySelected: string = '', @@ -28,7 +29,7 @@ describe('AccountTransactionsTable container', () => { ;(getAccountTransactions as Mock).mockImplementation( getAccountTransactionsImpl, ) - return mount( + return render( { } it('renders static parts', () => { - const wrapper = createWrapper() - expect(wrapper.find('.transaction-table').length).toBe(1) - wrapper.unmount() + renderComponent() + expect(screen.getAllByTitle('transaction-table')).toHaveLength(1) }) it('renders loader when fetching data', () => { - const wrapper = createWrapper() - expect(wrapper.find('.loader').length).toBe(1) - wrapper.unmount() + renderComponent() + expect(screen.getAllByTitle('loader')).toHaveLength(1) }) it('renders dynamic content with transaction data', async () => { - const component = createWrapper(() => - Promise.resolve(TEST_TRANSACTIONS_DATA), - ) + renderComponent(() => Promise.resolve(TEST_TRANSACTIONS_DATA)) await flushPromises() - component.update() - expect(component.find('.col-token').length).toBe(0) - expect(component.find('.load-more-btn').length).toBe(1) - expect(component.find('.transaction-table').length).toBe(1) - expect(component.find('.transaction-li.transaction-li-header').length).toBe( - 1, - ) - expect(component.find(Link).length).toBe(60) + expect(screen.queryByTitle('col-token')).toBeNull() + expect(screen.getAllByRole('button').length).toBe(1) + expect(screen.getAllByTitle('transaction-table')).toHaveLength(1) + expect(screen.getAllByRole('link')).toHaveLength(60) - component.find('.load-more-btn').simulate('click') + const button = screen.getByRole('button') + fireEvent.click(button) expect(getAccountTransactions).toHaveBeenCalledWith( TEST_ACCOUNT_ID, undefined, @@ -75,62 +69,44 @@ describe('AccountTransactionsTable container', () => { undefined, undefined, ) - - component.unmount() }) it('renders error message when request fails', async () => { - const component = createWrapper(() => Promise.reject()) + renderComponent(() => Promise.reject()) await flushPromises() - component.update() - expect(component.find('.load-more-btn')).not.toExist() - expect(component.find('.transaction-table')).toExist() - expect(component.find('.empty-transactions-message')).toHaveText( - 'get_account_transactions_failed', - ) - expect(component.find(Link).length).toBe(0) - component.unmount() + expect(screen.queryByRole('button')).toBeNull() + expect(screen.getAllByTitle('transaction-table')).toBeDefined() + expect(screen.queryByText('get_account_transactions_failed')).toBeDefined() + expect(screen.queryAllByRole('link')).toHaveLength(0) }) it('renders try loading more message when no filtered results show but there is a marker', async () => { - const component = createWrapper( - () => Promise.resolve(TEST_TRANSACTIONS_DATA), - 'EUR', - ) + renderComponent(() => Promise.resolve(TEST_TRANSACTIONS_DATA), 'EUR') await flushPromises() - component.update() - expect(component.find('.load-more-btn')).toExist() - expect(component.find('.transaction-table')).toExist() - expect(component.find('.empty-transactions-message')).toHaveText( - 'get_account_transactions_try', - ) - expect(component.find(Link).length).toBe(0) - component.unmount() + expect(screen.getByRole('button')).toBeDefined() + expect(screen.getAllByTitle('transaction-table')).toBeDefined() + expect(screen.queryByText('get_account_transactions_try')).toBeDefined() + expect(screen.queryAllByRole('link')).toHaveLength(0) }) it('renders dynamic content with transaction data and token column', async () => { - const component = createWrapper( - () => Promise.resolve(TEST_TRANSACTIONS_DATA), - undefined, - { hasToken: true }, - ) + renderComponent(() => Promise.resolve(TEST_TRANSACTIONS_DATA), undefined, { + hasToken: true, + }) await flushPromises() - component.update() - expect(component.find('.col-token').length).toBeGreaterThan(0) - expect(component.find('.load-more-btn').length).toBe(1) - expect(component.find('.transaction-table').length).toBe(1) - expect(component.find('.transaction-li.transaction-li-header').length).toBe( - 1, - ) - expect(component.find(Link).length).toBe(60) + expect(screen.queryAllByTitle('col-token').length).toBeGreaterThan(0) + expect(screen.getAllByRole('button').length).toBe(1) + expect(screen.getAllByTitle('transaction-table').length).toBe(1) + expect(screen.getAllByRole('link')).toHaveLength(60) - component.find('.load-more-btn').simulate('click') + const button = screen.getByRole('button') + fireEvent.click(button) expect(getAccountTransactions).toHaveBeenCalledWith( TEST_ACCOUNT_ID, undefined, @@ -138,40 +114,16 @@ describe('AccountTransactionsTable container', () => { undefined, undefined, ) - - component.unmount() - }) - - it('renders error message when request fails', async () => { - const component = createWrapper(() => Promise.reject()) - - await flushPromises() - component.update() - - expect(component.find('.load-more-btn')).not.toExist() - expect(component.find('.transaction-table')).toExist() - expect(component.find('.empty-transactions-message')).toHaveText( - 'get_account_transactions_failed', - ) - expect(component.find(Link).length).toBe(0) - component.unmount() }) it('renders try loading more message when no filtered results show but there is a marker', async () => { - const component = createWrapper( - () => Promise.resolve(TEST_TRANSACTIONS_DATA), - 'EUR', - ) + renderComponent(() => Promise.resolve(TEST_TRANSACTIONS_DATA), 'EUR') await flushPromises() - component.update() - expect(component.find('.load-more-btn')).toExist() - expect(component.find('.transaction-table')).toExist() - expect(component.find('.empty-transactions-message')).toHaveText( - 'get_account_transactions_try', - ) - expect(component.find(Link).length).toBe(0) - component.unmount() + expect(screen.getByRole('button')).toBeDefined() + expect(screen.getByTitle('transaction-table')).toBeDefined() + expect(screen.queryByText('get_account_transactions_try')).toBeDefined() + expect(screen.queryAllByRole('link')).toHaveLength(0) }) }) diff --git a/src/containers/Accounts/test/index.test.tsx b/src/containers/Accounts/test/index.test.tsx index efd512f38..1cf940d91 100644 --- a/src/containers/Accounts/test/index.test.tsx +++ b/src/containers/Accounts/test/index.test.tsx @@ -1,13 +1,12 @@ -import { mount } from 'enzyme' +import { cleanup, render, screen } from '@testing-library/react' import configureMockStore from 'redux-mock-store' import thunk from 'redux-thunk' import { Provider } from 'react-redux' import { Route } from 'react-router' +import userEvent from '@testing-library/user-event' import { initialState } from '../../../rootReducer' import i18n from '../../../i18n/testConfig' import { Accounts } from '../index' -import AccountHeader from '../AccountHeader' -import { AccountTransactionTable } from '../AccountTransactionTable' import mockAccountState from './mockAccountState.json' import { QuickHarness } from '../../test/utils' import { ACCOUNT_ROUTE } from '../../App/routes' @@ -17,9 +16,9 @@ describe('Account container', () => { const middlewares = [thunk] const mockStore = configureMockStore(middlewares) - const createWrapper = (state = {}) => { + const renderComponent = (state = {}) => { const store = mockStore({ ...initialState, ...state }) - return mount( + return render( { ) } + afterEach(cleanup) + it('renders without crashing', () => { - const wrapper = createWrapper() - wrapper.unmount() + renderComponent() }) it('renders static parts', () => { @@ -46,11 +46,9 @@ describe('Account container', () => { }, } - const wrapper = createWrapper(state) - wrapper.update() - expect(wrapper.find(AccountHeader).length).toBe(1) - expect(wrapper.find(AccountTransactionTable).length).toBe(1) - wrapper.find('.balance-selector button').simulate('click') - wrapper.unmount() + renderComponent(state) + expect(screen.getByTitle('account-header')).toBeDefined() + expect(screen.getByTitle('transaction-table')).toBeDefined() + userEvent.click(screen.getByRole('button')) }) }) diff --git a/src/containers/Amendment/BarChartVoting.tsx b/src/containers/Amendment/BarChartVoting.tsx index f8d436fdb..fffe16ee3 100644 --- a/src/containers/Amendment/BarChartVoting.tsx +++ b/src/containers/Amendment/BarChartVoting.tsx @@ -91,7 +91,7 @@ export const BarChartVoting = ({ data }: Props) => { const [showTooltips, setShowTooltips] = useState(false) return ( -
+
{ const renderStatus = () => voting ? ( -
{`${t('not')} ${t('enabled')}`}
+
{`${t('not')} ${t( + 'enabled', + )}`}
) : ( -
{t('enabled')}
+
+ {t('enabled')} +
) const renderDate = (date: string | null) => diff --git a/src/containers/Amendment/Votes.tsx b/src/containers/Amendment/Votes.tsx index ca887b0c3..6a59dce9a 100644 --- a/src/containers/Amendment/Votes.tsx +++ b/src/containers/Amendment/Votes.tsx @@ -47,11 +47,11 @@ export const Votes = ({ data, validators }: VotesProps) => { label: 'yeas' | 'nays', validatorsList: Array, ) => ( -
+
{t(label)}
{validatorsList.map((validator, index) => ( -
+
{index + 1} { } else if (data?.id && validators instanceof Array) { body = ( <> -
+
{t('amendment_summary')}
@@ -124,7 +124,7 @@ export const Amendment = () => { } return ( -
+
{(isValidatorsLoading || isAmendmentLoading) && } {body}
diff --git a/src/containers/Amendment/test/Amendment.test.tsx b/src/containers/Amendment/test/Amendment.test.tsx new file mode 100644 index 000000000..ea1227e3f --- /dev/null +++ b/src/containers/Amendment/test/Amendment.test.tsx @@ -0,0 +1,141 @@ +import { cleanup, render, screen, within } from '@testing-library/react' +import moxios from 'moxios' +import { Route } from 'react-router-dom' +import { Amendment } from '..' +import i18n from '../../../i18n/testConfig' +import NetworkContext from '../../shared/NetworkContext' +import { QuickHarness, flushPromises } from '../../test/utils' +import { AMENDMENT_ROUTE } from '../../App/routes' +import votingAmendment from './mockVotingAmendment.json' +import validators from './mockValidatorsList.json' +import { NOT_FOUND } from '../../shared/utils' +import { expectSimpleRowText } from '../../shared/components/Transaction/test' + +jest.mock('usehooks-ts', () => ({ + useWindowSize: () => ({ + width: 375, + height: 600, + }), +})) + +const MOCK_IDENTIFIER = votingAmendment.amendment.id + +describe('Amendments Page container', () => { + const renderComponent = () => + render( + + + } /> + + , + ) + + const oldEnvs = process.env + + const { ResizeObserver } = window + + beforeEach(() => { + moxios.install() + process.env = { ...oldEnvs, VITE_ENVIRONMENT: 'mainnet' } + window.ResizeObserver = jest.fn().mockImplementation(() => ({ + observe: jest.fn(), + unobserve: jest.fn(), + disconnect: jest.fn(), + })) + }) + + afterEach(() => { + moxios.uninstall() + process.env = oldEnvs + window.ResizeObserver = ResizeObserver + cleanup() + }) + + it('renders without crashing', () => { + renderComponent() + expect(screen.queryByTitle('amendment-summary')).toBeDefined() + }) + + it('renders all parts for a voting amendment', async () => { + moxios.stubRequest( + `${process.env.VITE_DATA_URL}/amendment/vote/main/${MOCK_IDENTIFIER}`, + { + status: 200, + response: votingAmendment, + }, + ) + + moxios.stubRequest(`${process.env.VITE_DATA_URL}/validators/main`, { + status: 200, + response: validators, + }) + + renderComponent() + await flushPromises() + expect(screen.queryByTitle('summary')).toBeDefined() + + expectSimpleRowText(screen, 'name', 'mock-name') + expectSimpleRowText(screen, 'amendment_id', 'mock-amendment-id') + expectSimpleRowText(screen, 'introduced_in', 'v1.12.0') + expect(screen.getByTestId('introduced_in')).toHaveTextContent('v1.12.0') + expect(screen.getByText('v1.12.0')).toHaveAttribute( + 'href', + 'https://github.com/XRPLF/rippled/releases/tag/1.12.0', + ) + + expectSimpleRowText(screen, 'threshold', '3/4') + expectSimpleRowText( + screen, + 'details', + 'https://xrpl.org/known-amendments.html#mock-name', + ) + + expect(screen.getByTestId('details')).toHaveTextContent( + 'https://xrpl.org/known-amendments.html#mock-name', + ) + expect( + screen.getByText('https://xrpl.org/known-amendments.html#mock-name'), + ).toHaveAttribute( + 'href', + 'https://xrpl.org/known-amendments.html#mock-name', + ) + + expect(screen.queryByTitle('enabled')).toHaveTextContent('not enabled') + expectSimpleRowText(screen, 'yeas (all)', '2') + expectSimpleRowText(screen, 'nays (all)', '4') + expectSimpleRowText(screen, 'yeas (unl)', '1') + expectSimpleRowText(screen, 'nays (unl)', '3') + expectSimpleRowText(screen, 'eta no', 'voting') + expectSimpleRowText(screen, 'badge consensus', '25%') + + expect(screen.queryByTitle('barchart')).toBeDefined() + + const votesColumn = screen.getAllByTitle('votes-column') + expect(votesColumn).toHaveLength(2) + expect(within(votesColumn[0]).getAllByTitle('validator')).toHaveLength(2) + expect(within(votesColumn[1]).getAllByTitle('validator')).toHaveLength(4) + }) + + it('renders 404 page on no match', async () => { + moxios.stubRequest( + `${process.env.VITE_DATA_URL}/amendment/vote/main/${MOCK_IDENTIFIER}`, + { + status: NOT_FOUND, + response: votingAmendment, + }, + ) + + moxios.stubRequest(`${process.env.VITE_DATA_URL}/validators/main`, { + status: 200, + response: validators, + }) + + renderComponent() + await flushPromises() + + expect(screen.queryByTitle('no-match')).toBeDefined() + }) +}) diff --git a/src/containers/Amendment/test/amendment-summary.test.js b/src/containers/Amendment/test/amendment-summary.test.js deleted file mode 100644 index 021f46abb..000000000 --- a/src/containers/Amendment/test/amendment-summary.test.js +++ /dev/null @@ -1,230 +0,0 @@ -import { mount } from 'enzyme' -import moxios from 'moxios' -import { Route } from 'react-router-dom' -import { Amendment } from '..' -import i18n from '../../../i18n/testConfig' -import NetworkContext from '../../shared/NetworkContext' -import { QuickHarness, flushPromises } from '../../test/utils' -import { AMENDMENT_ROUTE } from '../../App/routes' -import votingAmendment from './mockVotingAmendment.json' -import validators from './mockValidatorsList.json' -import { NOT_FOUND } from '../../shared/utils' - -jest.mock('usehooks-ts', () => ({ - useWindowSize: () => ({ - width: 375, - height: 600, - }), -})) - -const MOCK_IDENTIFIER = votingAmendment.amendment.id - -describe('Amendments Page container', () => { - const createWrapper = () => - mount( - - - } /> - - , - ) - - const oldEnvs = process.env - - const { ResizeObserver } = window - - beforeEach(() => { - moxios.install() - process.env = { ...oldEnvs, VITE_ENVIRONMENT: 'mainnet' } - window.ResizeObserver = jest.fn().mockImplementation(() => ({ - observe: jest.fn(), - unobserve: jest.fn(), - disconnect: jest.fn(), - })) - }) - - afterEach(() => { - moxios.uninstall() - process.env = oldEnvs - window.ResizeObserver = ResizeObserver - }) - - it('renders without crashing', () => { - const wrapper = createWrapper() - wrapper.unmount() - }) - - it('renders all parts for a voting amendment', async (done) => { - moxios.stubRequest( - `${process.env.VITE_DATA_URL}/amendment/vote/main/${MOCK_IDENTIFIER}`, - { - status: 200, - response: votingAmendment, - }, - ) - - moxios.stubRequest(`${process.env.VITE_DATA_URL}/validators/main`, { - status: 200, - response: validators, - }) - - const wrapper = createWrapper() - await flushPromises() - await flushPromises() - await flushPromises() - wrapper.update() - expect(wrapper.find('.amendment-summary .summary .type').length).toBe(1) - - expect( - wrapper - .find('.amendment-summary .simple-body .rows .row') - .at(0) - .find('.value') - .html(), - ).toBe('
mock-name
') - - expect( - wrapper - .find('.amendment-summary .simple-body .rows .row') - .at(1) - .find('.value') - .html(), - ).toBe('
mock-amendment-id
') - - expect( - wrapper - .find('.amendment-summary .simple-body .rows .row') - .at(2) - .find('.value') - .html(), - ).toBe( - '', - ) - - expect( - wrapper - .find('.amendment-summary .simple-body .rows .row') - .at(3) - .find('.value') - .html(), - ).toBe('
3/4
') - - expect( - wrapper - .find('.amendment-summary .simple-body .rows .row') - .at(4) - .find('.value a') - .html(), - ).toBe( - 'https://xrpl.org/known-amendments.html#mock-name', - ) - - expect( - wrapper - .find('.amendment-summary .simple-body .rows .row') - .at(5) - .find('.value .badge') - .html(), - ).toBe('
not enabled
') - - expect( - wrapper - .find('.amendment-summary .simple-body .rows .row') - .at(6) - .find('.value') - .html(), - ).toBe('
2
') - - expect( - wrapper - .find('.amendment-summary .simple-body .rows .row') - .at(7) - .find('.value') - .html(), - ).toBe('
4
') - - expect( - wrapper - .find('.amendment-summary .simple-body .rows .row') - .at(8) - .find('.value') - .html(), - ).toBe('
1
') - - expect( - wrapper - .find('.amendment-summary .simple-body .rows .row') - .at(9) - .find('.value') - .html(), - ).toBe('
3
') - - expect( - wrapper - .find('.amendment-summary .simple-body .rows .row') - .at(10) - .find('.value') - .html(), - ).toBe('
voting
') - - expect( - wrapper - .find('.amendment-summary .simple-body .rows .row') - .at(11) - .find('.value') - .html(), - ).toBe('
25%
') - - expect(wrapper.find('.amendment-summary .barchart').length).toBe(1) - - expect( - wrapper.find('.amendment-summary .votes .votes-columns .votes-column') - .length, - ).toBe(2) - - expect( - wrapper - .find('.amendment-summary .votes .votes-columns .votes-column') - .at(0) - .find('.vals .row').length, - ).toBe(2) - - expect( - wrapper - .find('.amendment-summary .votes .votes-columns .votes-column') - .at(1) - .find('.vals .row').length, - ).toBe(4) - - wrapper.unmount() - done() - }) - - it('renders 404 page on no match', async (done) => { - moxios.stubRequest( - `${process.env.VITE_DATA_URL}/amendment/vote/main/${MOCK_IDENTIFIER}`, - { - status: NOT_FOUND, - response: votingAmendment, - }, - ) - - moxios.stubRequest(`${process.env.VITE_DATA_URL}/validators/main`, { - status: 200, - response: validators, - }) - - const wrapper = createWrapper() - await flushPromises() - await flushPromises() - await flushPromises() - wrapper.update() - - expect(wrapper.find('.no-match').length).toBe(1) - wrapper.unmount() - done() - }) -}) diff --git a/src/containers/Amendments/AmendmentsTable.tsx b/src/containers/Amendments/AmendmentsTable.tsx index ae2e084e2..a79343a3f 100644 --- a/src/containers/Amendments/AmendmentsTable.tsx +++ b/src/containers/Amendments/AmendmentsTable.tsx @@ -95,10 +95,13 @@ export const AmendmentsTable: FC<{ const renderAmendment = (amendment, index) => ( - {index + 1} - + + {index + 1} + + {amendment.rippled_version ? ( - {amendment.id} - + + {amendment.id} + + {renderName(amendment.name, amendment.id, amendment.deprecated)} - {getVoter(amendment.voted)} - + + {getVoter(amendment.voted)} + + {amendment.threshold ?? DEFAULT_EMPTY_VALUE} - + {amendment.consensus ?? DEFAULT_EMPTY_VALUE} - + {renderEnabled(amendment.voted === undefined)} - {renderOnTx(amendment)} + + {renderOnTx(amendment)} + ) @@ -148,5 +157,9 @@ export const AmendmentsTable: FC<{ ) : ( ) - return
{content}
+ return ( +
+ {content} +
+ ) } diff --git a/src/containers/Amendments/index.tsx b/src/containers/Amendments/index.tsx index b641737b8..6ee868f19 100644 --- a/src/containers/Amendments/index.tsx +++ b/src/containers/Amendments/index.tsx @@ -46,7 +46,7 @@ export const Amendments = () => { return (
-
+
{t('amendments')}
diff --git a/src/containers/Amendments/test/Amendments.test.tsx b/src/containers/Amendments/test/Amendments.test.tsx new file mode 100644 index 000000000..cc45ff2a4 --- /dev/null +++ b/src/containers/Amendments/test/Amendments.test.tsx @@ -0,0 +1,59 @@ +import { cleanup, render, screen } from '@testing-library/react' +import moxios from 'moxios' +import { Route } from 'react-router-dom' +import i18n from '../../../i18n/testConfig' +import { Amendments } from '../index' +import NetworkContext from '../../shared/NetworkContext' +import { QuickHarness, flushPromises } from '../../test/utils' +import { AMENDMENTS_ROUTE } from '../../App/routes' +import amendmentsRaw from './mockAmendments.json' + +jest.mock('usehooks-ts', () => ({ + useWindowSize: () => ({ + width: 375, + height: 600, + }), +})) + +describe('Amendments Page container', () => { + const renderComponent = () => + render( + + + } /> + + , + ) + + const oldEnvs = process.env + + beforeEach(() => { + moxios.install() + process.env = { ...oldEnvs, VITE_ENVIRONMENT: 'mainnet' } + }) + + afterEach(() => { + moxios.uninstall() + process.env = oldEnvs + cleanup() + }) + + it('renders without crashing', () => { + renderComponent() + }) + + it('renders all parts', async () => { + moxios.stubRequest(`${process.env.VITE_DATA_URL}/amendments/vote/main`, { + status: 200, + response: amendmentsRaw, + }) + renderComponent() + + expect(screen.queryByTitle('amendments-table')).toBeDefined() + expect(screen.queryByTitle('summary')).toHaveTextContent('amendments') + await flushPromises() + expect(screen.getAllByRole('row')).toHaveLength( + amendmentsRaw.amendments.length + 1, + ) + }) +}) diff --git a/src/containers/Amendments/test/AmendmentsTable.test.tsx b/src/containers/Amendments/test/AmendmentsTable.test.tsx new file mode 100644 index 000000000..13d3ed677 --- /dev/null +++ b/src/containers/Amendments/test/AmendmentsTable.test.tsx @@ -0,0 +1,89 @@ +import { cleanup, render, screen, within } from '@testing-library/react' +import { BrowserRouter as Router } from 'react-router-dom' +import { I18nextProvider } from 'react-i18next' +import i18n from '../../../i18n/testConfig' +import { AmendmentsTable } from '../AmendmentsTable' +import amendmentsRaw from './mockAmendments.json' + +/* eslint-disable react/jsx-props-no-spreading */ +const renderComponent = ( + props: { amendments: any } = { amendments: undefined }, +) => + render( + + + + + , + ) +afterEach(cleanup) + +describe('Amendments table', () => { + it('renders without crashing', () => { + renderComponent() + }) + + it('renders all parts', () => { + renderComponent({ amendments: amendmentsRaw.amendments }) + expect(screen.getAllByRole('row')).toHaveLength( + amendmentsRaw.amendments.length + 1, + ) + + // Test voting amendment row. + const votingRow = within(screen.getAllByTitle('amendment-row')[3]) + expect(votingRow.getByTitle('version')).toHaveTextContent('1.12.0') + expect(votingRow.getByText('1.12.0')).toHaveAttribute( + 'href', + 'https://github.com/XRPLF/rippled/releases/tag/1.12.0', + ) + + expect(votingRow.getByTitle('count')).toHaveTextContent('4') + + expect(votingRow.getByTitle('amendment-id')).toHaveTextContent( + '56B241D7A43D40354D02A9DC4C8DF5C7A1F930D92A9035C4E12291B3CA3E1C2B', + ) + + expect(votingRow.getByTitle('name')).toHaveTextContent('Clawback') + expect(votingRow.getByText('Clawback')).toHaveAttribute( + 'href', + '/amendment/56B241D7A43D40354D02A9DC4C8DF5C7A1F930D92A9035C4E12291B3CA3E1C2B', + ) + + expect(votingRow.getByTitle('voters')).toHaveTextContent('4') + + expect(votingRow.getByTitle('enabled')).toHaveTextContent('no') + + expect(votingRow.getByTitle('on_tx')).toHaveTextContent('voting') + + // Test enabled amendment row. + + const enabledRow = within(screen.getAllByTitle('amendment-row')[1]) + expect(enabledRow.getByTitle('version')).toHaveTextContent('1.10.0') + expect(enabledRow.getByText('1.10.0')).toHaveAttribute( + 'href', + 'https://github.com/XRPLF/rippled/releases/tag/1.10.0', + ) + + expect(enabledRow.getByTitle('count')).toHaveTextContent('2') + + expect(enabledRow.getByTitle('amendment-id')).toHaveTextContent( + '75A7E01C505DD5A179DFE3E000A9B6F1EDDEB55A12F95579A23E15B15DC8BE5A', + ) + + expect(enabledRow.getByTitle('name')).toHaveTextContent( + 'ImmediateOfferKilled', + ) + expect(enabledRow.getByText('ImmediateOfferKilled')).toHaveAttribute( + 'href', + '/amendment/75A7E01C505DD5A179DFE3E000A9B6F1EDDEB55A12F95579A23E15B15DC8BE5A', + ) + + expect(enabledRow.getByTitle('enabled')).toHaveTextContent('yes') + + expect(enabledRow.getByTitle('on_tx')).toHaveTextContent('8/21/2023') + expect(enabledRow.getByText('8/21/2023')).toHaveAttribute( + 'href', + '/transactions/65B8A4068B20696A866A07E5668B2AEB0451564E13B79421356FB962EC9A536B', + ) + }) +}) diff --git a/src/containers/Amendments/test/amendments.test.js b/src/containers/Amendments/test/amendments.test.js deleted file mode 100644 index d5b2d1711..000000000 --- a/src/containers/Amendments/test/amendments.test.js +++ /dev/null @@ -1,168 +0,0 @@ -import { mount } from 'enzyme' -import moxios from 'moxios' -import { Route } from 'react-router-dom' -import i18n from '../../../i18n/testConfig' -import { Amendments } from '../index' -import NetworkContext from '../../shared/NetworkContext' -import { QuickHarness } from '../../test/utils' -import { AMENDMENTS_ROUTE } from '../../App/routes' -import amendmentsRaw from './mockAmendments.json' - -jest.mock('usehooks-ts', () => ({ - useWindowSize: () => ({ - width: 375, - height: 600, - }), -})) - -describe('Amendments Page container', () => { - const createWrapper = () => - mount( - - - } /> - - , - ) - - const oldEnvs = process.env - - beforeEach(() => { - moxios.install() - process.env = { ...oldEnvs, VITE_ENVIRONMENT: 'mainnet' } - }) - - afterEach(() => { - moxios.uninstall() - process.env = oldEnvs - }) - - it('renders without crashing', () => { - const wrapper = createWrapper() - wrapper.unmount() - }) - - it('renders all parts', (done) => { - moxios.stubRequest(`${process.env.VITE_DATA_URL}/amendments/vote/main`, { - status: 200, - response: amendmentsRaw, - }) - const wrapper = createWrapper() - - expect(wrapper.find('.amendments-table').length).toBe(1) - expect(wrapper.find('.type').html()).toBe( - '
amendments
', - ) - setTimeout(() => { - wrapper.update() - expect(wrapper.find('.amendments-table table tr').length).toBe( - amendmentsRaw.amendments.length + 1, - ) - - // Test voting amendment row. - - expect( - wrapper - .find('.amendments-table table tr') - .at(2) - .find('.version') - .html(), - ).toBe( - '1.12.0', - ) - - expect( - wrapper.find('.amendments-table table tr').at(2).find('.count').html(), - ).toBe('2') - - expect( - wrapper - .find('.amendments-table table tr') - .at(2) - .find('.amendment-id') - .html(), - ).toBe( - '56B241D7A43D40354D02A9DC4C8DF5C7A1F930D92A9035C4E12291B3CA3E1C2B', - ) - - expect( - wrapper - .find('.amendments-table table tr') - .at(2) - .find('.name .name-text') - .html(), - ).toBe( - 'Clawback', - ) - - expect( - wrapper.find('.amendments-table table tr').at(2).find('.voters').html(), - ).toBe('4') - - expect( - wrapper - .find('.amendments-table table tr') - .at(2) - .find('.enabled') - .html(), - ).toBe('no') - - expect( - wrapper.find('.amendments-table table tr').at(2).find('.on_tx').html(), - ).toBe('voting') - - // Test enabled amendment row. - - expect( - wrapper - .find('.amendments-table table tr') - .at(4) - .find('.version') - .html(), - ).toBe( - '1.10.0', - ) - - expect( - wrapper.find('.amendments-table table tr').at(4).find('.count').html(), - ).toBe('4') - - expect( - wrapper - .find('.amendments-table table tr') - .at(4) - .find('.amendment-id') - .html(), - ).toBe( - '75A7E01C505DD5A179DFE3E000A9B6F1EDDEB55A12F95579A23E15B15DC8BE5A', - ) - - expect( - wrapper - .find('.amendments-table table tr') - .at(4) - .find('.name .name-text') - .html(), - ).toBe( - 'ImmediateOfferKilled', - ) - - expect( - wrapper - .find('.amendments-table table tr') - .at(4) - .find('.enabled') - .html(), - ).toBe('yes') - - expect( - wrapper.find('.amendments-table table tr').at(4).find('.on_tx').html(), - ).toBe( - '8/21/2023', - ) - - wrapper.unmount() - done() - }) - }) -}) diff --git a/src/containers/Amendments/test/amendmentsTable.test.js b/src/containers/Amendments/test/amendmentsTable.test.js deleted file mode 100644 index 4f5fc9bda..000000000 --- a/src/containers/Amendments/test/amendmentsTable.test.js +++ /dev/null @@ -1,29 +0,0 @@ -import { mount } from 'enzyme' -import { BrowserRouter as Router } from 'react-router-dom' -import { I18nextProvider } from 'react-i18next' -import i18n from '../../../i18n/testConfig' -import { AmendmentsTable } from '../AmendmentsTable' -import amendmentsRaw from './mockAmendments.json' - -/* eslint-disable react/jsx-props-no-spreading */ -const createWrapper = (props = {}) => - mount( - - - - - , - ) - -describe('Amendments table', () => { - it('renders without crashing', () => { - const wrapper = createWrapper() - wrapper.unmount() - }) - - it('renders all parts', () => { - const wrapper = createWrapper({ amendments: amendmentsRaw.amendments }) - expect(wrapper.find('tr').length).toBe(amendmentsRaw.amendments.length + 1) - wrapper.unmount() - }) -}) diff --git a/src/containers/App/App.tsx b/src/containers/App/App.tsx index 33157f1d0..284f08212 100644 --- a/src/containers/App/App.tsx +++ b/src/containers/App/App.tsx @@ -12,7 +12,7 @@ export const App: FC<{ rippledUrl: string }> = memo(
-
+
diff --git a/src/containers/App/test/App.test.jsx b/src/containers/App/test/App.test.jsx index 886d08a59..a78a49474 100644 --- a/src/containers/App/test/App.test.jsx +++ b/src/containers/App/test/App.test.jsx @@ -1,4 +1,4 @@ -import { mount } from 'enzyme' +import { cleanup, render, screen, within } from '@testing-library/react' import moxios from 'moxios' import { MemoryRouter } from 'react-router' import { I18nextProvider } from 'react-i18next' @@ -71,7 +71,7 @@ const mockGetAccountInfo = getAccountInfo describe('App container', () => { const mockStore = configureMockStore([thunk]) - const createWrapper = ( + const renderComponent = ( path = '/', localNetworks = [], accountInfoMock = () => @@ -90,7 +90,7 @@ describe('App container', () => { } const store = mockStore(initialState) - return mount( + return render( @@ -102,7 +102,6 @@ describe('App container', () => { } const oldEnvs = process.env - let wrapper beforeEach(() => { moxios.install() @@ -116,23 +115,23 @@ describe('App container', () => { }) afterEach(() => { - wrapper.unmount() process.env = oldEnvs + cleanup() }) it('renders main parts', () => { - wrapper = createWrapper() - expect(wrapper.find('.header').length).toBe(1) - expect(wrapper.find('.content').length).toBe(1) - expect(wrapper.find('.footer').length).toBe(1) + renderComponent() + expect(screen.getByRole('banner')).toBeDefined() + expect(screen.getByTitle('content')).toBeDefined() + expect(screen.getByTitle('footer')).toBeDefined() }) it('renders home', () => { - wrapper = createWrapper() + renderComponent() return new Promise((r) => setTimeout(r, 10)).then(() => { expect(document.title).toEqual('xrpl_explorer | ledgers') - expect(wrapper.find('header')).not.toHaveClassName('header-no-network') - expect(wrapper.find('.ledgers').length).toBe(1) + expect(screen.getByRole('banner')).not.toHaveClass('header-no-network') + expect(screen.getByTitle('ledgers')).toBeDefined() expect(window.dataLayer).toEqual([ { page_path: '/', @@ -145,10 +144,8 @@ describe('App container', () => { }) it('renders ledger explorer page', async () => { - wrapper = createWrapper('/ledgers') + renderComponent('/ledgers') await flushPromises() - await flushPromises() - wrapper.update() expect(document.title).toEqual('xrpl_explorer | ledgers') expect(window.dataLayer).toEqual([ @@ -162,10 +159,8 @@ describe('App container', () => { }) it('renders ledger explorer page from index.html redirect', async () => { - wrapper = createWrapper('/index.html') - await flushPromises() + renderComponent('/index.html') await flushPromises() - wrapper.update() expect(document.title).toEqual('xrpl_explorer | ledgers') expect(window.dataLayer).toEqual([ @@ -179,10 +174,8 @@ describe('App container', () => { }) it('renders ledger explorer page from index.htm redirect', async () => { - wrapper = createWrapper('/index.html') - await flushPromises() + renderComponent('/index.html') await flushPromises() - wrapper.update() expect(document.title).toEqual('xrpl_explorer | ledgers') expect(window.dataLayer).toEqual([ @@ -196,7 +189,7 @@ describe('App container', () => { }) it('renders not found page', () => { - wrapper = createWrapper('/zzz') + renderComponent('/zzz') return new Promise((r) => setTimeout(r, 10)).then(() => { expect(document.title).toEqual('xrpl_explorer | not_found_default_title') expect(window.dataLayer).toEqual([ @@ -212,10 +205,8 @@ describe('App container', () => { it('renders ledger page', async () => { const id = 12345 - wrapper = createWrapper(`/ledgers/${id}`) + renderComponent(`/ledgers/${id}`) await flushPromises() - await flushPromises() // flush ledger request - wrapper.update() expect(document.title).toEqual(`xrpl_explorer | ledger ${id}`) expect(window.dataLayer).toEqual([ @@ -231,10 +222,8 @@ describe('App container', () => { it('renders transaction page', async () => { const id = '50BB0CC6EFC4F5EF9954E654D3230D4480DC83907A843C736B28420C7F02F774' - wrapper = createWrapper(`/transactions/${id}`) + renderComponent(`/transactions/${id}`) await flushPromises() - await flushPromises() // flush transaction request - wrapper.update() expect(document.title).toEqual( `xrpl_explorer | transaction_short 50BB0CC6...`, @@ -256,7 +245,7 @@ describe('App container', () => { it('renders transaction page with invalid hash', () => { const id = '12345' - wrapper = createWrapper(`/transactions/${id}`) + renderComponent(`/transactions/${id}`) return new Promise((r) => setTimeout(r, 10)).then(() => { expect(document.title).toEqual(`xrpl_explorer | invalid_transaction_hash`) expect(window.dataLayer).toEqual([ @@ -271,12 +260,13 @@ describe('App container', () => { }) it('renders transaction page with no hash', () => { - wrapper = createWrapper(`/transactions/`) + renderComponent(`/transactions/`) return new Promise((r) => setTimeout(r, 10)).then(() => { - expect(wrapper.find('.no-match .title')).toHaveText( + const noMatch = screen.getByTitle('no-match') + expect(within(noMatch).getByTestId('title')).toHaveTextContent( 'transaction_empty_title', ) - expect(wrapper.find('.no-match .hint')).toHaveText( + expect(within(noMatch).getByTestId('hint')).toHaveTextContent( 'transaction_empty_hint', ) expect(window.dataLayer).toEqual([ @@ -290,11 +280,10 @@ describe('App container', () => { }) }) - it('renders account page for classic address', () => { + it('renders account page for classic address', async () => { const id = 'rKV8HEL3vLc6q9waTiJcewdRdSFyx67QFb' - wrapper = createWrapper(`/accounts/${id}#ssss`) - flushPromises() - flushPromises() + renderComponent(`/accounts/${id}#ssss`) + await flushPromises() return new Promise((r) => setTimeout(r, 10)).then(() => { expect(document.title).toEqual(`xrpl_explorer | rKV8HEL3vLc6...`) expect(window.dataLayer).toEqual([ @@ -310,7 +299,7 @@ describe('App container', () => { it('renders account page for malformed', () => { const id = 'rZaChweF5oXn' - wrapper = createWrapper(`/accounts/${id}#ssss`, [], () => + renderComponent(`/accounts/${id}#ssss`, [], () => Promise.reject(new Error('account not found', 404)), ) return new Promise((r) => setTimeout(r, 10)).then(() => { @@ -328,7 +317,7 @@ describe('App container', () => { it('renders account page for a deleted account', () => { const id = 'r35jYntLwkrbc3edisgavDbEdNRSKgcQE6' - wrapper = createWrapper(`/accounts/${id}#ssss`, [], () => + renderComponent(`/accounts/${id}#ssss`, [], () => Promise.reject(new Error('account not found', 404)), ) return new Promise((r) => setTimeout(r, 10)).then(() => { @@ -350,7 +339,7 @@ describe('App container', () => { it('renders account page for x-address', () => { const id = 'XVVFXHFdehYhofb7XRWeJYV6kjTEwboaHpB9S1ruYMsuXcG' - wrapper = createWrapper(`/accounts/${id}#ssss`) + renderComponent(`/accounts/${id}#ssss`) return new Promise((r) => setTimeout(r, 10)).then(() => { expect(document.title).toEqual(`xrpl_explorer | XVVFXHFdehYh...`) expect(window.dataLayer).toEqual([ @@ -370,10 +359,15 @@ describe('App container', () => { }) it('renders account page with no id', () => { - wrapper = createWrapper(`/accounts/`) + renderComponent(`/accounts/`) return new Promise((r) => setTimeout(r, 10)).then(() => { - expect(wrapper.find('.no-match .title')).toHaveText('account_empty_title') - expect(wrapper.find('.no-match .hint')).toHaveText('account_empty_hint') + const noMatch = screen.getByTitle('no-match') + expect(within(noMatch).getByTestId('title')).toHaveTextContent( + 'account_empty_title', + ) + expect(within(noMatch).getByTestId('hint')).toHaveTextContent( + 'account_empty_hint', + ) expect(window.dataLayer).toEqual([ { page_path: '/accounts/', @@ -388,7 +382,7 @@ describe('App container', () => { it('redirects legacy transactions page', () => { const id = '50BB0CC6EFC4F5EF9954E654D3230D4480DC83907A843C736B28420C7F02F774' - wrapper = createWrapper(`/#/transactions/${id}`) + renderComponent(`/#/transactions/${id}`) return new Promise((r) => setTimeout(r, 10)).then(() => { expect(document.title).toEqual( `xrpl_explorer | transaction_short 50BB0CC6...`, @@ -411,7 +405,7 @@ describe('App container', () => { it('redirects legacy account page', () => { const id = 'rKV8HEL3vLc6q9waTiJcewdRdSFyx67QFb' - wrapper = createWrapper(`/#/graph/${id}#ssss`) + renderComponent(`/#/graph/${id}#ssss`) return new Promise((r) => setTimeout(r, 10)).then(() => { expect(document.title).toEqual(`xrpl_explorer | rKV8HEL3vLc6...`) expect(window.dataLayer).toEqual([ @@ -426,7 +420,7 @@ describe('App container', () => { }) it('redirects legacy account page with no account', () => { - wrapper = createWrapper(`/#/graph/`) + renderComponent(`/#/graph/`) return new Promise((r) => setTimeout(r, 10)).then(() => { expect(document.title).toEqual(`xrpl_explorer | ledgers`) expect(window.dataLayer).toEqual([ @@ -443,10 +437,9 @@ describe('App container', () => { it('renders custom mode homepage', async () => { process.env.VITE_ENVIRONMENT = 'custom' delete process.env.VITE_P2P_RIPPLED_HOST // For custom as there is no p2p. - wrapper = createWrapper('/') + renderComponent('/') await flushPromises() - wrapper.update() - expect(wrapper.find('header')).toHaveClassName('header-no-network') + expect(screen.getByRole('banner')).toHaveClass('header-no-network') // We don't know the endpoint yet. expect(XrplClient).toHaveBeenCalledTimes(0) expect(document.title).toEqual(`xrpl_explorer`) @@ -456,11 +449,10 @@ describe('App container', () => { process.env.VITE_ENVIRONMENT = 'custom' delete process.env.VITE_P2P_RIPPLED_HOST // For custom as there is no p2p. const network = 's2.ripple.com' - wrapper = createWrapper(`/${network}/`) + renderComponent(`/${network}/`) await flushPromises() - wrapper.update() // Make sure the sockets aren't double initialized. - expect(wrapper.find('header')).not.toHaveClassName('header-no-network') + expect(screen.getByRole('banner')).not.toHaveClass('header-no-network') expect(XrplClient).toHaveBeenCalledTimes(1) expect(document.title).toEqual(`xrpl_explorer | ledgers`) }) diff --git a/src/containers/App/test/AppErrorBoundary.test.tsx b/src/containers/App/test/AppErrorBoundary.test.tsx index 1e31d8b34..da969b808 100644 --- a/src/containers/App/test/AppErrorBoundary.test.tsx +++ b/src/containers/App/test/AppErrorBoundary.test.tsx @@ -1,4 +1,4 @@ -import { mount } from 'enzyme' +import { cleanup, render } from '@testing-library/react' import { analytics } from '../../shared/analytics' import AppErrorBoundary from '../AppErrorBoundary' @@ -18,8 +18,9 @@ const ProblemChild = () => { } describe(' component', () => { + afterEach(cleanup) it('calls analytics with exception', () => { - mount( + render( , diff --git a/src/containers/CustomNetworkHome/index.tsx b/src/containers/CustomNetworkHome/index.tsx index a7fdbb062..d3e2b3245 100644 --- a/src/containers/CustomNetworkHome/index.tsx +++ b/src/containers/CustomNetworkHome/index.tsx @@ -40,7 +40,11 @@ const SidechainHome = () => { function renderCustomNetwork(network: string) { return ( -
+
{network}
@@ -51,7 +55,10 @@ const SidechainHome = () => { return ( <>
-
+
{t('custom_network')}
diff --git a/src/containers/CustomNetworkHome/test/CustomNetworkHome.test.js b/src/containers/CustomNetworkHome/test/CustomNetworkHome.test.jsx similarity index 62% rename from src/containers/CustomNetworkHome/test/CustomNetworkHome.test.js rename to src/containers/CustomNetworkHome/test/CustomNetworkHome.test.jsx index f57ae01c3..f2b3f960d 100644 --- a/src/containers/CustomNetworkHome/test/CustomNetworkHome.test.js +++ b/src/containers/CustomNetworkHome/test/CustomNetworkHome.test.jsx @@ -1,4 +1,10 @@ -import { mount } from 'enzyme' +import { + cleanup, + render, + screen, + within, + fireEvent, +} from '@testing-library/react' import configureMockStore from 'redux-mock-store' import thunk from 'redux-thunk' import { Provider } from 'react-redux' @@ -11,12 +17,11 @@ import { QuickHarness } from '../../test/utils' describe('SidechainHome page', () => { let client - let wrapper const middlewares = [thunk] const mockStore = configureMockStore(middlewares) - const createWrapper = (localNetworks = null) => { + const renderComponent = (localNetworks = null) => { localStorage.removeItem(CUSTOM_NETWORKS_STORAGE_KEY) if (localNetworks) { localStorage.setItem( @@ -26,7 +31,7 @@ describe('SidechainHome page', () => { } const store = mockStore(initialState) - return mount( + return render( @@ -41,21 +46,20 @@ describe('SidechainHome page', () => { afterEach(() => { client.close() + cleanup() }) it('renders without crashing', () => { - wrapper = createWrapper() - const pageNode = wrapper.find('.custom-network-main-page') - expect(pageNode.length).toEqual(1) - wrapper.unmount() + renderComponent() + expect(screen.queryByTitle('custom-network-main-page')).toBeDefined() }) it('renders without crashing', () => { - wrapper = createWrapper(['custom_url', 'custom_url2']) - expect(wrapper.find('.custom-network-text').length).toEqual(2) - expect(wrapper.find('.custom-network-text').at(0)).toHaveText('custom_url') - expect(wrapper.find('.custom-network-text').at(1)).toHaveText('custom_url2') - wrapper.unmount() + renderComponent(['custom_url', 'custom_url2']) + const customNetworks = screen.getAllByTitle('custom-network-name') + expect(customNetworks).toHaveLength(2) + expect(customNetworks[0]).toHaveTextContent('custom_url') + expect(customNetworks[1]).toHaveTextContent('custom_url2') }) describe('test redirects', () => { @@ -75,21 +79,23 @@ describe('SidechainHome page', () => { }) it('redirect works on `enter` in textbox', () => { - wrapper = createWrapper() - expect(wrapper.find('.custom-network-input').length).toEqual(1) - wrapper - .find('.custom-network-input') - .simulate('change', { target: { value: 'custom_url' } }) + renderComponent() + expect( + within(screen.getByTitle('custom-network-main-page')).queryByRole( + 'textbox', + ), + ).toBeDefined() + const input = within( + screen.getByTitle('custom-network-main-page'), + ).getByRole('textbox') + + // TODO: figure out how to use userEvent for this instead + fireEvent.change(input, { target: { value: 'custom_url' } }) + fireEvent.keyDown(input, { key: 'Enter', code: 'Enter', charCode: 13 }) - wrapper.update() - wrapper.find('.custom-network-input').prop('onKeyDown')({ - key: 'Enter', - currentTarget: { value: 'custom_url' }, - }) expect(mockedFunction).toBeCalledWith( `${process.env.VITE_CUSTOMNETWORK_LINK}/custom_url`, ) - wrapper.unmount() }) }) }) diff --git a/src/containers/Footer/index.jsx b/src/containers/Footer/index.tsx similarity index 93% rename from src/containers/Footer/index.jsx rename to src/containers/Footer/index.tsx index 3f74f4356..42a9cb1e4 100644 --- a/src/containers/Footer/index.jsx +++ b/src/containers/Footer/index.tsx @@ -7,7 +7,7 @@ const Footer = () => { const { t } = useTranslation() return ( -
+
Learn
@@ -62,7 +62,7 @@ const Footer = () => {
- + {t('explorer')} @@ -71,7 +71,7 @@ const Footer = () => {
-
+
©  { - const createWrapper = () => - mount( + afterEach(cleanup) + + const renderComponent = () => + render(
, ) it('renders without crashing', () => { - const wrapper = createWrapper() - wrapper.unmount() + renderComponent() }) it('renders all parts', () => { - const wrapper = createWrapper() - expect(wrapper.find('.logo').length).toEqual(1) - expect(wrapper.find('.copyright').length).toEqual(1) - expect(wrapper.find('.footer-link').length).toEqual(12) - expect(wrapper.find('.footer-section-header').length).toEqual(3) - - wrapper.unmount() + renderComponent() + expect(screen.queryAllByTitle('logo')).toHaveLength(1) + expect(screen.queryAllByTitle('copyright')).toHaveLength(1) + expect(screen.getAllByRole('link')).toHaveLength(13) + expect( + screen + .getAllByRole('link') + .filter((element) => element.className.includes('footer-link')), + ).toHaveLength(12) + expect(screen.queryByText('Learn')).toBeDefined() + expect(screen.queryByText('Build')).toBeDefined() + expect(screen.queryByText('Contribute')).toBeDefined() }) }) diff --git a/src/containers/Header/LanguagePicker/test/LanguagePicker.test.tsx b/src/containers/Header/LanguagePicker/test/LanguagePicker.test.tsx index 2b84c6637..0723774b4 100644 --- a/src/containers/Header/LanguagePicker/test/LanguagePicker.test.tsx +++ b/src/containers/Header/LanguagePicker/test/LanguagePicker.test.tsx @@ -1,22 +1,22 @@ -import { mount } from 'enzyme' +import { cleanup, fireEvent, render, screen } from '@testing-library/react' import i18next from 'i18next' import { I18nextProvider } from 'react-i18next' import { LanguagePicker } from '../LanguagePicker' import testConfigEnglish from '../../../../i18n/testConfigEnglish' describe('LanguagePicker component', () => { + afterEach(cleanup) it('should switch language', () => { - const wrapper = mount( + render( , ) - wrapper.find('.dropdown-toggle').simulate('click') - wrapper.find('.dropdown-item.language-picker-ja-JP').simulate('click') + // TODO: replace with userEvent + fireEvent.click(screen.getByRole('button')) + fireEvent.click(screen.getByText('日本語')) expect(i18next.language).toEqual('ja-JP') - - wrapper.unmount() }) }) diff --git a/src/containers/Header/NavigationMenu/NavigationMenu.tsx b/src/containers/Header/NavigationMenu/NavigationMenu.tsx index adb54421f..93897eaa8 100644 --- a/src/containers/Header/NavigationMenu/NavigationMenu.tsx +++ b/src/containers/Header/NavigationMenu/NavigationMenu.tsx @@ -58,7 +58,7 @@ export const NavigationMenu = ({ return (
diff --git a/src/containers/shared/components/Dropdown/DropdownItem.tsx b/src/containers/shared/components/Dropdown/DropdownItem.tsx index 2e79bee8a..debbf1170 100644 --- a/src/containers/shared/components/Dropdown/DropdownItem.tsx +++ b/src/containers/shared/components/Dropdown/DropdownItem.tsx @@ -18,6 +18,7 @@ export const DropdownItem = ({ return ( { @@ -9,6 +9,8 @@ describe('Dropdown', () => { document.body.appendChild(sandbox) }) + afterEach(cleanup) + afterAll(() => { if (sandbox) { document.body.removeChild(sandbox) @@ -18,42 +20,43 @@ describe('Dropdown', () => { describe('prop: title', () => { it('renders when it is jsx', () => { const title = Woo - const wrapper = mount(Menu Contents) - expect(wrapper.find('.title-component')).toExist() - expect(wrapper.find('.title-component')).toHaveText('Woo') - wrapper.unmount() + render(Menu Contents) + + const button = screen.getByRole('button') + expect(button).toHaveTextContent('Woo') }) it('renders when it is a string', () => { const title = 'Woo' - const wrapper = mount(Menu Contents) - expect(wrapper.find('.dropdown-toggle')).toIncludeText(title) - wrapper.unmount() + render(Menu Contents) + + const button = screen.getByRole('button') + expect(button).toHaveTextContent('Woo') }) }) + describe(`prop: className`, () => { it('renders with custom className', () => { - const wrapper = mount( + render( Menu Contents , ) - expect(wrapper.find('.dropdown')).toHaveClassName('dropdown-custom') - wrapper.unmount() + expect(screen.getByTestId('dropdown')).toHaveClass('dropdown-custom') }) }) it('shows menu when clicking toggle', () => { - const wrapper = mount(Menu Contents) - expect(wrapper.find('.dropdown')).not.toHaveClassName('dropdown-expanded') - wrapper.find('.dropdown-toggle').simulate('click') - expect(wrapper.find('.dropdown')).toHaveClassName('dropdown-expanded') - wrapper.find('.dropdown-toggle').simulate('click') - expect(wrapper.find('.dropdown')).not.toHaveClassName('dropdown-expanded') - wrapper.unmount() + render(Menu Contents) + expect(screen.getByTestId('dropdown')).not.toHaveClass('dropdown-expanded') + const button = screen.getByRole('button') + fireEvent.click(button) + expect(screen.getByTestId('dropdown')).toHaveClass('dropdown-expanded') + fireEvent.click(button) + expect(screen.getByTestId('dropdown')).not.toHaveClass('dropdown-expanded') }) it('hides menu when clicking toggle outside the component', () => { - const wrapper = mount( + render(
Menu Contents
@@ -62,30 +65,28 @@ describe('Dropdown', () => { Outside
, - { attachTo: sandbox }, + // { attachTo: sandbox }, ) - expect(wrapper.find('.dropdown')).not.toHaveClassName('dropdown-expanded') - wrapper.find('.dropdown-toggle').simulate('click') - expect(wrapper.find('.dropdown')).toHaveClassName('dropdown-expanded') - wrapper.find('.child').getDOMNode().click() // simulate does not bubble - wrapper.update() - expect(wrapper.find('.dropdown')).toHaveClassName('dropdown-expanded') - wrapper.find('.outside').getDOMNode().click() // simulate does not bubble - wrapper.update() - expect(wrapper.find('.dropdown')).not.toHaveClassName('dropdown-expanded') - wrapper.unmount() + expect(screen.getByTestId('dropdown')).not.toHaveClass('dropdown-expanded') + + const child = screen.getAllByRole('button')[0] + const outside = screen.getAllByRole('button')[1] + fireEvent.click(child) + expect(screen.getByTestId('dropdown')).toHaveClass('dropdown-expanded') + fireEvent.click(outside) + expect(screen.getByTestId('dropdown')).not.toHaveClass('dropdown-expanded') }) it('adds aria roles', () => { - const wrapper = mount(Menu Contents) - const toggle = wrapper.find('.dropdown-toggle') - const menu = wrapper.find('.dropdown-menu') - expect(toggle).toHaveProp('aria-haspopup', 'true') - expect(toggle).toHaveProp('tabIndex', 0) - expect(menu).toHaveProp('role', 'menu') - expect(menu).toHaveProp('tabIndex', 0) - toggle.simulate('click') - expect(wrapper.find('.dropdown-toggle')).toHaveProp('aria-expanded', true) - expect(wrapper.find('.dropdown-menu')).toHaveProp('aria-hidden', false) + render(Menu Contents) + const toggle = screen.getByRole('button') + const menu = screen.getByTestId('dropdown-menu') + expect(toggle).toHaveAttribute('aria-haspopup', 'true') + expect(toggle).toHaveAttribute('tabIndex', '0') + expect(menu).toHaveAttribute('role', 'menu') + expect(menu).toHaveAttribute('tabIndex', '0') + fireEvent.click(toggle) + expect(toggle).toHaveAttribute('aria-expanded', 'true') + expect(menu).toHaveAttribute('aria-hidden', 'false') }) }) diff --git a/src/containers/shared/components/Dropdown/test/DropdownItem.test.tsx b/src/containers/shared/components/Dropdown/test/DropdownItem.test.tsx index 66c3437df..890cdf0e4 100644 --- a/src/containers/shared/components/Dropdown/test/DropdownItem.test.tsx +++ b/src/containers/shared/components/Dropdown/test/DropdownItem.test.tsx @@ -1,65 +1,67 @@ -import { mount } from 'enzyme' +import { cleanup, fireEvent, render, screen } from '@testing-library/react' import { DropdownItem } from '../DropdownItem' import createSpy = jasmine.createSpy describe('DropdownItem', () => { + afterEach(cleanup) describe(`prop: className`, () => { it('renders with custom className', () => { - const wrapper = mount( - Hello, - ) - expect(wrapper.find('.dropdown-item')).toHaveClassName('custom') + render(Hello) + const item = screen.getByText('Hello') + expect(item).toHaveClass('custom') }) }) describe('prop: handler', () => { - let wrapper + let item const handler = createSpy('handler') beforeEach(() => { - wrapper = mount(Hello) + render(Hello) + item = screen.getByText('Hello') }) it('renders as an anchor tag', () => { - expect(wrapper.find('.dropdown-item')).toHaveDisplayName('a') + expect(item.tagName).toBe('A') }) it('executes handler on click', () => { - wrapper.find('.dropdown-item').simulate('click') + fireEvent.click(item) expect(handler).toHaveBeenCalled() }) it('executes handler on keyup', () => { - wrapper.find('.dropdown-item').simulate('click') + fireEvent.click(item) expect(handler).toHaveBeenCalled() }) }) describe('prop: href', () => { - let wrapper + let item beforeEach(() => { - wrapper = mount( - Hello, - ) + render(Hello) + item = screen.getByText('Hello') }) it('renders as an anchor tag', () => { - expect(wrapper.find('.dropdown-item')).toHaveDisplayName('a') + expect(item.tagName).toBe('A') }) it('renders href attribute on anchor', () => { - expect(wrapper.find('.dropdown-item')).toHaveProp('href') + expect(item.tagName).toBe('A') }) }) it('renders as div without handler or href', () => { - const wrapper = mount(Hello) - expect(wrapper.find('.dropdown-item')).toHaveDisplayName('div') + render(Hello) + const item = screen.getByText('Hello') + expect(item.tagName).toBe('DIV') }) it('adds aria roles', () => { - const wrapper = mount(Hello) - expect(wrapper.find('.dropdown-item')).toHaveProp('role', 'menuitem') + render(Hello) + const item = screen.getByText('Hello') + expect(item).toHaveAttribute('role', 'menuitem') }) }) diff --git a/src/containers/shared/components/Loader.tsx b/src/containers/shared/components/Loader.tsx index 51c9a6aaf..0a73253a3 100644 --- a/src/containers/shared/components/Loader.tsx +++ b/src/containers/shared/components/Loader.tsx @@ -6,8 +6,8 @@ import '../css/loader.scss' export const Loader: FC<{ className?: string }> = ({ className }) => { const { t } = useTranslation() return ( -
- {t('loading')} +
+ {t('loading')}
) } diff --git a/src/containers/shared/components/Notification/index.tsx b/src/containers/shared/components/Notification/index.tsx index 5ebc5ec92..fdece5909 100755 --- a/src/containers/shared/components/Notification/index.tsx +++ b/src/containers/shared/components/Notification/index.tsx @@ -43,7 +43,7 @@ export const Notification = ({ ' ', ) return !dismissed ? ( -
+
{message} {action}
diff --git a/src/containers/shared/components/Notification/tests/Notification.test.tsx b/src/containers/shared/components/Notification/tests/Notification.test.tsx new file mode 100755 index 000000000..fd07556af --- /dev/null +++ b/src/containers/shared/components/Notification/tests/Notification.test.tsx @@ -0,0 +1,100 @@ +import { cleanup, render, screen } from '@testing-library/react' +import { Notification } from '../index' + +/* eslint-disable react/jsx-props-no-spreading */ +const VALID_USAGES = [ + 'default', + 'success', + 'warning', + 'danger', + 'dark', + 'light', + 'dark50', +] +const notificationLevels = ['primary', 'secondary', 'ghost'] +const message = 'A catchy message' + +const renderComponent = (props) => render() + +describe('', () => { + afterEach(cleanup) + + it('renders without crashing', () => { + render() + }) + + it('should render with custom className', () => { + const className = 'test-class' + renderComponent({ + message, + className, + }) + expect(screen.getByText(message).parentElement).toHaveClass(className) + }) + + it('should render the action button', () => { + const action =