From bedf98245bf6c14d0791f878cbe5754b3d75cd49 Mon Sep 17 00:00:00 2001 From: Kirill Date: Mon, 25 May 2026 18:14:27 +0500 Subject: [PATCH] test: add AddToCartButton and ToggleCartIcon integration tests, fix timer cleanup --- .../ui/__tests__/AddToCartButton.test.tsx | 38 ++++++++++ .../ui/__tests__/ToggleCartIcon.test.tsx | 69 ++++++++++++++++++ .../shared/ui/__tests__/CartSnackbar.test.tsx | 7 +- server/prisma/prisma/dev.db | Bin 352256 -> 352256 bytes 4 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 client/src/features/cart/add-to-cart/ui/__tests__/AddToCartButton.test.tsx create mode 100644 client/src/features/cart/toggle-cart-icon/ui/__tests__/ToggleCartIcon.test.tsx diff --git a/client/src/features/cart/add-to-cart/ui/__tests__/AddToCartButton.test.tsx b/client/src/features/cart/add-to-cart/ui/__tests__/AddToCartButton.test.tsx new file mode 100644 index 0000000..5d95186 --- /dev/null +++ b/client/src/features/cart/add-to-cart/ui/__tests__/AddToCartButton.test.tsx @@ -0,0 +1,38 @@ +import { render, screen, fireEvent } from '@testing-library/react' +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import * as notifications from '@/shared/model/cart-notifications' +import { AddToCartButton } from '../AddToCartButton' + +vi.mock('@/entities/cart/api/cart-api', () => ({ + addToCart: vi.fn(() => Promise.resolve()), +})) + +vi.mock('effector-react', async () => { + const actual = await vi.importActual('effector-react') + return { ...actual, useUnit: () => ({ id: '1', email: 'test@test.com' }) } +}) + +describe('AddToCartButton', () => { + const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } }) + + beforeEach(() => { + vi.clearAllMocks() + qc.clear() + }) + + it('calls cartAdded after successful add', async () => { + const spy = vi.spyOn(notifications, 'cartAdded') + render( + + + , + ) + + fireEvent.click(screen.getByRole('button', { name: /в корзину/i })) + + await vi.waitFor(() => { + expect(spy).toHaveBeenCalled() + }) + }) +}) diff --git a/client/src/features/cart/toggle-cart-icon/ui/__tests__/ToggleCartIcon.test.tsx b/client/src/features/cart/toggle-cart-icon/ui/__tests__/ToggleCartIcon.test.tsx new file mode 100644 index 0000000..4ed67bb --- /dev/null +++ b/client/src/features/cart/toggle-cart-icon/ui/__tests__/ToggleCartIcon.test.tsx @@ -0,0 +1,69 @@ +import { render, screen, fireEvent } from '@testing-library/react' +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { MemoryRouter } from 'react-router-dom' +import * as notifications from '@/shared/model/cart-notifications' +import * as api from '@/entities/cart/api/cart-api' +import { ToggleCartIcon } from '../ToggleCartIcon' + +vi.mock('@/entities/cart/api/cart-api', () => ({ + addToCart: vi.fn(() => Promise.resolve()), + fetchMyCart: vi.fn(() => Promise.resolve({ items: [] })), + removeCartItem: vi.fn(() => Promise.resolve()), +})) + +vi.mock('effector-react', async () => { + const actual = await vi.importActual('effector-react') + return { ...actual, useUnit: () => ({ id: '1', email: 'test@test.com' }) } +}) + +describe('ToggleCartIcon', () => { + const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } }) + + beforeEach(() => { + vi.clearAllMocks() + qc.clear() + }) + + it('calls cartAdded after successful add', async () => { + const spy = vi.spyOn(notifications, 'cartAdded') + render( + + + + + , + ) + + fireEvent.click(screen.getByRole('button', { name: /в корзину/i })) + + await vi.waitFor(() => { + expect(spy).toHaveBeenCalled() + }) + }) + + it('does not call cartAdded on remove', async () => { + vi.mocked(api.fetchMyCart).mockResolvedValueOnce({ + items: [{ id: 'cart-1', qty: 1, product: { id: 'test-product' } as never }], + }) + const spy = vi.spyOn(notifications, 'cartAdded') + + render( + + + + + , + ) + + await vi.waitFor(() => { + expect(screen.getByRole('button', { name: /убрать из корзины/i })).toBeInTheDocument() + }) + + fireEvent.click(screen.getByRole('button', { name: /убрать из корзины/i })) + + await vi.waitFor(() => { + expect(spy).not.toHaveBeenCalled() + }) + }) +}) diff --git a/client/src/shared/ui/__tests__/CartSnackbar.test.tsx b/client/src/shared/ui/__tests__/CartSnackbar.test.tsx index cf6a3e3..b163e27 100644 --- a/client/src/shared/ui/__tests__/CartSnackbar.test.tsx +++ b/client/src/shared/ui/__tests__/CartSnackbar.test.tsx @@ -1,6 +1,6 @@ import { render, screen, fireEvent, act } from '@testing-library/react' import { MemoryRouter } from 'react-router-dom' -import { describe, it, expect, vi, beforeEach } from 'vitest' +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { cartAdded, cartDismissed } from '@/shared/model/cart-notifications' import { CartSnackbar } from '@/shared/ui/CartSnackbar' @@ -15,6 +15,10 @@ beforeEach(() => { navigateMock.mockClear() }) +afterEach(() => { + vi.useRealTimers() +}) + function renderWithRouter() { render( @@ -53,7 +57,6 @@ describe('CartSnackbar', () => { vi.advanceTimersByTime(4000) }) expect(screen.queryByText(/товар добавлен/i)).not.toBeInTheDocument() - vi.useRealTimers() }) it('navigates to /cart and closes on "Перейти в корзину" click', () => { diff --git a/server/prisma/prisma/dev.db b/server/prisma/prisma/dev.db index 7d9b45c1cf09750d79bc468b12899b53f902c7cf..18b8cecc6e6e796844fb4ec746e729ec1b35efa2 100644 GIT binary patch delta 352 zcmZoTAlh(1bb>T*A_D_M7!Zd7G2=uHbH>Dt2}}C982Mi@@W0}}#lLy8pu-~m$s6Wd z35hZra^@B!W?PsV7&@fqCT8X||C!(ZXFlWhKl7P54sbDY8!&Ji@F%dG-W;efn`QgT z6y|m|wUw@nT=s?xmzd=lSXq;E3v$dYiwn~YfS|-QGcU6uBeTlXAbWdQHuDGO=_Z-X z+eAPv;sQB`|1ksqWB#N3%Ne+Uu9(h0eR(~zizrt8+Zvcz7O*k$pJU)Zw^=Y?Kfk#t zBey-L0f^vW;N;}wKv5002&AZ>j(Q delta 2894 zcmb_eO^6&t6rSFhj+v1#@n@Bwk_ZlXsj}<8J05n&1U48#5Rw(wNVd~6la0Gemh3W0 z6goj9MaUUY&1U}9O!yoT~+UW^}VlN z&AIE-=dMp*d}7D(TCFyZ=O~`3_n&pa@h=uXeXmv9S*x|bZ~xG~di9~%Pud@RzPb0| z#0hC*Cv`O2e=}WOTj{@ZbMwN@&0utMb87qba!t`*Y|)A68%c>2idh z%+6Xf9GqJ2>)u9xeS@v{H`v-6Ot{yB=E7Krg|{L;SBzFcd8kD^^%&g=V&pYq1^ zwR82xN2A8<)Qz??!Gkl+=HpHLD%`1-fISdWMrY2R%w%sk6jEtJzEA#9wqMqNYd?C2 zi91HYTJYEIYm1Esehz}s_Sx{y_Gqpf9tpM$2E7v}^yuRkLKEala?_EKH9S_CbX;ll zB0M_bh6%-G^)3^xV;W?FE62F7OmbLlndXt~I>{r2@ye1DdITP+$c=~ukIcLf(E%lg zEar*H@3|~i+>lEpQ$eA9Q~8ZpIjFfZnGa;Sq*+n|8`&{n+DX-2=tx2`IJL6UQTscx zg`QHG9+<57=3uCby>sEKIgk8$$DQ1#BkpK*X|(k{@81wr!e(1Y17C`kYVIf`x{0@f z#|G?h6*C??ZXIzbxi-vjqlrhy608A}ptUO`x2k-XJoXu31OkgJ!=NUoARX)*E|DfM z!KjNDBqUiVHV=l+jeeaA_XoMKV>%o+CL<(RtT-`6P;Kc=L}j3HSM!+iMzkVLlxa(e zpjnb(4qPFaKvYTaBkDy8jdH2LR{gHM%}XynLD(aORa~o#DelN+#2gnAE8z%G3p=EwqQwahx%v%~6^x8pVa< z30`6@$R9elttJmqjs|(~F}PYdlpu%~poasmiST&X1PwX93l@QFj*;8~f&dj1di@8O zUDaMn*=&JA${rRwyJ0K$f{ar~r2EE0M+cHBzYP=XD?`i;ZAFZwjx$VZf{Z+Enrl=q zfm2D2#R2yGLG(;pUz zgVd2ZRUt_r;KpZMqShzWx6oLMa^y=^Wha=*Lc^%=#i(%!`2>+@i2`pyBn4LhPoqd^ zS@uwpDLFBhu7mTB>?WQ^;P(Il+N%j9RXRs%$`A_0w|t`_I=nMqq-f$tkf&~ygUU{} zKr?dVL&Xud^MX>CRw=IR*6A7!J$a~65Ct?1A7fL^Ba4sZJdWoRc^3sjgml|HhIy0g z2AEYp>t!bf1+L#GbKv*dvN)S|UxNBa@nVt~tfL!}!bC(AUi_#4udyELOeE#GB5H9< zK^mkYKhIZW+*Qcby@ZnArj+{#1!#QPt!zbyz(r;0QjpeH8VC`k3ip&l`sp8p1u#@Y z$0T&&(gg57E%V8MkUdYhUMwA;QiYHR zeN_+^3@WM@p}`gACqul;@sMeN)mBXhWp-vDpd?)t5#b&RcmU7vWrJink7jZ#NGg+YBx(p#2*