Merge remote-tracking branch 'origin/master' into feat/web-terminal-card

# Conflicts:
#	packages/client/ui-conversation/src/client/chat/ToolRow.tsx
This commit is contained in:
Chinesezjc
2026-07-28 16:43:15 +08:00
183 changed files with 2401 additions and 1402 deletions

View File

@@ -271,14 +271,47 @@ describe('Menu', () => {
expect(onClose).toHaveBeenCalledTimes(1)
})
it('portal mode positions from the opposite edges for align=end / side=top', () => {
it('portal mode resolves align=end / side=top to clamped left/top coordinates', () => {
render(
<Menu portal open align="end" side="top" anchor={<span>trigger</span>} items={items} onSelect={() => {}} onClose={() => {}} />)
const menu = screen.getByRole('menu')
expect(menu.style.right).not.toBe('')
expect(menu.style.bottom).not.toBe('')
expect(menu.style.left).toBe('')
expect(menu.style.top).toBe('')
expect(menu.style.left).not.toBe('')
expect(menu.style.top).not.toBe('')
expect(menu.style.right).toBe('')
expect(menu.style.bottom).toBe('')
})
it('renders footer rows in a pinned section below the items; they still select', () => {
const onSelect = vi.fn()
render(
<Menu
open
anchor={<span>trigger</span>}
items={items}
footer={[{ id: 'new', label: 'Create new' }]}
onSelect={onSelect}
onClose={() => {}}
/>)
const footerItem = screen.getByRole('menuitem', { name: 'Create new' })
expect((footerItem.closest('div[class*="footer"]'))).not.toBeNull()
expect(screen.getByRole('menuitem', { name: 'Alpha' }).closest('div[class*="footer"]')).toBeNull()
fireEvent.click(footerItem)
expect(onSelect).toHaveBeenCalledWith('new')
})
it('caps the list height for internal scrolling unless a submenu row is present', () => {
const { rerender } = render(
<Menu open anchor={<span>trigger</span>} items={items} onSelect={() => {}} onClose={() => {}} />)
expect(screen.getByRole('menu').className).toMatch(/scrollable/)
rerender(
<Menu
open
anchor={<span>trigger</span>}
items={[{ id: 'p', label: 'Parent', submenu: [{ id: 's', label: 'Sub' }] }]}
onSelect={() => {}}
onClose={() => {}}
/>)
expect(screen.getByRole('menu').className).not.toMatch(/scrollable/)
})
})

View File

@@ -14,16 +14,17 @@ describe('StateDot', () => {
expect(dot.getAttribute('aria-hidden')).toBe('true')
})
it('solid states are spans; ongoing is an svg gradient ring', () => {
it('solid states are spans; ongoing is an svg pixel matrix', () => {
const { container, rerender } = render(<StateDot state="done" />)
expect(container.firstElementChild?.tagName).toBe('SPAN')
rerender(<StateDot state="ongoing" />)
const ring = container.firstElementChild as SVGSVGElement
expect(ring.tagName).toBe('svg')
const circle = ring.querySelector('circle')
expect(circle?.getAttribute('stroke-width')).toBe('1')
expect(circle?.getAttribute('stroke')).toMatch(/^url\(#/)
expect(ring.querySelector('linearGradient')).not.toBeNull()
const matrix = container.firstElementChild as SVGSVGElement
expect(matrix.tagName).toBe('svg')
const cells = matrix.querySelectorAll('rect')
expect(cells).toHaveLength(8)
// Chase phase: every cell carries its own negative animation delay.
const delays = [...cells].map(cell => (cell).style.animationDelay)
expect(new Set(delays).size).toBe(8)
})
it('sizes via the size prop in both shapes', () => {

View File

@@ -81,23 +81,20 @@ describe('Tooltip', () => {
expect(screen.getByRole('tooltip')).toBeTruthy()
})
it('keeps the bubble while either hover or focus is still active', () => {
it('mouse leave hides the bubble immediately, even while the anchor stays focused', () => {
render(
<Tooltip label="Sticky">
<button type="button">anchor</button>
</Tooltip>,
)
const anchor = screen.getByText('anchor')
// Focused AND hovered: leaving with the mouse must not drop the bubble.
// Focused AND hovered: leaving with the mouse drops the bubble at once.
fireEvent.focus(anchor)
fireEvent.mouseEnter(anchor)
fireEvent.mouseLeave(anchor)
expect(screen.getByRole('tooltip')).toBeTruthy()
fireEvent.blur(anchor)
expect(screen.queryByRole('tooltip')).toBeNull()
// Symmetric: blurring while still hovered keeps it, mouseleave ends it.
// Re-entering shows it again; blurring while still hovered keeps it.
fireEvent.mouseEnter(anchor)
fireEvent.focus(anchor)
fireEvent.blur(anchor)
expect(screen.getByRole('tooltip')).toBeTruthy()
fireEvent.mouseLeave(anchor)