ываыв

This commit is contained in:
Kirill
2026-05-28 21:46:17 +05:00
parent ba375aee12
commit f34c05095a
12 changed files with 1039 additions and 18 deletions
+11 -12
View File
@@ -21,6 +21,14 @@ export function SseProvider() {
const es = createEventStream(token)
sourceRef.current = es
function invalidateOrderQueries(orderId: unknown) {
if (!orderId) return
queryClient.invalidateQueries({ queryKey: ['me', 'orders', orderId] })
queryClient.invalidateQueries({ queryKey: ['admin', 'orders', 'detail', orderId] })
queryClient.invalidateQueries({ queryKey: ['admin', 'orders'] })
queryClient.invalidateQueries({ queryKey: ['admin', 'orders', 'summary'] })
}
function handleEvent(eventName: string) {
return function (event: MessageEvent) {
try {
@@ -31,22 +39,13 @@ export function SseProvider() {
case 'message:new':
queryClient.invalidateQueries({ queryKey: ['me', 'messages', 'unread-count'] })
queryClient.invalidateQueries({ queryKey: ['me', 'conversations'] })
if (orderId) {
queryClient.invalidateQueries({ queryKey: ['me', 'orders', orderId] })
queryClient.invalidateQueries({ queryKey: ['admin', 'orders', orderId] })
}
invalidateOrderQueries(orderId)
break
case 'order:statusChanged':
if (orderId) {
queryClient.invalidateQueries({ queryKey: ['me', 'orders', orderId] })
queryClient.invalidateQueries({ queryKey: ['admin', 'orders', orderId] })
}
invalidateOrderQueries(orderId)
break
case 'order:updated':
if (orderId) {
queryClient.invalidateQueries({ queryKey: ['me', 'orders', orderId] })
queryClient.invalidateQueries({ queryKey: ['admin', 'orders', orderId] })
}
invalidateOrderQueries(orderId)
break
case 'order:new':
queryClient.invalidateQueries({ queryKey: ['admin', 'orders', 'summary'] })
@@ -108,7 +108,9 @@ describe('SseProvider', () => {
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['me', 'messages', 'unread-count'] })
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['me', 'conversations'] })
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['me', 'orders', 'o1'] })
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['admin', 'orders', 'o1'] })
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['admin', 'orders', 'detail', 'o1'] })
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['admin', 'orders'] })
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['admin', 'orders', 'summary'] })
})
it('invalidates order queries on order:statusChanged', () => {
@@ -117,7 +119,9 @@ describe('SseProvider', () => {
const handler = mockEventHandlers['order:statusChanged']
handler(new MessageEvent('order:statusChanged', { data: JSON.stringify({ orderId: 'o2' }) }))
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['me', 'orders', 'o2'] })
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['admin', 'orders', 'o2'] })
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['admin', 'orders', 'detail', 'o2'] })
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['admin', 'orders'] })
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['admin', 'orders', 'summary'] })
})
it('invalidates order queries on order:updated', () => {
@@ -126,7 +130,9 @@ describe('SseProvider', () => {
const handler = mockEventHandlers['order:updated']
handler(new MessageEvent('order:updated', { data: JSON.stringify({ orderId: 'o3' }) }))
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['me', 'orders', 'o3'] })
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['admin', 'orders', 'o3'] })
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['admin', 'orders', 'detail', 'o3'] })
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['admin', 'orders'] })
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['admin', 'orders', 'summary'] })
})
it('invalidates admin queries on order:new', () => {
@@ -215,7 +215,7 @@ export function OrderDetailContent({ detail, orderId }: { detail: AdminOrderDeta
/>
)
return (
<ChatMessageBubble key={m.id} authorType={isAdminMsg ? 'user' : 'admin'} avatar={avatarNode}>
<ChatMessageBubble key={m.id} authorType={isAdminMsg ? 'admin' : 'user'} avatar={avatarNode}>
<Typography variant="caption" color="text.secondary">
{isAdminMsg ? 'Админ (вы)' : 'Пользователь'} · {new Date(m.createdAt).toLocaleString()}
</Typography>
@@ -1,3 +1,4 @@
import type { ReactNode } from 'react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
@@ -29,6 +30,12 @@ vi.mock('@/shared/ui/RichTextMessageEditor.lazy', () => ({
}) => <textarea aria-label="Ответ админа" value={value} onChange={(e) => onChange(e.target.value)} />,
}))
vi.mock('@/shared/ui/ChatMessageBubble', () => ({
ChatMessageBubble: ({ authorType, children }: { authorType: 'admin' | 'user'; children: ReactNode }) => (
<div data-testid={`chat-message-${authorType}`}>{children}</div>
),
}))
const setAdminOrderStatusMock = vi.mocked(setAdminOrderStatus)
function createDetail(overrides?: Partial<AdminOrderDetailResponse['item']>): AdminOrderDetailResponse['item'] {
@@ -175,4 +182,30 @@ describe('OrderDetailContent quick status transitions', () => {
await user.click(paidButton)
expect(setAdminOrderStatusMock).toHaveBeenCalledTimes(2)
})
it('передает фактический authorType в пузырь сообщения', () => {
renderComponent(
createDetail({
messages: [
{
id: 'message-admin',
authorType: 'admin',
text: 'Ответ администратора',
attachmentUrl: null,
createdAt: '2026-05-28T10:00:00.000Z',
},
{
id: 'message-user',
authorType: 'user',
text: 'Сообщение покупателя',
attachmentUrl: null,
createdAt: '2026-05-28T10:01:00.000Z',
},
],
}),
)
expect(screen.getByTestId('chat-message-admin')).toHaveTextContent('Админ (вы)')
expect(screen.getByTestId('chat-message-user')).toHaveTextContent('Пользователь')
})
})
@@ -0,0 +1,17 @@
import { describe, expect, it } from 'vitest'
import { canTransitionOrderStatus, getAdminNextOrderStatuses } from '../order'
describe('client order status helpers', () => {
it('returns delivery-specific next statuses', () => {
expect(getAdminNextOrderStatuses('IN_PROGRESS', 'delivery')).toEqual(['SHIPPED', 'CANCELLED'])
})
it('returns pickup-specific next statuses', () => {
expect(getAdminNextOrderStatuses('IN_PROGRESS', 'pickup')).toEqual(['READY_FOR_PICKUP', 'CANCELLED'])
})
it('checks pickup transition without falling back to delivery rules', () => {
expect(canTransitionOrderStatus('IN_PROGRESS', 'READY_FOR_PICKUP', 'pickup')).toBe(true)
expect(canTransitionOrderStatus('IN_PROGRESS', 'SHIPPED', 'pickup')).toBe(false)
})
})
+2 -2
View File
@@ -11,7 +11,7 @@ export function getAdminNextOrderStatuses(status: string, deliveryType: 'deliver
return sharedGetNextAdminStatuses(status, deliveryType) as OrderStatus[]
}
export function canTransitionOrderStatus(from: string, to: string): boolean {
export function canTransitionOrderStatus(from: string, to: string, deliveryType: 'delivery' | 'pickup'): boolean {
if (from === to) return true
return getAdminNextOrderStatuses(from, 'delivery').includes(to as OrderStatus)
return getAdminNextOrderStatuses(from, deliveryType).includes(to as OrderStatus)
}