conductor/frontend/src/components/ActionInspector/configurationUtils.test.ts

82 lines
3.8 KiB
TypeScript

import type { CanvasComponent, ProjectDocument, RestAction } from '../../types/project';
import {
createResponseBinding,
findVariableReferences,
parseVariableDefault,
removeBindingAt,
removeVariable,
responseTargetOptions,
setVariable,
validateVariableName,
} from './configurationUtils';
const action: RestAction = {
id: 'action_lookup', name: 'Lookup', method: 'GET', url: 'https://example.com',
headers: {}, queryParameters: {}, pathParameters: {}, bodyTemplate: '', authenticationType: 'anonymous',
};
const components: CanvasComponent[] = [
{ id: 'label', type: 'Label', name: 'status', position: { x: 0, y: 0 }, size: { width: 10, height: 10 }, properties: {} },
{ id: 'dropdown', type: 'Dropdown', name: 'choices', position: { x: 0, y: 0 }, size: { width: 10, height: 10 }, properties: {} },
{ id: 'input', type: 'TextInput', name: 'query', position: { x: 0, y: 0 }, size: { width: 10, height: 10 }, properties: {} },
];
const doc: ProjectDocument = {
schemaVersion: '0.1.0',
project: { id: 'p', name: 'P', pages: [], actions: [action], bindings: [], variables: {}, settings: {} },
};
test('creates canonical action-response bindings with onSuccess and a unique ID', () => {
const first = createResponseBinding([], action, 'components.status.value');
const second = createResponseBinding([first], action, 'variables.result');
expect(first).toEqual({ id: 'binding_response_1', source: 'actions.action_lookup.response.body', target: 'components.status.value', trigger: 'onSuccess' });
expect(second.id).toBe('binding_response_2');
});
test('offers only runtime-supported component targets plus variables', () => {
expect(responseTargetOptions(components, { result: { type: 'object' } })).toEqual([
{ value: 'components.status.value', label: 'status (Label.value)' },
{ value: 'components.choices.options', label: 'choices (Dropdown.options)' },
{ value: 'variables.result', label: 'result (variable)' },
]);
});
test('does not offer ambiguous duplicate component names as new targets', () => {
expect(responseTargetOptions([
components[0],
{ ...components[0], id: 'other-label' },
], {})).toEqual([]);
});
test('parses typed defaults and reports invalid declarations', () => {
expect(parseVariableDefault('string', '5')).toBe('5');
expect(parseVariableDefault('number', '5')).toBe(5);
expect(parseVariableDefault('boolean', 'false')).toBe(false);
expect(parseVariableDefault('array', '[1,2]')).toEqual([1, 2]);
expect(() => parseVariableDefault('object', '[]')).toThrow('JSON object');
expect(validateVariableName('bad.name', {})).not.toBeNull();
expect(validateVariableName('result', { result: { type: 'string' } })).toContain('already exists');
});
test('writes and removes variables and bindings without touching other canonical state', () => {
const withVariable = setVariable(doc, 'result', { type: 'string', defaultValue: 'ready' });
const binding = createResponseBinding([], action, 'variables.result');
const withBinding = { ...withVariable, project: { ...withVariable.project, bindings: [binding] } };
expect(removeBindingAt(withBinding, 0).project.bindings).toEqual([]);
expect(removeVariable(withVariable, 'result').project.variables).toEqual({});
expect(withVariable.project.actions).toBe(doc.project.actions);
});
test('finds binding and request-template references before variable deletion', () => {
const referenced: ProjectDocument = {
...doc,
project: {
...doc.project,
actions: [{ ...action, queryParameters: { item: '{{variables.result}}' }, pathParameters: { legacy: '{{variables.result}}' } }],
bindings: [{ id: 'b', source: 'actions.action_lookup.response', target: 'variables.result', trigger: 'onSuccess' }],
variables: { result: { type: 'string' } },
},
};
expect(findVariableReferences(referenced, 'result')).toEqual(['binding "b"', 'action "Lookup"']);
});