Files
ersatztv/web/src/builder/rules/RuleBuilder.test.tsx
T

73 lines
2.7 KiB
TypeScript

import { cleanup, render, screen, fireEvent } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { RuleBuilder } from './RuleBuilder';
import type { RuleField } from './fieldCatalog';
import type { Group } from './types';
afterEach(() => {
cleanup();
});
const FIELDS: RuleField[] = [
{ name: 'genre', label: 'Genre', type: 'text', group: 'General', values: [] },
{ name: 'type', label: 'Item type', type: 'enum', group: 'General', values: ['movie', 'episode'] }
];
function setup(initial: Group) {
const onChange = vi.fn();
const utils = render(<RuleBuilder value={initial} onChange={onChange} fields={FIELDS} />);
return { onChange, ...utils };
}
describe('RuleBuilder', () => {
it('adds a rule', () => {
const { onChange } = setup({ match: 'all', children: [] });
fireEvent.click(screen.getByText('Add rule'));
expect(onChange).toHaveBeenCalledWith(
expect.objectContaining({
match: 'all',
children: expect.arrayContaining([expect.objectContaining({ field: 'genre' })])
})
);
});
it('removes a rule', () => {
const { onChange } = setup({ match: 'all', children: [{ field: 'genre', operator: 'is', value: 'Horror' }] });
fireEvent.click(screen.getByLabelText('Remove rule'));
expect(onChange).toHaveBeenCalledWith({ match: 'all', children: [] });
});
it('adds a nested group only at top level', () => {
const { onChange } = setup({ match: 'all', children: [] });
fireEvent.click(screen.getByText('Add group'));
expect(onChange).toHaveBeenCalledWith(
expect.objectContaining({ children: expect.arrayContaining([expect.objectContaining({ match: 'any' })]) })
);
});
it('shows enum values as a dropdown', () => {
setup({ match: 'all', children: [{ field: 'type', operator: 'is', value: 'movie' }] });
expect(screen.getByRole('option', { name: 'episode' })).toBeInTheDocument();
});
it('resets operator and drops value/value2 when the field type changes', () => {
const onChange = vi.fn();
const fieldsWithNumber = [
{ name: 'genre', label: 'Genre', type: 'text' as const, group: 'General', values: [] },
{ name: 'minutes', label: 'Duration', type: 'number' as const, group: 'Technical', values: [] }
];
render(
<RuleBuilder
value={{ match: 'all', children: [{ field: 'minutes', operator: 'between', value: '10', value2: '90' }] }}
onChange={onChange}
fields={fieldsWithNumber}
/>
);
fireEvent.change(screen.getByLabelText('Field'), { target: { value: 'genre' } });
expect(onChange).toHaveBeenCalledWith({
match: 'all',
children: [{ field: 'genre', operator: 'is', value: '' }]
});
});
});