Files
shop-server/docs/superpowers/plans/2026-05-27-client-duplication.md
T
2026-05-27 20:56:08 +05:00

5.3 KiB

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

// 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
// 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:

const { data: cart } = useQuery({
  queryKey: ['me', 'cart'],
  queryFn: fetchMyCart,
  enabled: Boolean(user),
})

After:

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
cd client && npm run lint && npm test && npm run build
  • Step 8: Commit
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

cd client && npm run lint && npm test && npm run build
  • Step 5: Commit
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"