Merge remote-tracking branch 'origin/main' into feat/model-plugins-implementing

This commit is contained in:
yyh
2026-03-13 15:11:39 +08:00
37 changed files with 2126 additions and 701 deletions

View File

@ -3,12 +3,10 @@ import { Avatar } from '../index'
describe('Avatar', () => {
describe('Rendering', () => {
it('should render img element when avatar URL is provided', () => {
it('should keep the fallback visible when avatar URL is provided before image load', () => {
render(<Avatar name="John Doe" avatar="https://example.com/avatar.jpg" />)
const img = screen.getByRole('img', { name: 'John Doe' })
expect(img).toBeInTheDocument()
expect(img).toHaveAttribute('src', 'https://example.com/avatar.jpg')
expect(screen.getByText('J')).toBeInTheDocument()
})
it('should render fallback with uppercase initial when avatar is null', () => {
@ -18,10 +16,9 @@ describe('Avatar', () => {
expect(screen.getByText('A')).toBeInTheDocument()
})
it('should render both image and fallback when avatar is provided', () => {
it('should render the fallback when avatar is provided', () => {
render(<Avatar name="John" avatar="https://example.com/avatar.jpg" />)
expect(screen.getByRole('img')).toBeInTheDocument()
expect(screen.getByText('J')).toBeInTheDocument()
})
})
@ -90,7 +87,7 @@ describe('Avatar', () => {
})
describe('onLoadingStatusChange', () => {
it('should render image when avatar and onLoadingStatusChange are provided', () => {
it('should render the fallback when avatar and onLoadingStatusChange are provided', () => {
render(
<Avatar
name="John"
@ -99,7 +96,7 @@ describe('Avatar', () => {
/>,
)
expect(screen.getByRole('img')).toBeInTheDocument()
expect(screen.getByText('J')).toBeInTheDocument()
})
it('should not render image when avatar is null even with onLoadingStatusChange', () => {

View File

@ -978,7 +978,7 @@ describe('ChatWrapper', () => {
expect(screen.getByAltText('answer icon')).toBeInTheDocument()
})
it('should render question icon when user avatar is available', () => {
it('should render question icon fallback when user avatar is available', () => {
vi.mocked(useChatWithHistoryContext).mockReturnValue({
...defaultContextValue,
initUserVariables: {
@ -992,12 +992,11 @@ describe('ChatWrapper', () => {
chatList: [{ id: 'q1', content: 'Question' }],
} as unknown as ChatHookReturn)
const { container } = render(<ChatWrapper />)
const avatar = container.querySelector('img[alt="John Doe"]')
expect(avatar).toBeInTheDocument()
render(<ChatWrapper />)
expect(screen.getByText('J')).toBeInTheDocument()
})
it('should use fallback values for nullable appData, appMeta and user name', () => {
it('should use fallback values for nullable appData, appMeta and avatar name', () => {
vi.mocked(useChatWithHistoryContext).mockReturnValue({
...defaultContextValue,
appData: null as unknown as AppData,
@ -1014,7 +1013,7 @@ describe('ChatWrapper', () => {
render(<ChatWrapper />)
expect(screen.getByText('Question with fallback avatar name')).toBeInTheDocument()
expect(screen.getByAltText('user')).toBeInTheDocument()
expect(screen.getByText('U')).toBeInTheDocument()
})
it('should set handleStop on currentChatInstanceRef', () => {

View File

@ -327,7 +327,7 @@ describe('EmbeddedChatbot chat-wrapper', () => {
expect(screen.getByRole('button', { name: 'send message' })).toBeDisabled()
})
it('should show the user name when avatar data is provided', () => {
it('should show the user avatar fallback when avatar data is provided', () => {
vi.mocked(useEmbeddedChatbotContext).mockReturnValue(createContextValue({
initUserVariables: {
avatar_url: 'https://example.com/avatar.png',
@ -337,7 +337,7 @@ describe('EmbeddedChatbot chat-wrapper', () => {
render(<ChatWrapper />)
expect(screen.getByRole('img', { name: 'Alice' })).toBeInTheDocument()
expect(screen.getByText('A')).toBeInTheDocument()
})
})

View File

@ -639,128 +639,50 @@ describe('Mermaid Flowchart Component Module Isolation', () => {
}
})
it('should tolerate missing hidden container during classic render and cleanup', async () => {
vi.resetModules()
let pendingContainerRef: unknown | null = null
let patchedContainerRef = false
let patchedTimeoutRef = false
let containerReadCount = 0
const virtualContainer = { innerHTML: 'seed' } as HTMLDivElement
vi.doMock('react', async () => {
const reactActual = await vi.importActual<typeof import('react')>('react')
const mockedUseRef = ((initialValue: unknown) => {
const ref = reactActual.useRef(initialValue as never)
if (!patchedContainerRef && initialValue === null)
pendingContainerRef = ref
if (!patchedContainerRef
&& pendingContainerRef
&& typeof initialValue === 'string'
&& initialValue.startsWith('mermaid-chart-')) {
Object.defineProperty(pendingContainerRef as { current: unknown }, 'current', {
configurable: true,
get() {
containerReadCount += 1
if (containerReadCount === 1)
return virtualContainer
return null
},
set(_value: HTMLDivElement | null) { },
})
patchedContainerRef = true
pendingContainerRef = null
}
if (patchedContainerRef && !patchedTimeoutRef && initialValue === undefined) {
patchedTimeoutRef = true
Object.defineProperty(ref, 'current', {
configurable: true,
get() {
return undefined
},
set(_value: NodeJS.Timeout | undefined) { },
})
return ref
}
return ref
}) as typeof reactActual.useRef
return {
...reactActual,
useRef: mockedUseRef,
}
})
try {
const { default: FlowchartFresh } = await import('../index')
const { unmount } = render(<FlowchartFresh PrimitiveCode={mockCode} />)
await waitFor(() => {
expect(screen.getByText('test-svg')).toBeInTheDocument()
}, { timeout: 3000 })
unmount()
}
finally {
vi.doUnmock('react')
}
})
it('should tolerate missing hidden container during handDrawn render', async () => {
vi.resetModules()
let pendingContainerRef: unknown | null = null
let patchedContainerRef = false
let containerReadCount = 0
const virtualContainer = { innerHTML: 'seed' } as HTMLDivElement
vi.doMock('react', async () => {
const reactActual = await vi.importActual<typeof import('react')>('react')
const mockedUseRef = ((initialValue: unknown) => {
const ref = reactActual.useRef(initialValue as never)
if (!patchedContainerRef && initialValue === null)
pendingContainerRef = ref
if (!patchedContainerRef
&& pendingContainerRef
&& typeof initialValue === 'string'
&& initialValue.startsWith('mermaid-chart-')) {
Object.defineProperty(pendingContainerRef as { current: unknown }, 'current', {
configurable: true,
get() {
containerReadCount += 1
if (containerReadCount === 1)
return virtualContainer
return null
},
set(_value: HTMLDivElement | null) { },
})
patchedContainerRef = true
pendingContainerRef = null
}
return ref
}) as typeof reactActual.useRef
return {
...reactActual,
useRef: mockedUseRef,
}
})
it('should cancel a pending classic render on unmount', async () => {
const { default: FlowchartFresh } = await import('../index')
vi.useFakeTimers()
try {
const { default: FlowchartFresh } = await import('../index')
const { rerender } = render(<FlowchartFresh PrimitiveCode="graph" />)
const { unmount } = render(<FlowchartFresh PrimitiveCode={mockCode} />)
await act(async () => {
fireEvent.click(screen.getByText(HAND_DRAWN_RE))
rerender(<FlowchartFresh PrimitiveCode={mockCode} />)
unmount()
await vi.advanceTimersByTimeAsync(350)
})
await Promise.resolve()
expect(screen.getByText('test-svg-api')).toBeInTheDocument()
expect(vi.mocked(mermaidFresh.render)).not.toHaveBeenCalled()
}
finally {
vi.useRealTimers()
}
})
it('should cancel a pending handDrawn render on unmount', async () => {
const { default: FlowchartFresh } = await import('../index')
const { unmount } = render(<FlowchartFresh PrimitiveCode={mockCode} />)
await waitFor(() => {
expect(screen.getByText('test-svg')).toBeInTheDocument()
}, { timeout: 3000 })
const initialHandDrawnCalls = vi.mocked(mermaidFresh.mermaidAPI.render).mock.calls.length
vi.useFakeTimers()
try {
await act(async () => {
fireEvent.click(screen.getByText(HAND_DRAWN_RE))
})
await act(async () => {
unmount()
await vi.advanceTimersByTimeAsync(350)
})
expect(vi.mocked(mermaidFresh.mermaidAPI.render).mock.calls.length).toBe(initialHandDrawnCalls)
}
finally {
vi.useRealTimers()
vi.doUnmock('react')
}
})
})

View File

@ -4,7 +4,6 @@ import { cva } from 'class-variance-authority'
import * as React from 'react'
import { cn } from '@/utils/classnames'
import Divider from '../divider'
import './index.css'
type SegmentedControlOption<T> = {
value: T
@ -131,7 +130,7 @@ export const SegmentedControl = <T extends string | number | symbol>({
<div className={cn('inline-flex items-center gap-x-1', ItemTextWrapperVariants({ size }))}>
<span>{text}</span>
{!!(count && size === 'large') && (
<div className="system-2xs-medium-uppercase inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-[5px] border border-divider-deep bg-components-badge-bg-dimm px-[5px] text-text-tertiary">
<div className="inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-[5px] border border-divider-deep bg-components-badge-bg-dimm px-[5px] text-text-tertiary system-2xs-medium-uppercase">
{count}
</div>
)}