176 lines
5.3 KiB
Markdown
176 lines
5.3 KiB
Markdown
# Client Duplication — Implementation Plan
|
|
|
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development or superpowers:executing-plans.
|
|
|
|
**Goal:** Устранить дублирование `useQuery` для корзины (4 копии → 1 хук), устранить дублирование статусов заказа.
|
|
|
|
**Architecture:** Кастомный хук `useCartQuery` в `entities/cart/lib/`, единый источник `ORDER_STATUS_DATA`.
|
|
|
|
**Tech Stack:** TypeScript, TanStack React Query, Vitest
|
|
|
|
**Depends on:** none
|
|
|
|
---
|
|
|
|
### Task 1: useCartQuery хук + тесты
|
|
|
|
**Files:**
|
|
- Create: `client/src/entities/cart/lib/use-cart-query.ts`
|
|
- Test: `client/src/entities/cart/lib/use-cart-query.test.tsx`
|
|
- Modify: `client/src/widgets/catalog-slider/ui/AppHeader.tsx`
|
|
- Modify: `client/src/pages/cart/CartPage.tsx`
|
|
- Modify: `client/src/pages/checkout/CheckoutPage.tsx`
|
|
- Modify: `client/src/features/cart/ui/ToggleCartIcon/ToggleCartIcon.tsx`
|
|
|
|
- [ ] **Step 1: Write failing tests**
|
|
|
|
```tsx
|
|
// client/src/entities/cart/lib/use-cart-query.test.tsx
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
import { renderHook, waitFor } from '@testing-library/react'
|
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
import { useCartQuery } from './use-cart-query'
|
|
import { fetchMyCart } from '../../api'
|
|
|
|
vi.mock('../../api', () => ({
|
|
fetchMyCart: vi.fn(),
|
|
}))
|
|
|
|
vi.mock('@/shared/model/auth', () => ({
|
|
useAuthUser: vi.fn(),
|
|
}))
|
|
|
|
import { useAuthUser } from '@/shared/model/auth'
|
|
|
|
function createWrapper() {
|
|
const qc = new QueryClient()
|
|
return ({ children }: { children: React.ReactNode }) => (
|
|
<QueryClientProvider client={qc}>{children}</QueryClientProvider>
|
|
)
|
|
}
|
|
|
|
describe('useCartQuery', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
it('returns query with correct key and enabled flag for authenticated user', async () => {
|
|
vi.mocked(useAuthUser).mockReturnValue({ id: '1', email: 'test@test.com' })
|
|
vi.mocked(fetchMyCart).mockResolvedValue({ items: [] })
|
|
|
|
const { result } = renderHook(() => useCartQuery(), { wrapper: createWrapper() })
|
|
|
|
await waitFor(() => expect(result.current.isSuccess).toBe(true))
|
|
expect(fetchMyCart).toHaveBeenCalled()
|
|
expect(result.current.queryKey).toEqual(['me', 'cart'])
|
|
})
|
|
|
|
it('does not fetch when user is not authenticated', () => {
|
|
vi.mocked(useAuthUser).mockReturnValue(null)
|
|
|
|
const { result } = renderHook(() => useCartQuery(), { wrapper: createWrapper() })
|
|
|
|
expect(fetchMyCart).not.toHaveBeenCalled()
|
|
expect(result.current.fetchStatus).toBe('idle')
|
|
})
|
|
})
|
|
```
|
|
|
|
- [ ] **Step 2: Run to verify failure**
|
|
|
|
Run: `cd client && npx vitest run entities/cart/lib/use-cart-query.test.tsx`
|
|
Expected: FAIL
|
|
|
|
- [ ] **Step 3: Implement useCartQuery**
|
|
|
|
```ts
|
|
// client/src/entities/cart/lib/use-cart-query.ts
|
|
import { useQuery } from '@tanstack/react-query'
|
|
import { useAuthUser } from '@/shared/model/auth'
|
|
import { fetchMyCart } from '../../api'
|
|
|
|
export function useCartQuery() {
|
|
const user = useAuthUser()
|
|
|
|
return useQuery({
|
|
queryKey: ['me', 'cart'],
|
|
queryFn: fetchMyCart,
|
|
enabled: Boolean(user),
|
|
})
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 4: Run tests**
|
|
|
|
Run: `cd client && npx vitest run entities/cart/lib/use-cart-query.test.tsx`
|
|
Expected: PASS
|
|
|
|
- [ ] **Step 5: Apply to AppHeader.tsx**
|
|
|
|
Before:
|
|
```tsx
|
|
const { data: cart } = useQuery({
|
|
queryKey: ['me', 'cart'],
|
|
queryFn: fetchMyCart,
|
|
enabled: Boolean(user),
|
|
})
|
|
```
|
|
|
|
After:
|
|
```tsx
|
|
import { useCartQuery } from '@/entities/cart/lib/use-cart-query'
|
|
const { data: cart } = useCartQuery()
|
|
```
|
|
|
|
- [ ] **Step 6: Apply to CartPage.tsx, CheckoutPage.tsx, ToggleCartIcon.tsx**
|
|
|
|
Same replacement in each file.
|
|
|
|
- [ ] **Step 7: Run lint + test + build**
|
|
|
|
```bash
|
|
cd client && npm run lint && npm test && npm run build
|
|
```
|
|
|
|
- [ ] **Step 8: Commit**
|
|
|
|
```bash
|
|
git add client/src/entities/cart/lib/use-cart-query.ts client/src/entities/cart/lib/use-cart-query.test.tsx client/src/widgets/catalog-slider/ui/AppHeader.tsx client/src/pages/cart/CartPage.tsx client/src/pages/checkout/CheckoutPage.tsx client/src/features/cart/ui/ToggleCartIcon/ToggleCartIcon.tsx
|
|
git commit -m "refactor: extract useCartQuery hook"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 2: Устранить дублирование статусов заказа
|
|
|
|
**Files:**
|
|
- Read: `client/src/shared/lib/order-status-data.ts`
|
|
- Read: `client/src/shared/lib/order-status-labels.ts`
|
|
- Delete: `client/src/shared/lib/order-status-labels.ts`
|
|
- Modify: all files importing from `order-status-labels`
|
|
|
|
- [ ] **Step 1: Найти все импорты orderStatusLabelRu**
|
|
|
|
Run: `rg 'orderStatusLabelRu' client/src/ --include '*.ts' --include '*.tsx'`
|
|
|
|
- [ ] **Step 2: Заменить импорты на ORDER_STATUS_DATA**
|
|
|
|
Each file importing `{ orderStatusLabelRu }` from `order-status-labels`:
|
|
- Change to import `{ ORDER_STATUS_DATA }` from `order-status-data`
|
|
- Replace `orderStatusLabelRu(status)` with `ORDER_STATUS_DATA[status].label`
|
|
|
|
- [ ] **Step 3: Delete order-status-labels.ts**
|
|
|
|
- [ ] **Step 4: Run lint + test + build**
|
|
|
|
```bash
|
|
cd client && npm run lint && npm test && npm run build
|
|
```
|
|
|
|
- [ ] **Step 5: Commit**
|
|
|
|
```bash
|
|
git add client/src/shared/lib/order-status-labels.ts client/src/shared/lib/order-status-data.ts
|
|
git commit -m "refactor: remove duplicate order status labels, use ORDER_STATUS_DATA as single source"
|
|
```
|