diff --git a/src/__tests__/editor-integration.test.ts b/src/__tests__/editor-integration.test.ts index 00dc463..1942b9b 100644 --- a/src/__tests__/editor-integration.test.ts +++ b/src/__tests__/editor-integration.test.ts @@ -86,6 +86,29 @@ triggers: {} expect(names).toContain('health-handler'); }); + it('round-trips module satisfies keys for derived IaC modules', () => { + const { config, yaml } = roundTrip(` +modules: + - name: otel-collector + type: infra.otel_collector + satisfies: + - observability.telemetry.default + - observability.metrics.default + config: + endpoint: "\${OTEL_EXPORTER_OTLP_ENDPOINT}" +workflows: {} +triggers: {} +`); + + const collector = config.modules.find((m) => m.name === 'otel-collector'); + expect(collector?.satisfies).toEqual([ + 'observability.telemetry.default', + 'observability.metrics.default', + ]); + expect(yaml).toContain('satisfies:'); + expect(yaml).toContain('observability.telemetry.default'); + }); + it('adding a node and exporting produces 4 modules', () => { const { nodes, edges, parsed } = roundTrip(httpYaml); // Simulate adding a middleware node diff --git a/src/components/properties/PropertyPanel.test.tsx b/src/components/properties/PropertyPanel.test.tsx index 6cce058..fffab09 100644 --- a/src/components/properties/PropertyPanel.test.tsx +++ b/src/components/properties/PropertyPanel.test.tsx @@ -133,6 +133,55 @@ describe('PropertyPanel', () => { expect(updatedNode?.data.label).toBe('My Custom Server'); }); + it('edits module satisfies keys outside config', () => { + act(() => { + useModuleSchemaStore.getState().loadEditorBundle({ + version: 'editor-bundle/v1', + moduleSchemas: { + 'infra.otel_collector': { + type: 'infra.otel_collector', + label: 'OTel Collector', + category: 'observability', + configFields: [], + defaultConfig: {}, + }, + }, + coercionRules: {}, + contracts: {}, + messages: {}, + schemas: { app: {} }, + }); + useWorkflowStore.setState({ + nodes: [{ + id: 'otel-collector', + type: 'observabilityNode', + position: { x: 0, y: 0 }, + data: { + moduleType: 'infra.otel_collector', + label: 'otel-collector', + config: {}, + satisfies: ['observability.telemetry.default'], + }, + }], + selectedNodeId: 'otel-collector', + }); + }); + + render(); + + const satisfiesInput = screen.getByLabelText('Satisfies'); + fireEvent.change(satisfiesInput, { + target: { value: 'observability.telemetry.default\nobservability.metrics.default' }, + }); + + const updatedNode = useWorkflowStore.getState().nodes.find((n) => n.id === 'otel-collector'); + expect(updatedNode?.data.satisfies).toEqual([ + 'observability.telemetry.default', + 'observability.metrics.default', + ]); + expect(updatedNode?.data.config.satisfies).toBeUndefined(); + }); + it('close button clears selection', () => { act(() => { useWorkflowStore.getState().addNode('http.server', { x: 0, y: 0 }); diff --git a/src/components/properties/PropertyPanel.tsx b/src/components/properties/PropertyPanel.tsx index fd0c182..0fde2f3 100644 --- a/src/components/properties/PropertyPanel.tsx +++ b/src/components/properties/PropertyPanel.tsx @@ -59,6 +59,7 @@ export default function PropertyPanel() { const selectedNodeId = useWorkflowStore((s) => s.selectedNodeId); const updateNodeConfig = useWorkflowStore((s) => s.updateNodeConfig); const updateNodeName = useWorkflowStore((s) => s.updateNodeName); + const updateNodeSatisfies = useWorkflowStore((s) => s.updateNodeSatisfies); const removeNode = useWorkflowStore((s) => s.removeNode); const setSelectedNode = useWorkflowStore((s) => s.setSelectedNode); @@ -477,6 +478,18 @@ export default function PropertyPanel() { )} + {/* Module requirement keys */} +