rgb_test.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. package main
  2. import (
  3. "bytes"
  4. "reflect"
  5. "strings"
  6. "testing"
  7. "github.com/spf13/cobra"
  8. intdevice "netdome.biz/paul/qmk-rgb/internal/device"
  9. "netdome.biz/paul/qmk-rgb/internal/via"
  10. )
  11. func TestLightingEnabledRequiresActiveEffectAndBrightness(t *testing.T) {
  12. cases := []struct {
  13. name string
  14. mode uint8
  15. brightness uint8
  16. want bool
  17. }{
  18. {"active", 1, 160, true},
  19. {"zero brightness", 1, 0, false},
  20. {"disabled effect", 0, 160, false},
  21. {"both zero", 0, 0, false},
  22. }
  23. for _, tc := range cases {
  24. t.Run(tc.name, func(t *testing.T) {
  25. if got := lightingEnabled(tc.mode, tc.brightness); got != tc.want {
  26. t.Errorf("lightingEnabled(%d, %d) = %t, want %t", tc.mode, tc.brightness, got, tc.want)
  27. }
  28. })
  29. }
  30. }
  31. // stubOneImpact80 puts one keyboard in front of the root command's validation,
  32. // which now resolves the target before a subcommand runs.
  33. func stubOneImpact80(t *testing.T) {
  34. t.Helper()
  35. stubDiscovery(t, []intdevice.Device{{Index: 1, Path: "hid-device-0", VendorID: 0x36B0, ProductID: 0x309F}})
  36. }
  37. func TestDeviceFlagIsInheritedByCommands(t *testing.T) {
  38. stubOneImpact80(t)
  39. ran := false
  40. cmd := newRootCommand()
  41. cmd.AddCommand(&cobra.Command{
  42. Use: "probe",
  43. Run: func(*cobra.Command, []string) { ran = true },
  44. })
  45. cmd.SetArgs([]string{"--device", "1", "probe"})
  46. if err := cmd.Execute(); err != nil {
  47. t.Fatalf("Execute() error = %v", err)
  48. }
  49. if !ran {
  50. t.Fatal("child command did not run")
  51. }
  52. if targetDevice != "1" {
  53. t.Errorf("targetDevice = %q, want %q", targetDevice, "1")
  54. }
  55. }
  56. func TestResolveChannelsDefaultsToEveryDetectedChannel(t *testing.T) {
  57. proto := &verifyingProtocol{}
  58. got, err := resolveChannels(proto, targetDeviceData{})
  59. if err != nil {
  60. t.Fatalf("resolveChannels() unexpected error: %v", err)
  61. }
  62. want := []via.Channel{via.ChannelRgblight, via.ChannelRgbMatrix, via.ChannelAudio}
  63. if !reflect.DeepEqual(got, want) {
  64. t.Errorf("resolveChannels() = %v, want %v", got, want)
  65. }
  66. }
  67. func TestResolveChannelsRejectsAChannelTheKeyboardDoesNotHave(t *testing.T) {
  68. proto := &verifyingProtocol{channels: []via.Channel{via.ChannelRgblight}}
  69. target := targetDeviceData{Requested: []via.Channel{via.ChannelAudio}}
  70. if _, err := resolveChannels(proto, target); err == nil {
  71. t.Fatal("resolveChannels() expected an error, want the missing channel reported")
  72. }
  73. }
  74. func TestResolveChannelsRejectsAShadowedDisplayName(t *testing.T) {
  75. proto := &verifyingProtocol{channels: []via.Channel{via.ChannelBacklight, via.ChannelRgbMatrix}}
  76. target := targetDeviceData{Display: map[uint16]string{3: "backlight"}}
  77. if _, err := resolveChannels(proto, target); err == nil {
  78. t.Fatal("resolveChannels() expected an error, want the shadowed name reported")
  79. }
  80. }
  81. func TestResolveChannelsReportsWhenNoChannelIsPresent(t *testing.T) {
  82. proto := &verifyingProtocol{channels: []via.Channel{}}
  83. if _, err := resolveChannels(proto, targetDeviceData{}); err == nil {
  84. t.Fatal("resolveChannels() expected an error when the keyboard has no channels")
  85. }
  86. }
  87. // The zone is a positional argument, so it is spelled only where a command reads
  88. // one. A flag the root carries is a flag every help lists, which told `keyboard
  89. // info` about a channel it cannot address and then ignored it.
  90. func TestNoCommandCarriesAZoneFlag(t *testing.T) {
  91. root := newRootCommand()
  92. registerCommands(root)
  93. var found []string
  94. var walk func(*cobra.Command)
  95. walk = func(cmd *cobra.Command) {
  96. for _, name := range []string{"zone", "all"} {
  97. if cmd.Flags().Lookup(name) != nil || cmd.PersistentFlags().Lookup(name) != nil {
  98. found = append(found, cmd.CommandPath()+" --"+name)
  99. }
  100. }
  101. for _, sub := range cmd.Commands() {
  102. walk(sub)
  103. }
  104. }
  105. walk(root)
  106. if len(found) > 0 {
  107. t.Errorf("zone flags still registered: %v; the zone is a positional argument", found)
  108. }
  109. }
  110. // A command that names no channel must not accept one, and one that does must
  111. // show it in its usage line, which is where a positional argument is documented.
  112. func TestZoneIsSpelledOnlyWhereACommandReadsOne(t *testing.T) {
  113. usageOf := func(args ...string) string {
  114. var out bytes.Buffer
  115. cmd := newRootCommand()
  116. registerCommands(cmd)
  117. cmd.SetOut(&out)
  118. cmd.SetErr(&out)
  119. cmd.SetArgs(args)
  120. if err := cmd.Execute(); err != nil {
  121. t.Fatalf("%v --help returned error: %v", args, err)
  122. }
  123. return out.String()
  124. }
  125. for _, args := range [][]string{{"effect", "--help"}, {"brightness", "--help"}, {"load", "--help"}} {
  126. if !strings.Contains(usageOf(args...), "zone") {
  127. t.Errorf("%v --help does not show a zone argument", args)
  128. }
  129. }
  130. // list, delete and the keyboard commands take a name or nothing, and a zone
  131. // in their usage line would promise a channel they never read.
  132. for _, args := range [][]string{{"list", "--help"}, {"keyboard", "info", "--help"}} {
  133. if strings.Contains(usageOf(args...), "<zone>") {
  134. t.Errorf("%v --help shows a zone argument, want none", args)
  135. }
  136. }
  137. }
  138. // Precedence, documented because two errors can apply at once: a board that
  139. // cannot be selected is reported first, since a zone name is resolved against
  140. // the board's own names.
  141. func TestNoKeyboardIsReportedBeforeAnUnknownZone(t *testing.T) {
  142. stubDiscovery(t, []intdevice.Device{})
  143. _, _, _, err := openTarget("matx")
  144. if err == nil {
  145. t.Fatal("openTarget() expected an error, got nil")
  146. }
  147. if !strings.Contains(err.Error(), "no QMK keyboard found") {
  148. t.Errorf("openTarget() error = %v, want the missing keyboard reported", err)
  149. }
  150. }