Compare commits

...

3 Commits

Author SHA1 Message Date
Stephen Zhou
9d1d76055a test 2026-01-30 16:29:46 +08:00
Stephen Zhou
a34d3ffc28 update 2026-01-30 16:26:20 +08:00
Stephen Zhou
1d2ed8da2a test: fix failed test 2026-01-30 16:16:50 +08:00
5 changed files with 36 additions and 27 deletions

View File

@@ -124,7 +124,7 @@ describe('CreateAppModal', () => {
const nameInput = screen.getByPlaceholderText('app.newApp.appNamePlaceholder') const nameInput = screen.getByPlaceholderText('app.newApp.appNamePlaceholder')
fireEvent.change(nameInput, { target: { value: 'My App' } }) fireEvent.change(nameInput, { target: { value: 'My App' } })
fireEvent.click(screen.getByRole('button', { name: 'app.newApp.Create' })) fireEvent.click(screen.getByRole('button', { name: /app\.newApp\.Create/ }))
await waitFor(() => expect(mockCreateApp).toHaveBeenCalledWith({ await waitFor(() => expect(mockCreateApp).toHaveBeenCalledWith({
name: 'My App', name: 'My App',
@@ -152,7 +152,7 @@ describe('CreateAppModal', () => {
const nameInput = screen.getByPlaceholderText('app.newApp.appNamePlaceholder') const nameInput = screen.getByPlaceholderText('app.newApp.appNamePlaceholder')
fireEvent.change(nameInput, { target: { value: 'My App' } }) fireEvent.change(nameInput, { target: { value: 'My App' } })
fireEvent.click(screen.getByRole('button', { name: 'app.newApp.Create' })) fireEvent.click(screen.getByRole('button', { name: /app\.newApp\.Create/ }))
await waitFor(() => expect(mockCreateApp).toHaveBeenCalled()) await waitFor(() => expect(mockCreateApp).toHaveBeenCalled())
expect(mockNotify).toHaveBeenCalledWith({ type: 'error', message: 'boom' }) expect(mockNotify).toHaveBeenCalledWith({ type: 'error', message: 'boom' })

View File

@@ -138,7 +138,7 @@ describe('CreateAppModal', () => {
setup({ appName: 'My App', isEditModal: false }) setup({ appName: 'My App', isEditModal: false })
expect(screen.getByText('explore.appCustomize.title:{"name":"My App"}')).toBeInTheDocument() expect(screen.getByText('explore.appCustomize.title:{"name":"My App"}')).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'common.operation.create' })).toBeInTheDocument() expect(screen.getByRole('button', { name: /common\.operation\.create/ })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'common.operation.cancel' })).toBeInTheDocument() expect(screen.getByRole('button', { name: 'common.operation.cancel' })).toBeInTheDocument()
}) })
@@ -146,7 +146,7 @@ describe('CreateAppModal', () => {
setup({ isEditModal: true, appMode: AppModeEnum.CHAT, max_active_requests: 5 }) setup({ isEditModal: true, appMode: AppModeEnum.CHAT, max_active_requests: 5 })
expect(screen.getByText('app.editAppTitle')).toBeInTheDocument() expect(screen.getByText('app.editAppTitle')).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'common.operation.save' })).toBeInTheDocument() expect(screen.getByRole('button', { name: /common\.operation\.save/ })).toBeInTheDocument()
expect(screen.getByRole('switch')).toBeInTheDocument() expect(screen.getByRole('switch')).toBeInTheDocument()
expect((screen.getByRole('spinbutton') as HTMLInputElement).value).toBe('5') expect((screen.getByRole('spinbutton') as HTMLInputElement).value).toBe('5')
}) })
@@ -166,7 +166,7 @@ describe('CreateAppModal', () => {
it('should not render modal content when hidden', () => { it('should not render modal content when hidden', () => {
setup({ show: false }) setup({ show: false })
expect(screen.queryByRole('button', { name: 'common.operation.create' })).not.toBeInTheDocument() expect(screen.queryByRole('button', { name: /common\.operation\.create/ })).not.toBeInTheDocument()
}) })
}) })
@@ -175,13 +175,13 @@ describe('CreateAppModal', () => {
it('should disable confirm action when confirmDisabled is true', () => { it('should disable confirm action when confirmDisabled is true', () => {
setup({ confirmDisabled: true }) setup({ confirmDisabled: true })
expect(screen.getByRole('button', { name: 'common.operation.create' })).toBeDisabled() expect(screen.getByRole('button', { name: /common\.operation\.create/ })).toBeDisabled()
}) })
it('should disable confirm action when appName is empty', () => { it('should disable confirm action when appName is empty', () => {
setup({ appName: ' ' }) setup({ appName: ' ' })
expect(screen.getByRole('button', { name: 'common.operation.create' })).toBeDisabled() expect(screen.getByRole('button', { name: /common\.operation\.create/ })).toBeDisabled()
}) })
}) })
@@ -245,7 +245,7 @@ describe('CreateAppModal', () => {
setup({ isEditModal: false }) setup({ isEditModal: false })
expect(screen.getByText('billing.apps.fullTip2')).toBeInTheDocument() expect(screen.getByText('billing.apps.fullTip2')).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'common.operation.create' })).toBeDisabled() expect(screen.getByRole('button', { name: /common\.operation\.create/ })).toBeDisabled()
}) })
it('should allow saving when apps quota is reached in edit mode', () => { it('should allow saving when apps quota is reached in edit mode', () => {
@@ -257,7 +257,7 @@ describe('CreateAppModal', () => {
setup({ isEditModal: true }) setup({ isEditModal: true })
expect(screen.queryByText('billing.apps.fullTip2')).not.toBeInTheDocument() expect(screen.queryByText('billing.apps.fullTip2')).not.toBeInTheDocument()
expect(screen.getByRole('button', { name: 'common.operation.save' })).toBeEnabled() expect(screen.getByRole('button', { name: /common\.operation\.save/ })).toBeEnabled()
}) })
}) })
@@ -384,7 +384,7 @@ describe('CreateAppModal', () => {
fireEvent.click(screen.getByRole('button', { name: 'app.iconPicker.ok' })) fireEvent.click(screen.getByRole('button', { name: 'app.iconPicker.ok' }))
fireEvent.click(screen.getByRole('button', { name: 'common.operation.create' })) fireEvent.click(screen.getByRole('button', { name: /common\.operation\.create/ }))
act(() => { act(() => {
vi.advanceTimersByTime(300) vi.advanceTimersByTime(300)
}) })
@@ -433,7 +433,7 @@ describe('CreateAppModal', () => {
expect(screen.queryByRole('button', { name: 'app.iconPicker.cancel' })).not.toBeInTheDocument() expect(screen.queryByRole('button', { name: 'app.iconPicker.cancel' })).not.toBeInTheDocument()
// Submit and verify the payload uses the original icon (cancel reverts to props) // Submit and verify the payload uses the original icon (cancel reverts to props)
fireEvent.click(screen.getByRole('button', { name: 'common.operation.create' })) fireEvent.click(screen.getByRole('button', { name: /common\.operation\.create/ }))
act(() => { act(() => {
vi.advanceTimersByTime(300) vi.advanceTimersByTime(300)
}) })
@@ -471,7 +471,7 @@ describe('CreateAppModal', () => {
appIconBackground: '#000000', appIconBackground: '#000000',
}) })
fireEvent.click(screen.getByRole('button', { name: 'common.operation.create' })) fireEvent.click(screen.getByRole('button', { name: /common\.operation\.create/ }))
act(() => { act(() => {
vi.advanceTimersByTime(300) vi.advanceTimersByTime(300)
}) })
@@ -495,7 +495,7 @@ describe('CreateAppModal', () => {
const { onConfirm } = setup({ appDescription: 'Old description' }) const { onConfirm } = setup({ appDescription: 'Old description' })
fireEvent.change(screen.getByPlaceholderText('app.newApp.appDescriptionPlaceholder'), { target: { value: 'Updated description' } }) fireEvent.change(screen.getByPlaceholderText('app.newApp.appDescriptionPlaceholder'), { target: { value: 'Updated description' } })
fireEvent.click(screen.getByRole('button', { name: 'common.operation.create' })) fireEvent.click(screen.getByRole('button', { name: /common\.operation\.create/ }))
act(() => { act(() => {
vi.advanceTimersByTime(300) vi.advanceTimersByTime(300)
}) })
@@ -512,7 +512,7 @@ describe('CreateAppModal', () => {
appIconBackground: null, appIconBackground: null,
}) })
fireEvent.click(screen.getByRole('button', { name: 'common.operation.create' })) fireEvent.click(screen.getByRole('button', { name: /common\.operation\.create/ }))
act(() => { act(() => {
vi.advanceTimersByTime(300) vi.advanceTimersByTime(300)
}) })
@@ -536,7 +536,7 @@ describe('CreateAppModal', () => {
fireEvent.click(screen.getByRole('switch')) fireEvent.click(screen.getByRole('switch'))
fireEvent.change(screen.getByRole('spinbutton'), { target: { value: '12' } }) fireEvent.change(screen.getByRole('spinbutton'), { target: { value: '12' } })
fireEvent.click(screen.getByRole('button', { name: 'common.operation.save' })) fireEvent.click(screen.getByRole('button', { name: /common\.operation\.save/ }))
act(() => { act(() => {
vi.advanceTimersByTime(300) vi.advanceTimersByTime(300)
}) })
@@ -551,7 +551,7 @@ describe('CreateAppModal', () => {
it('should omit max_active_requests when input is empty', () => { it('should omit max_active_requests when input is empty', () => {
const { onConfirm } = setup({ isEditModal: true, max_active_requests: null }) const { onConfirm } = setup({ isEditModal: true, max_active_requests: null })
fireEvent.click(screen.getByRole('button', { name: 'common.operation.save' })) fireEvent.click(screen.getByRole('button', { name: /common\.operation\.save/ }))
act(() => { act(() => {
vi.advanceTimersByTime(300) vi.advanceTimersByTime(300)
}) })
@@ -564,7 +564,7 @@ describe('CreateAppModal', () => {
const { onConfirm } = setup({ isEditModal: true, max_active_requests: null }) const { onConfirm } = setup({ isEditModal: true, max_active_requests: null })
fireEvent.change(screen.getByRole('spinbutton'), { target: { value: 'abc' } }) fireEvent.change(screen.getByRole('spinbutton'), { target: { value: 'abc' } })
fireEvent.click(screen.getByRole('button', { name: 'common.operation.save' })) fireEvent.click(screen.getByRole('button', { name: /common\.operation\.save/ }))
act(() => { act(() => {
vi.advanceTimersByTime(300) vi.advanceTimersByTime(300)
}) })
@@ -576,7 +576,7 @@ describe('CreateAppModal', () => {
it('should show toast error and not submit when name becomes empty before debounced submit runs', () => { it('should show toast error and not submit when name becomes empty before debounced submit runs', () => {
const { onConfirm, onHide } = setup({ appName: 'My App' }) const { onConfirm, onHide } = setup({ appName: 'My App' })
fireEvent.click(screen.getByRole('button', { name: 'common.operation.create' })) fireEvent.click(screen.getByRole('button', { name: /common\.operation\.create/ }))
fireEvent.change(screen.getByPlaceholderText('app.newApp.appNamePlaceholder'), { target: { value: ' ' } }) fireEvent.change(screen.getByPlaceholderText('app.newApp.appNamePlaceholder'), { target: { value: ' ' } })
act(() => { act(() => {

View File

@@ -86,6 +86,7 @@ vi.mock('./actions/commands/registry', () => ({
vi.mock('@/app/components/workflow/utils/common', () => ({ vi.mock('@/app/components/workflow/utils/common', () => ({
getKeyboardKeyCodeBySystem: () => 'ctrl', getKeyboardKeyCodeBySystem: () => 'ctrl',
getKeyboardKeyNameBySystem: (key: string) => key,
isEventTargetInputArea: () => false, isEventTargetInputArea: () => false,
isMac: () => false, isMac: () => false,
})) }))

View File

@@ -134,19 +134,20 @@ vi.mock('@/app/components/workflow/constants', () => ({
WORKFLOW_DATA_UPDATE: 'WORKFLOW_DATA_UPDATE', WORKFLOW_DATA_UPDATE: 'WORKFLOW_DATA_UPDATE',
})) }))
// Mock FileReader // Mock FileReader - synchronous to avoid timing issues in tests
class MockFileReader { class MockFileReader {
result: string | null = null result: string | null = null
onload: ((e: { target: { result: string | null } }) => void) | null = null onload: ((e: { target: { result: string | null } }) => void) | null = null
readAsText(_file: File) { readAsText(_file: File) {
// Simulate async file reading // Call onload synchronously to avoid race conditions in tests
setTimeout(() => { this.result = 'test file content'
this.result = 'test file content' // Use queueMicrotask instead of setTimeout to ensure it runs before other timers
if (this.onload) { // but still allows React state updates to complete
queueMicrotask(() => {
if (this.onload)
this.onload({ target: { result: this.result } }) this.onload({ target: { result: this.result } })
} })
}, 0)
} }
} }

View File

@@ -33,6 +33,11 @@ vi.mock('@/context/i18n', () => ({
useDocLink: () => (path: string) => `https://docs.example.com${path}`, useDocLink: () => (path: string) => `https://docs.example.com${path}`,
})) }))
// Mock workflow utils for ShortcutsName component
vi.mock('@/app/components/workflow/utils', () => ({
getKeyboardKeyNameBySystem: (key: string) => key,
}))
// Mock StartNodeSelectionPanel (using real component would be better for integration, // Mock StartNodeSelectionPanel (using real component would be better for integration,
// but for this test we'll mock to control behavior) // but for this test we'll mock to control behavior)
vi.mock('./start-node-selection-panel', () => ({ vi.mock('./start-node-selection-panel', () => ({
@@ -551,8 +556,10 @@ describe('WorkflowOnboardingModal', () => {
// Assert // Assert
const escKey = screen.getByText('workflow.onboarding.escTip.key') const escKey = screen.getByText('workflow.onboarding.escTip.key')
expect(escKey.closest('kbd')).toBeInTheDocument() // ShortcutsName renders a div with system-kbd class, not a kbd element
expect(escKey.closest('kbd')).toHaveClass('system-kbd') const kbdContainer = escKey.closest('.system-kbd')
expect(kbdContainer).toBeInTheDocument()
expect(kbdContainer).toHaveClass('system-kbd')
}) })
it('should have descriptive text for ESC functionality', () => { it('should have descriptive text for ESC functionality', () => {