Skip to content
This repository was archived by the owner on Jul 8, 2025. It is now read-only.
Draft
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
import { render } from '@/lib/test-utils'
import { screen, waitFor } from '@testing-library/react'
import { FormMuxRules } from '../form-mux-rules'
import userEvent from '@testing-library/user-event'

test('all fields are disabled if the form is disabled', async () => {
render(<FormMuxRules isDisabled={true} workspaceName="fake-workspace" />)

expect(
screen.getByLabelText(/matcher type/i, { selector: 'button' })
).toBeDisabled()
expect(
screen.getByLabelText(/matcher/i, { selector: 'input' })
).toBeDisabled()
expect(screen.getByLabelText(/model/i, { selector: 'input' })).toBeDisabled()
expect(
screen.getByLabelText(/delete/i, { selector: 'button' })
).toBeDisabled()
expect(
screen.getByLabelText(/add filter/i, { selector: 'button' })
).toBeDisabled()
expect(
screen.getByLabelText(/revert changes/i, { selector: 'button' })
).toBeDisabled()
expect(screen.getByLabelText(/save/i, { selector: 'button' })).toBeDisabled()
})

test('muxing rules form works correctly', async () => {
render(<FormMuxRules isDisabled={false} workspaceName="fake-workspace" />)

// should only have 1 row initially
expect(
screen.getAllByLabelText(/matcher type/i, { selector: 'button' }).length
).toEqual(1)
expect(
screen.getAllByLabelText(/matcher/i, { selector: 'input' }).length
).toEqual(1)
expect(
screen.getAllByLabelText(/model/i, { selector: 'input' }).length
).toEqual(1)
expect(
screen.getAllByLabelText(/delete/i, { selector: 'button' }).length
).toEqual(1)

// shouldn't be able to delete the catch-all row
expect(
screen.getAllByLabelText(/delete/i, { selector: 'button' })[0]
).toBeDisabled()

// populate the "catch-all" row
await userEvent.click(screen.getByLabelText('Model', { selector: 'input' }))
await userEvent.click(screen.getByRole('option', { name: /claude-3.6/i }))
await waitFor(() => {
expect(screen.getByLabelText('Model', { selector: 'input' })).toHaveValue(
'claude-3.6'
)
})

// add a new row
await userEvent.click(
screen.getByLabelText(/add filter/i, { selector: 'button' })
)

// add a new row
expect(
screen.getAllByLabelText(/matcher type/i, { selector: 'button' }).length
).toEqual(2)
expect(
screen.getAllByLabelText(/matcher/i, { selector: 'input' }).length
).toEqual(2)
expect(
screen.getAllByLabelText(/model/i, { selector: 'input' }).length
).toEqual(2)
expect(
screen.getAllByLabelText(/delete/i, { selector: 'button' }).length
).toEqual(2)

// shouldn't be able to delete the catch-all row
expect(
screen.getAllByLabelText(/delete/i, { selector: 'button' })[1]
).toBeDisabled()

// populate new row
const newMatcherTypeSelect = screen.getAllByLabelText(/matcher type/i, {
selector: 'button',
})[0]
const newMatcherInput = screen.getAllByLabelText(/matcher/i, {
selector: 'input',
})[0]
const newModelComboBox = screen.getAllByLabelText(/model/i, {
selector: 'input',
})[0]

await userEvent.click(newMatcherTypeSelect as HTMLFormElement)
await userEvent.click(screen.getByRole('option', { name: 'FIM' }))

await userEvent.type(newMatcherInput as HTMLFormElement, '.tsx')

await userEvent.click(newModelComboBox as HTMLFormElement)
await userEvent.click(screen.getByRole('option', { name: /chatgpt-4p/i }))

await userEvent.click(screen.getByRole('button', { name: /save/i }))

await waitFor(() => {
expect(
screen.getByText(/muxing rules for fake-workspace updated/i)
).toBeVisible()
})
})

test('displays validation errors with invalid config', async () => {
render(<FormMuxRules isDisabled={false} workspaceName="fake-workspace" />)

// should only have 1 row initially
expect(
screen.getAllByLabelText(/matcher type/i, { selector: 'button' }).length
).toEqual(1)
expect(
screen.getAllByLabelText(/matcher/i, { selector: 'input' }).length
).toEqual(1)
expect(
screen.getAllByLabelText(/model/i, { selector: 'input' }).length
).toEqual(1)
expect(
screen.getAllByLabelText(/delete/i, { selector: 'button' }).length
).toEqual(1)

// add a new row
await userEvent.click(
screen.getByLabelText(/add filter/i, { selector: 'button' })
)

// populate the matcher input, but not the model
const matcherInput = screen.getAllByLabelText(/matcher/i, {
selector: 'input',
})[0]
await userEvent.type(matcherInput as HTMLFormElement, '.tsx')

await userEvent.click(screen.getByRole('button', { name: /save/i }))

await waitFor(() => {
expect(screen.getByText(/muxing: model is required/i)).toBeVisible()
})
})

This file was deleted.

26 changes: 26 additions & 0 deletions src/features/workspace/components/form-mux-button-add-row.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
import { Button } from '@stacklok/ui-kit'
import { MuxMatcherType } from '@/api/generated'
import { Plus } from '@untitled-ui/icons-react'
import { useFormMuxRulesContext } from './form-mux-context-provider'

export function FormMuxButtonAddRow({ isDisabled }: { isDisabled: boolean }) {
const { prepend } = useFormMuxRulesContext()

return (
<Button
aria-label="Add filter"
className="w-fit"
variant="tertiary"
isDisabled={isDisabled}
onPress={() =>
prepend({
model: '',
matcher: '',
matcher_type: MuxMatcherType.FILENAME_MATCH,
})
}
>
<Plus /> Add Filter
</Button>
)
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
import { Button } from '@stacklok/ui-kit'
import { Trash01 } from '@untitled-ui/icons-react'
import { useFormMuxRulesContext } from './form-mux-context-provider'
import { FieldValuesMuxRow } from '../lib/schema-mux'
import { MuxMatcherType } from '@/api/generated'

export function FormMuxButtonDeleteRow({
index,
row,
isDisabled,
}: {
index: number
row: FieldValuesMuxRow & { id: string }
isDisabled: boolean
}) {
const { remove } = useFormMuxRulesContext()

return (
<Button
aria-label="Delete"
isIcon
isDisabled={row.matcher_type === MuxMatcherType.CATCH_ALL || isDisabled}
variant="secondary"
onPress={() => remove(index)}
>
<Trash01 />
</Button>
)
}
Loading