package main import ( "bytes" "reflect" "strings" "testing" intrgb "netdome.biz/paul/qmk-rgb/internal/rgb" ) func TestResolveEffectTargetsBacklightOnly(t *testing.T) { targets, skipped, err := resolveEffectTargets("rainbow_moving_chevron", intrgb.AllZones()) if err != nil { t.Fatalf("resolveEffectTargets() unexpected error: %v", err) } wantTargets := []intrgb.EffectTarget{{Zone: intrgb.ZoneBacklight, ID: 17}} if !reflect.DeepEqual(targets, wantTargets) { t.Errorf("resolveEffectTargets() targets = %v, want %v", targets, wantTargets) } wantSkipped := []intrgb.Zone{intrgb.ZoneLogo, intrgb.ZoneSide} if !reflect.DeepEqual(skipped, wantSkipped) { t.Errorf("resolveEffectTargets() skipped = %v, want %v", skipped, wantSkipped) } } func TestResolveEffectTargetsBreathing(t *testing.T) { targets, skipped, err := resolveEffectTargets("breathing", intrgb.AllZones()) if err != nil { t.Fatalf("resolveEffectTargets() unexpected error: %v", err) } wantTargets := []intrgb.EffectTarget{ {Zone: intrgb.ZoneLogo, ID: 4}, {Zone: intrgb.ZoneBacklight, ID: 5}, {Zone: intrgb.ZoneSide, ID: 4}, } if !reflect.DeepEqual(targets, wantTargets) { t.Errorf("resolveEffectTargets() targets = %v, want %v", targets, wantTargets) } if len(skipped) != 0 { t.Errorf("resolveEffectTargets() skipped = %v, want none", skipped) } } func TestResolveEffectTargetsExplicitUnsupported(t *testing.T) { cases := []struct { name string zones []intrgb.Zone }{ {"rainbow_moving_chevron", []intrgb.Zone{intrgb.ZoneLogo}}, {"rainbow_wave", []intrgb.Zone{intrgb.ZoneBacklight}}, {"splash", []intrgb.Zone{intrgb.ZoneSide}}, } for _, tc := range cases { t.Run(tc.name+string(tc.zones[0]), func(t *testing.T) { targets, skipped, err := resolveEffectTargets(tc.name, tc.zones) if err == nil { t.Fatal("resolveEffectTargets() expected error, got nil") } if len(targets) != 0 { t.Errorf("resolveEffectTargets() targets = %v, want none", targets) } if len(skipped) != 0 { t.Errorf("resolveEffectTargets() skipped = %v, want none", skipped) } }) } } func TestResolveEffectTargetsUnknownName(t *testing.T) { targets, skipped, err := resolveEffectTargets("not_an_effect", intrgb.AllZones()) if err == nil { t.Fatal("resolveEffectTargets() expected error, got nil") } if len(targets) != 0 { t.Errorf("resolveEffectTargets() targets = %v, want none", targets) } if len(skipped) != 0 { t.Errorf("resolveEffectTargets() skipped = %v, want none", skipped) } } func TestResolveEffectTargetsStaticCompatibility(t *testing.T) { targets, skipped, err := resolveEffectTargets("static", intrgb.AllZones()) if err != nil { t.Fatalf("resolveEffectTargets() unexpected error: %v", err) } wantTargets := []intrgb.EffectTarget{ {Zone: intrgb.ZoneLogo, ID: 5}, {Zone: intrgb.ZoneBacklight, ID: 1}, {Zone: intrgb.ZoneSide, ID: 5}, } if !reflect.DeepEqual(targets, wantTargets) { t.Errorf("resolveEffectTargets() targets = %v, want %v", targets, wantTargets) } if len(skipped) != 0 { t.Errorf("resolveEffectTargets() skipped = %v, want none", skipped) } } func executeEffectCommand(t *testing.T, protocol *fakeZoneProtocol, target string, args ...string) (string, string, error) { t.Helper() originalOpen := openRGBProtocol originalZone := targetZone openRGBProtocol = func() (rgbProtocol, error) { return protocol, nil } targetZone = target t.Cleanup(func() { openRGBProtocol = originalOpen targetZone = originalZone }) var stdout bytes.Buffer var stderr bytes.Buffer cmd := NewEffectCmd() cmd.SetOut(&stdout) cmd.SetErr(&stderr) cmd.SilenceErrors = true cmd.SilenceUsage = true cmd.SetArgs(args) err := cmd.Execute() return stdout.String(), stderr.String(), err } func TestEffectCommandWarnsForSkippedZones(t *testing.T) { protocol := &fakeZoneProtocol{failAt: -1} stdout, stderr, err := executeEffectCommand(t, protocol, "", "rainbow_moving_chevron") if err != nil { t.Fatalf("Execute() unexpected error: %v", err) } if !strings.Contains(stderr, "effect not supported on zone(s): logo, side") { t.Errorf("stderr = %q, want skipped-zone warning", stderr) } if !strings.Contains(stdout, `Effect set to "rainbow_moving_chevron"`) { t.Errorf("stdout = %q, want success output", stdout) } if len(protocol.reports) != 1 { t.Fatalf("reports = %v, want one Backlight report", protocol.reports) } if protocol.reports[0].channel != 3 || protocol.reports[0].param != 2 || protocol.reports[0].value != 17 { t.Errorf("report = %+v, want Backlight Effect 17", protocol.reports[0]) } } func TestEffectCommandRejectsBeforeOpeningProtocol(t *testing.T) { protocol := &fakeZoneProtocol{failAt: -1} stdout, _, err := executeEffectCommand(t, protocol, "logo", "rainbow_moving_chevron") if err == nil { t.Fatal("Execute() expected error, got nil") } if len(protocol.reports) != 0 { t.Errorf("reports = %v, want no writes before rejection", protocol.reports) } if stdout != "" { t.Errorf("stdout = %q, want no success output", stdout) } } func TestEffectCommandSuppressesSuccessAfterLaterWriteFailure(t *testing.T) { protocol := &fakeZoneProtocol{failAt: 1} stdout, _, err := executeEffectCommand(t, protocol, "", "breathing") if err == nil { t.Fatal("Execute() expected write error, got nil") } if len(protocol.reports) != 1 { t.Fatalf("reports = %v, want one report before failure", protocol.reports) } if strings.Contains(stdout, "Effect set") { t.Errorf("stdout = %q, want success output suppressed", stdout) } }