From 362e360f405fb255c100954e75de34e4f57e4c44 Mon Sep 17 00:00:00 2001 From: yougotwill Date: Mon, 22 Jul 2024 12:01:45 +1000 Subject: [PATCH] fix: replace chai with react-test-renderer for unit testing components rewrite avatar placeholder test --- package.json | 5 +- ts/test/components/AvatarPlaceHolder_test.tsx | 102 +++++------------- ts/test/components/renderComponent.tsx | 43 ++++++-- ts/test/test-utils/utils/components.ts | 25 ----- yarn.lock | 85 +++++++++++---- 5 files changed, 134 insertions(+), 126 deletions(-) delete mode 100644 ts/test/test-utils/utils/components.ts diff --git a/package.json b/package.json index c5fd3ff69..9e6b5ae35 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "@reduxjs/toolkit": "1.9.7", "@signalapp/better-sqlite3": "^8.4.3", "@types/react-mentions": "^4.1.8", + "@types/react-test-renderer": "^18.3.0", "abort-controller": "3.0.0", "auto-bind": "^4.0.0", "backbone": "1.3.3", @@ -120,6 +121,7 @@ "react-mentions": "^4.4.9", "react-qrcode-logo": "^3.0.0", "react-redux": "8.1.3", + "react-test-renderer": "^18.3.1", "react-toastify": "^6.0.9", "react-use": "^17.5.0", "react-virtualized": "^9.22.4", @@ -140,6 +142,7 @@ "@commitlint/config-conventional": "^17.7.0", "@commitlint/types": "^17.4.4", "@electron/notarize": "^2.1.0", + "@testing-library/jest-dom": "^6.4.6", "@testing-library/react": "^15.0.7", "@testing-library/user-event": "^14.5.2", "@types/backbone": "1.4.2", @@ -149,7 +152,6 @@ "@types/bytebuffer": "^5.0.41", "@types/chai": "4.2.18", "@types/chai-as-promised": "^7.1.2", - "@types/chai-dom": "^1.11.3", "@types/classnames": "2.2.3", "@types/config": "0.0.34", "@types/dompurify": "^2.0.0", @@ -179,7 +181,6 @@ "chai": "^4.3.4", "chai-as-promised": "^7.1.1", "chai-bytes": "^0.1.2", - "chai-dom": "^1.12.0", "cross-env": "^6.0.3", "css-loader": "^6.7.2", "dmg-builder": "23.6.0", diff --git a/ts/test/components/AvatarPlaceHolder_test.tsx b/ts/test/components/AvatarPlaceHolder_test.tsx index 7a012fd58..388bee5a3 100644 --- a/ts/test/components/AvatarPlaceHolder_test.tsx +++ b/ts/test/components/AvatarPlaceHolder_test.tsx @@ -1,16 +1,12 @@ /* eslint-disable import/no-extraneous-dependencies */ -import { cleanup, waitFor } from '@testing-library/react'; -import chai, { expect } from 'chai'; -import chaiDom from 'chai-dom'; +import { cleanup } from '@testing-library/react'; +import { expect } from 'chai'; import Sinon from 'sinon'; import { AvatarSize } from '../../components/avatar/Avatar'; import { AvatarPlaceHolder } from '../../components/avatar/AvatarPlaceHolder/AvatarPlaceHolder'; import { MemberAvatarPlaceHolder } from '../../components/icon/MemberAvatarPlaceHolder'; -import { COLORS } from '../../themes/constants/colors'; import { TestUtils } from '../test-utils'; -import { renderComponent } from './renderComponent'; - -chai.use(chaiDom); +import { areResultsEqual, findByDataTestId, renderComponent } from './renderComponent'; describe('AvatarPlaceHolder', () => { const pubkey = TestUtils.generateFakePubKeyStr(); @@ -35,16 +31,11 @@ describe('AvatarPlaceHolder', () => { /> ); - // calculating the hash and initials needs to be done first - await waitFor(() => { - result.getByText('HW'); - }); - - const el = result.getByTestId('avatar-placeholder'); - expect(el.outerHTML, 'should not be null').to.not.equal(null); - expect(el.outerHTML, 'should not be undefined').to.not.equal(undefined); - expect(el.outerHTML, 'should not be an empty string').to.not.equal(''); - expect(el.tagName, 'should be an svg').to.equal('svg'); + const el = findByDataTestId(result, 'avatar-placeholder'); + expect(el, 'should not be null').to.not.equal(null); + expect(el, 'should not be undefined').to.not.equal(undefined); + expect(el.children, 'should not be an empty string').to.not.equal(''); + expect(el.type, 'should be an svg').to.equal('svg'); result.unmount(); }); it('should render the MemberAvatarPlaceholder if we are loading or there is no hash', async () => { @@ -56,71 +47,32 @@ describe('AvatarPlaceHolder', () => { dataTestId="avatar-placeholder" /> ); - const el = result.getByTestId('avatar-placeholder'); const result2 = renderComponent( ); - const el2 = result2.getByTestId('member-avatar-placeholder'); - // The data test ids are different so we don't use the outerHTML for comparison - expect(el.innerHTML).to.equal(el2.innerHTML); + expect(areResultsEqual(result, result2, true)).to.equal(true); result.unmount(); }); - it('should render the background color using the primary colors in the correct order', async () => { - const testPubkeys = [ - '0541214ef26572066f0535140b1d6d021218299321c6001e2cdcaaa8cd5c9382fc', // green - '0541214ef26572066f0535140b1d6d021218299321c6001e2cdcaaa8cd5c9382fa', // blue - '0541214ef26572066f0535140b1d6d021218299321c6001e2cdcaaa8cd5c9382fd', // yellow - '0541214ef26572066f0535140b1d6d021218299321c6001e2cdcaaa8cd5c9382ff', // pink - '0541214ef26572066f0535140b1d6d021218299321c6001e2cdcaaa8cd5c9382ed', // purple - '0541214ef26572066f0535140b1d6d021218299321c6001e2cdcaaa8cd5c9382f9', // orange - '0541214ef26572066f0535140b1d6d021218299321c6001e2cdcaaa8cd5c9382eb', // red - ]; - - // NOTE we can trust the order of Object.keys and Object.values to be correct since our typescript build target is 'esnext' - const primaryColorKeys = Object.keys(COLORS.PRIMARY); - const primaryColorValues = Object.values(COLORS.PRIMARY); - - async function testBackgroundColor(testPubkey: string, expectedColorValue: string) { - const result = renderComponent( - - ); - - // calculating the hash and initials needs to be done first - await waitFor(() => { - result.getByText('HW'); - }); - - const el = result.getByTestId('avatar-placeholder'); - const circle = el.querySelector('circle'); - const circleColor = circle?.getAttribute('fill'); - expect(circleColor, 'background color should not be null').to.not.equal(null); - expect(circleColor, 'background color should not be undefined').to.not.equal(undefined); - expect(circleColor, 'background color should not be an empty string').to.not.equal(''); - expect( - primaryColorValues.includes(circleColor!), - 'background color should be in COLORS.PRIMARY' - ).to.equal(true); - expect( - circleColor, - `background color should be ${primaryColorKeys[primaryColorValues.indexOf(expectedColorValue)]} (${expectedColorValue}) and not ${primaryColorKeys[primaryColorValues.indexOf(circleColor!)]} (${circleColor}) for testPubkey ${testPubkeys.indexOf(testPubkey)} (${testPubkey})` - ).to.equal(expectedColorValue); - result.unmount(); - } + it('should render the background using a color from our theme', async () => { + const testPubkey = TestUtils.generateFakePubKeyStr(); + const result = renderComponent( + // NOTE we test the pubkey to color generation and ordering with appium. Since we can't access the value of a css variable in the test + + ); - // NOTE this is the standard order of background colors for avatars on each platform - await testBackgroundColor(testPubkeys[0], COLORS.PRIMARY.GREEN); - await testBackgroundColor(testPubkeys[1], COLORS.PRIMARY.BLUE); - await testBackgroundColor(testPubkeys[2], COLORS.PRIMARY.YELLOW); - await testBackgroundColor(testPubkeys[3], COLORS.PRIMARY.PINK); - await testBackgroundColor(testPubkeys[4], COLORS.PRIMARY.PURPLE); - await testBackgroundColor(testPubkeys[5], COLORS.PRIMARY.ORANGE); - await testBackgroundColor(testPubkeys[6], COLORS.PRIMARY.RED); + const el = findByDataTestId(result, 'avatar-placeholder'); + const circle = el.findByType('circle'); + const colorVariable = circle.props.fill; + expect(colorVariable, 'should have a background color if var(--primary-color)').to.equal( + 'var(--primary-color)' + ); + result.unmount(); }); }); diff --git a/ts/test/components/renderComponent.tsx b/ts/test/components/renderComponent.tsx index 35ce3a12d..c11ce694c 100644 --- a/ts/test/components/renderComponent.tsx +++ b/ts/test/components/renderComponent.tsx @@ -1,9 +1,9 @@ -/* eslint-disable import/no-extraneous-dependencies */ -import { render, RenderOptions } from '@testing-library/react'; import { AnimatePresence, MotionGlobalConfig } from 'framer-motion'; +import { isArray, isEqual, unset } from 'lodash'; import { ReactElement, ReactNode } from 'react'; -import { SessionTheme } from '../../themes/SessionTheme'; import { ErrorBoundary } from 'react-error-boundary'; +import TestRenderer from 'react-test-renderer'; +import { SessionTheme } from '../../themes/SessionTheme'; const Providers = ({ children }: { children: ReactNode }) => { MotionGlobalConfig.skipAnimations = false; @@ -21,7 +21,38 @@ const Providers = ({ children }: { children: ReactNode }) => { ); }; -const renderComponent = (ui: ReactElement, options?: Omit) => - render(ui, { wrapper: Providers, ...options }); +function renderComponent(children: ReactElement): TestRenderer.ReactTestRenderer { + return TestRenderer.create({children}); +} + +function getComponentTree( + result: TestRenderer.ReactTestRenderer +): Array { + const trees = result.toTree(); + return !trees ? [] : isArray(trees) ? trees : [trees]; +} + +function findByDataTestId( + renderResult: TestRenderer.ReactTestRenderer, + dataTestId: string +): TestRenderer.ReactTestInstance { + return renderResult.root.findByProps({ 'data-testid': dataTestId }); +} + +function areResultsEqual( + renderResult: TestRenderer.ReactTestRenderer, + renderResult2: TestRenderer.ReactTestRenderer, + ignoreDataTestIds?: boolean +): boolean { + if (ignoreDataTestIds) { + const obj = renderResult.toJSON(); + const obj2 = renderResult2.toJSON(); + unset(obj, "props['data-testid']"); + unset(obj2, "props['data-testid']"); + return isEqual(obj, obj2); + } + + return isEqual(renderResult.toJSON(), renderResult2.toJSON()); +} -export { renderComponent }; +export { areResultsEqual, findByDataTestId, getComponentTree, renderComponent }; diff --git a/ts/test/test-utils/utils/components.ts b/ts/test/test-utils/utils/components.ts deleted file mode 100644 index d912691bf..000000000 --- a/ts/test/test-utils/utils/components.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { RenderResult, prettyDOM } from '@testing-library/react'; -import { enableLogRedirect } from './stubbing'; - -const printHTMLElement = async (element: HTMLElement, name?: string) => { - if (!window.log || !enableLogRedirect) { - throw Error( - 'window.log is not defined. Have you turned on enableLogRedirect / called stubWindowLog() ?' - ); - } - - return window.log.debug(`\nHTML Element${name ? ` (${name})` : ''}:\n${prettyDOM(element)}\n`); -}; -const printRenderResult = async (result: RenderResult, name?: string) => { - if (!window.log || !enableLogRedirect) { - throw Error( - 'window.log is not defined. Have you turned on enableLogRedirect / called stubWindowLog() ?' - ); - } - - return window.log.debug( - `\nRender Result${name ? ` (${name})` : ''}:\n${prettyDOM(result.baseElement)}\n` - ); -}; - -export { printHTMLElement, printRenderResult }; diff --git a/yarn.lock b/yarn.lock index 18b2c91f0..53144a6d3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,6 +7,11 @@ resolved "https://registry.yarnpkg.com/7zip-bin/-/7zip-bin-5.1.1.tgz#9274ec7460652f9c632c59addf24efb1684ef876" integrity sha512-sAP4LldeWNz0lNzmTird3uWfFDWWTeg6V/MsmyyLR9X1idwKBWIgt/ZvinqQldJm3LecKEs1emkbquO6PCiLVQ== +"@adobe/css-tools@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.4.0.tgz#728c484f4e10df03d5a3acd0d8adcbbebff8ad63" + integrity sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ== + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" @@ -583,6 +588,20 @@ lz-string "^1.5.0" pretty-format "^27.0.2" +"@testing-library/jest-dom@^6.4.6": + version "6.4.6" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.4.6.tgz#ec1df8108651bed5475534955565bed88c6732ce" + integrity sha512-8qpnGVincVDLEcQXWaHOf6zmlbwTKc6Us6PPu4CRnPXCzo2OGBS5cwgMMOWdxDpEz1mkbvXHpEy99M5Yvt682w== + dependencies: + "@adobe/css-tools" "^4.4.0" + "@babel/runtime" "^7.9.2" + aria-query "^5.0.0" + chalk "^3.0.0" + css.escape "^1.5.1" + dom-accessibility-api "^0.6.3" + lodash "^4.17.21" + redent "^3.0.0" + "@testing-library/react@^15.0.7": version "15.0.7" resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-15.0.7.tgz#ff733ce0893c875cb5a47672e8e772897128f4ae" @@ -679,13 +698,6 @@ dependencies: "@types/chai" "*" -"@types/chai-dom@^1.11.3": - version "1.11.3" - resolved "https://registry.yarnpkg.com/@types/chai-dom/-/chai-dom-1.11.3.tgz#1659ace2698cdcd9ed8b2c007876f53e37d9cc89" - integrity sha512-EUEZI7uID4ewzxnU7DJXtyvykhQuwe+etJ1wwOiJyQRTH/ifMWKX+ghiXkxCUvNJ6IQDodf0JXhuP6zZcy2qXQ== - dependencies: - "@types/chai" "*" - "@types/chai@*": version "4.3.16" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.16.tgz#b1572967f0b8b60bf3f87fe1d854a5604ea70c82" @@ -980,6 +992,13 @@ hoist-non-react-statics "^3.3.0" redux "^4.0.0" +"@types/react-test-renderer@^18.3.0": + version "18.3.0" + resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-18.3.0.tgz#839502eae70058a4ae161f63385a8e7929cef4c0" + integrity sha512-HW4MuEYxfDbOHQsVlY/XtOvNHftCVEPhJF2pQXXwcUiUF+Oyb0usgp48HSgpK5rt8m9KZb22yqOeZm+rrVG8gw== + dependencies: + "@types/react" "*" + "@types/react-virtualized@^9.21.30": version "9.21.30" resolved "https://registry.yarnpkg.com/@types/react-virtualized/-/react-virtualized-9.21.30.tgz#ba39821bcb2487512a8a2cdd9fbdb5e6fc87fedb" @@ -1606,7 +1625,7 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -aria-query@5.3.0: +aria-query@5.3.0, aria-query@^5.0.0: version "5.3.0" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== @@ -2090,11 +2109,6 @@ chai-bytes@^0.1.2: resolved "https://registry.yarnpkg.com/chai-bytes/-/chai-bytes-0.1.2.tgz#c297e81d47eb3106af0676ded5bb5e0c9f981db3" integrity sha512-0ol6oJS0y1ozj6AZK8n1pyv1/G+l44nqUJygAkK1UrYl+IOGie5vcrEdrAlwmLYGIA9NVvtHWosPYwWWIXf/XA== -chai-dom@^1.12.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/chai-dom/-/chai-dom-1.12.0.tgz#cfa4023ddfe2de93c78670eafbe2dd36902c9131" - integrity sha512-pLP8h6IBR8z1AdeQ+EMcJ7dXPdsax/1Q7gdGZjsnAmSBl3/gItQUYSCo32br1qOy4SlcBjvqId7ilAf3uJ2K1w== - chai@^4.3.4: version "4.4.1" resolved "https://registry.yarnpkg.com/chai/-/chai-4.4.1.tgz#3603fa6eba35425b0f2ac91a009fe924106e50d1" @@ -2122,6 +2136,14 @@ chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" @@ -2547,6 +2569,11 @@ css-tree@^1.1.2: mdn-data "2.0.14" source-map "^0.6.1" +css.escape@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== + cssesc@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" @@ -2825,6 +2852,11 @@ dom-accessibility-api@^0.5.9: resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== +dom-accessibility-api@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz#993e925cc1d73f2c662e7d75dd5a5445259a8fd8" + integrity sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w== + dom-helpers@^5.0.1, dom-helpers@^5.1.3: version "5.2.1" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" @@ -6208,6 +6240,11 @@ react-intersection-observer@^9.7.0: resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-9.10.3.tgz#70d21ad3c3719ea4fb4eb5a543b9755d31de3b8d" integrity sha512-9NYfKwPZRovB6QJee7fDg0zz/SyYrqXtn5xTZU0vwLtLVBtfu9aZt1pVmr825REE49VPDZ7Lm5SNHjJBOTZHpA== +"react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.0.0, react-is@^18.2.0, react-is@^18.3.1: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -6218,11 +6255,6 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-is@^18.0.0, react-is@^18.2.0: - version "18.3.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" - integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== - react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" @@ -6258,6 +6290,23 @@ react-redux@8.1.3: react-is "^18.0.0" use-sync-external-store "^1.0.0" +react-shallow-renderer@^16.15.0: + version "16.15.0" + resolved "https://registry.yarnpkg.com/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz#48fb2cf9b23d23cde96708fe5273a7d3446f4457" + integrity sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA== + dependencies: + object-assign "^4.1.1" + react-is "^16.12.0 || ^17.0.0 || ^18.0.0" + +react-test-renderer@^18.3.1: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-18.3.1.tgz#e693608a1f96283400d4a3afead6893f958b80b4" + integrity sha512-KkAgygexHUkQqtvvx/otwxtuFu5cVjfzTCtjXLH9boS19/Nbtg84zS7wIQn39G8IlrhThBpQsMKkq5ZHZIYFXA== + dependencies: + react-is "^18.3.1" + react-shallow-renderer "^16.15.0" + scheduler "^0.23.2" + react-toastify@^6.0.9: version "6.2.0" resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-6.2.0.tgz#f2d76747c70b9de91f71f253d9feae6b53dc836c"