Skip to content

Commit 11d904b

Browse files
committed
add unit tests + fix eslint issues
Signed-off-by: Lucas Pinheiro <lucas@superplane.com>
1 parent a27c42a commit 11d904b

5 files changed

Lines changed: 261 additions & 9 deletions

File tree

pkg/grpc/actions/common.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -681,7 +681,7 @@ func ProtoToNodes(registry *registry.Registry, nodes []*componentpb.Node) []mode
681681
warningMessage = node.WarningMessage
682682
}
683683

684-
nodeType, nodeRef := ComponentToNodeTypeAndRef(registry, node.Type, node.Component)
684+
nodeType, nodeRef := ComponentToNodeTypeAndRef(node.Type, node.Component)
685685

686686
//
687687
// NOTE: we do not include metadata in here,
@@ -705,7 +705,7 @@ func ProtoToNodes(registry *registry.Registry, nodes []*componentpb.Node) []mode
705705
return result
706706
}
707707

708-
func ComponentToNodeTypeAndRef(registry *registry.Registry, nodeType componentpb.Node_Type, component string) (string, *models.NodeRef) {
708+
func ComponentToNodeTypeAndRef(nodeType componentpb.Node_Type, component string) (string, *models.NodeRef) {
709709
switch nodeType {
710710
case componentpb.Node_TYPE_ACTION:
711711
return models.NodeTypeComponent, &models.NodeRef{

pkg/registry/registry_test.go

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/stretchr/testify/assert"
88
"github.com/stretchr/testify/require"
99
"github.com/superplanehq/superplane/pkg/core"
10+
"github.com/superplanehq/superplane/pkg/models"
1011
"github.com/superplanehq/superplane/pkg/registry"
1112
supportcontexts "github.com/superplanehq/superplane/test/support/contexts"
1213
"github.com/superplanehq/superplane/test/support/impl"
@@ -260,3 +261,207 @@ func TestRegistry_FindIntegrationHook(t *testing.T) {
260261
assert.Contains(t, err.Error(), "hook missing-hook not found for integration unit_integration")
261262
})
262263
}
264+
265+
func TestRegistry_FindConfigurableComponent(t *testing.T) {
266+
t.Run("finds action", func(t *testing.T) {
267+
action := impl.NewDummyAction(impl.DummyActionOptions{Name: "unit_action"})
268+
269+
r := &registry.Registry{
270+
Actions: map[string]core.Action{"unit_action": registry.NewPanicableAction(action)},
271+
Triggers: map[string]core.Trigger{},
272+
Integrations: map[string]core.Integration{},
273+
Widgets: map[string]core.Widget{},
274+
}
275+
276+
component, err := r.FindConfigurableComponent("unit_action")
277+
require.NoError(t, err)
278+
279+
foundAction, ok := component.(core.Action)
280+
require.True(t, ok)
281+
assert.Equal(t, "unit_action", foundAction.Name())
282+
})
283+
284+
t.Run("finds integration action", func(t *testing.T) {
285+
action := impl.NewDummyAction(impl.DummyActionOptions{Name: "unitapp.action"})
286+
integration := impl.NewDummyIntegration(impl.DummyIntegrationOptions{
287+
Actions: []core.Action{action},
288+
})
289+
290+
r := &registry.Registry{
291+
Actions: map[string]core.Action{},
292+
Triggers: map[string]core.Trigger{},
293+
Integrations: map[string]core.Integration{"unitapp": registry.NewPanicableIntegration(integration)},
294+
Widgets: map[string]core.Widget{},
295+
}
296+
297+
component, err := r.FindConfigurableComponent("unitapp.action")
298+
require.NoError(t, err)
299+
300+
foundAction, ok := component.(core.Action)
301+
require.True(t, ok)
302+
assert.Equal(t, "unitapp.action", foundAction.Name())
303+
})
304+
305+
t.Run("finds trigger", func(t *testing.T) {
306+
trigger := impl.NewDummyTrigger(impl.DummyTriggerOptions{Name: "unit_trigger"})
307+
308+
r := &registry.Registry{
309+
Actions: map[string]core.Action{},
310+
Triggers: map[string]core.Trigger{"unit_trigger": registry.NewPanicableTrigger(trigger)},
311+
Integrations: map[string]core.Integration{},
312+
Widgets: map[string]core.Widget{},
313+
}
314+
315+
component, err := r.FindConfigurableComponent("unit_trigger")
316+
require.NoError(t, err)
317+
318+
foundTrigger, ok := component.(core.Trigger)
319+
require.True(t, ok)
320+
assert.Equal(t, "unit_trigger", foundTrigger.Name())
321+
})
322+
323+
t.Run("finds widget", func(t *testing.T) {
324+
widget := impl.NewDummyWidget(impl.DummyWidgetOptions{Name: "unit_widget"})
325+
326+
r := &registry.Registry{
327+
Actions: map[string]core.Action{},
328+
Triggers: map[string]core.Trigger{},
329+
Integrations: map[string]core.Integration{},
330+
Widgets: map[string]core.Widget{"unit_widget": widget},
331+
}
332+
333+
component, err := r.FindConfigurableComponent("unit_widget")
334+
require.NoError(t, err)
335+
336+
foundWidget, ok := component.(core.Widget)
337+
require.True(t, ok)
338+
assert.Equal(t, "unit_widget", foundWidget.Name())
339+
})
340+
341+
t.Run("prefers action when action and trigger share the same name", func(t *testing.T) {
342+
action := impl.NewDummyAction(impl.DummyActionOptions{Name: "shared_name"})
343+
trigger := impl.NewDummyTrigger(impl.DummyTriggerOptions{Name: "shared_name"})
344+
345+
r := &registry.Registry{
346+
Actions: map[string]core.Action{"shared_name": registry.NewPanicableAction(action)},
347+
Triggers: map[string]core.Trigger{"shared_name": registry.NewPanicableTrigger(trigger)},
348+
Integrations: map[string]core.Integration{},
349+
Widgets: map[string]core.Widget{"shared_name": impl.NewDummyWidget(impl.DummyWidgetOptions{Name: "shared_name"})},
350+
}
351+
352+
component, err := r.FindConfigurableComponent("shared_name")
353+
require.NoError(t, err)
354+
355+
foundAction, ok := component.(core.Action)
356+
require.True(t, ok)
357+
assert.Equal(t, "shared_name", foundAction.Name())
358+
})
359+
360+
t.Run("returns error when component is missing", func(t *testing.T) {
361+
r := &registry.Registry{
362+
Actions: map[string]core.Action{},
363+
Triggers: map[string]core.Trigger{},
364+
Integrations: map[string]core.Integration{},
365+
Widgets: map[string]core.Widget{},
366+
}
367+
368+
_, err := r.FindConfigurableComponent("missing_component")
369+
require.Error(t, err)
370+
assert.EqualError(t, err, "component missing_component not found")
371+
})
372+
}
373+
374+
func TestRegistry_ComponentType(t *testing.T) {
375+
t.Run("returns component for action", func(t *testing.T) {
376+
action := impl.NewDummyAction(impl.DummyActionOptions{Name: "unit_action"})
377+
378+
r := &registry.Registry{
379+
Actions: map[string]core.Action{"unit_action": registry.NewPanicableAction(action)},
380+
Triggers: map[string]core.Trigger{},
381+
Integrations: map[string]core.Integration{},
382+
Widgets: map[string]core.Widget{},
383+
}
384+
385+
componentType, err := r.ComponentType("unit_action")
386+
require.NoError(t, err)
387+
assert.Equal(t, models.NodeTypeComponent, componentType)
388+
})
389+
390+
t.Run("returns component for integration action", func(t *testing.T) {
391+
action := impl.NewDummyAction(impl.DummyActionOptions{Name: "unitapp.action"})
392+
integration := impl.NewDummyIntegration(impl.DummyIntegrationOptions{
393+
Actions: []core.Action{action},
394+
})
395+
396+
r := &registry.Registry{
397+
Actions: map[string]core.Action{},
398+
Triggers: map[string]core.Trigger{},
399+
Integrations: map[string]core.Integration{"unitapp": registry.NewPanicableIntegration(integration)},
400+
Widgets: map[string]core.Widget{},
401+
}
402+
403+
componentType, err := r.ComponentType("unitapp.action")
404+
require.NoError(t, err)
405+
assert.Equal(t, models.NodeTypeComponent, componentType)
406+
})
407+
408+
t.Run("returns trigger for trigger", func(t *testing.T) {
409+
trigger := impl.NewDummyTrigger(impl.DummyTriggerOptions{Name: "unit_trigger"})
410+
411+
r := &registry.Registry{
412+
Actions: map[string]core.Action{},
413+
Triggers: map[string]core.Trigger{"unit_trigger": registry.NewPanicableTrigger(trigger)},
414+
Integrations: map[string]core.Integration{},
415+
Widgets: map[string]core.Widget{},
416+
}
417+
418+
componentType, err := r.ComponentType("unit_trigger")
419+
require.NoError(t, err)
420+
assert.Equal(t, models.NodeTypeTrigger, componentType)
421+
})
422+
423+
t.Run("returns widget for widget", func(t *testing.T) {
424+
widget := impl.NewDummyWidget(impl.DummyWidgetOptions{Name: "unit_widget"})
425+
426+
r := &registry.Registry{
427+
Actions: map[string]core.Action{},
428+
Triggers: map[string]core.Trigger{},
429+
Integrations: map[string]core.Integration{},
430+
Widgets: map[string]core.Widget{"unit_widget": widget},
431+
}
432+
433+
componentType, err := r.ComponentType("unit_widget")
434+
require.NoError(t, err)
435+
assert.Equal(t, models.NodeTypeWidget, componentType)
436+
})
437+
438+
t.Run("prefers component when action and trigger share the same name", func(t *testing.T) {
439+
action := impl.NewDummyAction(impl.DummyActionOptions{Name: "shared_name"})
440+
trigger := impl.NewDummyTrigger(impl.DummyTriggerOptions{Name: "shared_name"})
441+
442+
r := &registry.Registry{
443+
Actions: map[string]core.Action{"shared_name": registry.NewPanicableAction(action)},
444+
Triggers: map[string]core.Trigger{"shared_name": registry.NewPanicableTrigger(trigger)},
445+
Integrations: map[string]core.Integration{},
446+
Widgets: map[string]core.Widget{"shared_name": impl.NewDummyWidget(impl.DummyWidgetOptions{Name: "shared_name"})},
447+
}
448+
449+
componentType, err := r.ComponentType("shared_name")
450+
require.NoError(t, err)
451+
assert.Equal(t, models.NodeTypeComponent, componentType)
452+
})
453+
454+
t.Run("returns error when component is missing", func(t *testing.T) {
455+
r := &registry.Registry{
456+
Actions: map[string]core.Action{},
457+
Triggers: map[string]core.Trigger{},
458+
Integrations: map[string]core.Integration{},
459+
Widgets: map[string]core.Widget{},
460+
}
461+
462+
componentType, err := r.ComponentType("missing_component")
463+
require.Error(t, err)
464+
assert.Empty(t, componentType)
465+
assert.EqualError(t, err, "component missing_component not found")
466+
})
467+
}

test/support/impl/widget.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package impl
2+
3+
import (
4+
"github.com/superplanehq/superplane/pkg/configuration"
5+
)
6+
7+
type DummyWidgetOptions struct {
8+
Name string
9+
}
10+
11+
type DummyWidget struct {
12+
name string
13+
}
14+
15+
func NewDummyWidget(options DummyWidgetOptions) *DummyWidget {
16+
name := options.Name
17+
if name == "" {
18+
name = "dummy"
19+
}
20+
21+
return &DummyWidget{
22+
name: name,
23+
}
24+
}
25+
26+
func (w *DummyWidget) Name() string {
27+
return w.name
28+
}
29+
30+
func (w *DummyWidget) Label() string {
31+
return "dummy"
32+
}
33+
34+
func (w *DummyWidget) Description() string {
35+
return "Just a dummy widget used in unit tests"
36+
}
37+
38+
func (w *DummyWidget) Icon() string {
39+
return "dummy"
40+
}
41+
42+
func (w *DummyWidget) Color() string {
43+
return "dummy"
44+
}
45+
46+
func (w *DummyWidget) Configuration() []configuration.Field {
47+
return nil
48+
}
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
{
2-
"maxAllowedTotalIssues": 741,
2+
"maxAllowedTotalIssues": 738,
33
"maxAllowedByRule": {
4-
"complexity": 238,
4+
"complexity": 236,
55
"@typescript-eslint/no-explicit-any": 170,
66
"@typescript-eslint/no-non-null-asserted-optional-chain": 83,
77
"max-lines-per-function": 79,
88
"max-statements": 77,
99
"react-hooks/exhaustive-deps": 38,
1010
"react-refresh/only-export-components": 24,
11-
"max-lines": 19,
11+
"max-lines": 18,
1212
"max-depth": 7,
1313
"max-params": 6
1414
},
15-
"updatedAt": "2026-04-25T21:03:04.731Z"
15+
"updatedAt": "2026-04-26T20:02:23.158Z"
1616
}

web_src/src/ui/componentSidebar/pages/ExecutionChainPage.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,6 @@ export const ExecutionChainPage: React.FC<ExecutionChainPageProps> = ({
255255
.map((childExec: any) => {
256256
let badgeColor = "bg-gray-400";
257257
const componentName = childExec.nodeName || childExec.nodeId || "Unknown";
258-
let componentIcon = "box";
259258

260259
if (getExecutionState) {
261260
const { map, state } = getExecutionState(exec.nodeId, childExec);
@@ -268,7 +267,7 @@ export const ExecutionChainPage: React.FC<ExecutionChainPageProps> = ({
268267
executionId: childExec.id || "",
269268
badgeColor,
270269
backgroundColor: map[state]?.backgroundColor,
271-
componentIcon: "box"
270+
componentIcon: "box",
272271
};
273272
}
274273

@@ -278,7 +277,7 @@ export const ExecutionChainPage: React.FC<ExecutionChainPageProps> = ({
278277
nodeId: childExec.nodeId || "",
279278
executionId: childExec.id || "",
280279
badgeColor,
281-
componentIcon: "box"
280+
componentIcon: "box",
282281
};
283282
});
284283
}

0 commit comments

Comments
 (0)