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 7d9b45c..18b8cec 100644
Binary files a/server/prisma/prisma/dev.db and b/server/prisma/prisma/dev.db differ