|
|
@@ -0,0 +1,127 @@
|
|
|
+package main
|
|
|
+
|
|
|
+import (
|
|
|
+ "strings"
|
|
|
+ "testing"
|
|
|
+)
|
|
|
+
|
|
|
+// A number is an effect ID, not a name. That is the whole rule: a user typing
|
|
|
+// digits means the number, and a name is never written as digits, so the two
|
|
|
+// cannot be confused.
|
|
|
+func TestEffectTakesAnIDAsWellAsAName(t *testing.T) {
|
|
|
+ tests := []struct {
|
|
|
+ name string
|
|
|
+ argument string
|
|
|
+ wantValue uint8
|
|
|
+ wantOutput string
|
|
|
+ }{
|
|
|
+ {"an ID the definition names", "13", 13, "Effect set to index 13"},
|
|
|
+ {"the ID past the end of the definition", "46", 46, "Effect set to index 46"},
|
|
|
+ {"ID 0, which two channels refuse", "0", 0, "Effect set to index 0"},
|
|
|
+ }
|
|
|
+ for _, tt := range tests {
|
|
|
+ t.Run(tt.name, func(t *testing.T) {
|
|
|
+ proto := &fakeZoneProtocol{failAt: -1}
|
|
|
+ t.Cleanup(vendoredDefinitions(t))
|
|
|
+
|
|
|
+ stdout, _, err := executeEffectCommand(t, proto, "Backlight", tt.argument)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatalf("effect %s: %v", tt.argument, err)
|
|
|
+ }
|
|
|
+ if !strings.Contains(stdout, tt.wantOutput) {
|
|
|
+ t.Errorf("stdout = %q, want it to contain %q", stdout, tt.wantOutput)
|
|
|
+ }
|
|
|
+ if len(proto.reports) != 1 {
|
|
|
+ t.Fatalf("reports = %v, want one write", proto.reports)
|
|
|
+ }
|
|
|
+ if got := proto.reports[0]; got.value != tt.wantValue || got.param != 0x02 {
|
|
|
+ t.Errorf("wrote value %d param 0x%02x, want %d param 0x02", got.value, got.param, tt.wantValue)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// The point of taking IDs on the same command: a board with no names is still
|
|
|
+// fully drivable, because an ID is the only thing it ever needed.
|
|
|
+func TestEffectTakesAnIDOnAKeyboardWithNoNames(t *testing.T) {
|
|
|
+ proto := &fakeZoneProtocol{failAt: -1}
|
|
|
+ t.Cleanup(definitionFlagRestore(t))
|
|
|
+ t.Cleanup(forceDefinitionsDir(t, t.TempDir()))
|
|
|
+
|
|
|
+ stdout, _, err := executeEffectCommand(t, proto, "Backlight", "46")
|
|
|
+ if err != nil {
|
|
|
+ t.Fatalf("effect 46 without a definition: %v", err)
|
|
|
+ }
|
|
|
+ if len(proto.reports) != 1 || proto.reports[0].value != 46 {
|
|
|
+ t.Errorf("reports = %v, want one write of 46", proto.reports)
|
|
|
+ }
|
|
|
+ if !strings.Contains(stdout, "Effect set to index 46") {
|
|
|
+ t.Errorf("stdout = %q, want the ID form reported", stdout)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// A number that cannot be an effect ID is a number that cannot be used, and the
|
|
|
+// message says so rather than looking for a name nobody wrote.
|
|
|
+func TestEffectSaysWhenAnIDIsOutOfRange(t *testing.T) {
|
|
|
+ proto := &fakeZoneProtocol{failAt: -1}
|
|
|
+ t.Cleanup(vendoredDefinitions(t))
|
|
|
+
|
|
|
+ _, _, err := executeEffectCommand(t, proto, "Backlight", "300")
|
|
|
+ if err == nil {
|
|
|
+ t.Fatal("effect 300 = nil error, want a range error")
|
|
|
+ }
|
|
|
+ if !strings.Contains(err.Error(), "0-255") {
|
|
|
+ t.Errorf("error = %q, want it to name the range", err)
|
|
|
+ }
|
|
|
+ if len(proto.reports) != 0 {
|
|
|
+ t.Errorf("reports = %v, want nothing written for a value that cannot be one", proto.reports)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// A name still resolves by name, and still goes through the alias table.
|
|
|
+func TestEffectStillTakesAName(t *testing.T) {
|
|
|
+ proto := &fakeZoneProtocol{failAt: -1}
|
|
|
+ t.Cleanup(vendoredDefinitions(t))
|
|
|
+
|
|
|
+ stdout, _, err := executeEffectCommand(t, proto, "Backlight", "rainbow_moving_chevron")
|
|
|
+ if err != nil {
|
|
|
+ t.Fatalf("effect by name: %v", err)
|
|
|
+ }
|
|
|
+ if len(proto.reports) != 1 || proto.reports[0].value != 17 {
|
|
|
+ t.Errorf("reports = %v, want one write of 17", proto.reports)
|
|
|
+ }
|
|
|
+ if stdout != "Effect set to \"rainbow_moving_chevron\"\n" {
|
|
|
+ t.Errorf("stdout = %q, want the plain success line", stdout)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// An unknown name is still an unknown name, and the message points at the ID form
|
|
|
+// rather than at a command that no longer exists.
|
|
|
+func TestUnknownNameErrorPointsAtTheIDForm(t *testing.T) {
|
|
|
+ proto := &fakeZoneProtocol{failAt: -1}
|
|
|
+ t.Cleanup(vendoredDefinitions(t))
|
|
|
+
|
|
|
+ _, _, err := executeEffectCommand(t, proto, "Backlight", "nonsense")
|
|
|
+ if err == nil {
|
|
|
+ t.Fatal("effect nonsense = nil error, want one")
|
|
|
+ }
|
|
|
+ if strings.Contains(err.Error(), "`mode") {
|
|
|
+ t.Errorf("error = %q, want no reference to a removed command", err)
|
|
|
+ }
|
|
|
+ if !strings.Contains(err.Error(), "effect <index>") {
|
|
|
+ t.Errorf("error = %q, want it to point at the ID form", err)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// The mode command is gone: an ID goes through the command that also takes names.
|
|
|
+func TestModeCommandIsGone(t *testing.T) {
|
|
|
+ root := newRootCommand()
|
|
|
+ registerCommands(root)
|
|
|
+ registerFlagCompletions(root)
|
|
|
+
|
|
|
+ for _, c := range root.Commands() {
|
|
|
+ if c.Name() == "mode" {
|
|
|
+ t.Fatal("the mode command is still registered; effect takes an ID now")
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|