| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322 |
- package main
- import (
- "bytes"
- "strings"
- "testing"
- intdevice "netdome.biz/paul/qmk-rgb/internal/device"
- "netdome.biz/paul/qmk-rgb/internal/via"
- )
- func TestResolveZoneNameAcceptsASubsystemName(t *testing.T) {
- got, err := resolveZoneName("rgb_matrix", map[uint16]string{2: "logo"})
- if err != nil {
- t.Fatalf("resolveZoneName() error = %v", err)
- }
- if len(got) != 1 || got[0] != via.ChannelRgbMatrix {
- t.Errorf("resolveZoneName() = %v, want [3]", got)
- }
- }
- func TestResolveZoneNameAcceptsADisplayName(t *testing.T) {
- got, err := resolveZoneName("logo", map[uint16]string{2: "logo", 3: "backlight", 4: "side"})
- if err != nil {
- t.Fatalf("resolveZoneName() error = %v", err)
- }
- if len(got) != 1 || got[0] != via.ChannelRgblight {
- t.Errorf("resolveZoneName() = %v, want [2]", got)
- }
- }
- func TestResolveZoneNameRejectsAnUnknownName(t *testing.T) {
- _, err := resolveZoneName("nope", map[uint16]string{2: "logo"})
- if err == nil {
- t.Fatal("resolveZoneName() expected an error, got nil")
- }
- if !strings.Contains(err.Error(), "rgb_matrix") {
- t.Errorf("error = %q, want it to name an accepted form", err)
- }
- }
- // A board that supplies no display names keeps the subsystem vocabulary, so the
- // physical names that work on the Impact 80 do not work elsewhere.
- func TestResolveZoneNameIgnoresDisplayNamesForAnotherBoard(t *testing.T) {
- if _, err := resolveZoneName("logo", nil); err == nil {
- t.Fatal("resolveZoneName(\"logo\", nil) expected an error, got nil")
- }
- }
- // Two channels carrying the same display name are ambiguous, and the tool
- // reports that rather than picking one. Whether a display name shadows another
- // channel's subsystem name cannot be decided here, because presence is only
- // known after the probe; displayNameConflicts decides that.
- func TestResolveZoneNameReportsAnAmbiguousName(t *testing.T) {
- _, err := resolveZoneName("backlight", map[uint16]string{2: "logo", 3: "backlight", 4: "backlight"})
- if err == nil {
- t.Fatal("resolveZoneName() expected an error for a name two channels answer to")
- }
- if !strings.Contains(err.Error(), "backlight") {
- t.Errorf("error = %q, want it to name the conflicting name", err)
- }
- }
- func TestDisplayNameConflictsRejectsAShadowedSubsystemName(t *testing.T) {
- // Channel 1 is present and its subsystem is "backlight", while the file
- // also calls channel 3 "backlight": two channels, one name.
- err := displayNameConflicts(
- map[uint16]string{3: "backlight"},
- []via.Channel{via.ChannelBacklight, via.ChannelRgbMatrix},
- )
- if err == nil {
- t.Fatal("displayNameConflicts() expected an error, got nil")
- }
- if !strings.Contains(err.Error(), "backlight") {
- t.Errorf("error = %q, want it to name the conflicting name", err)
- }
- }
- func TestDisplayNameConflictsAllowsTheImpact80Naming(t *testing.T) {
- // The Impact 80 calls channel 3 "backlight" and has no channel 1, so
- // nothing shadows anything.
- err := displayNameConflicts(
- map[uint16]string{2: "logo", 3: "backlight", 4: "side"},
- []via.Channel{via.ChannelRgblight, via.ChannelRgbMatrix, via.ChannelAudio},
- )
- if err != nil {
- t.Fatalf("displayNameConflicts() error = %v, want nil", err)
- }
- }
- func TestChannelNamePrefersTheDisplayName(t *testing.T) {
- display := map[uint16]string{2: "logo"}
- if got := channelName(via.ChannelRgblight, display); got != "logo" {
- t.Errorf("channelName(2) = %q, want %q", got, "logo")
- }
- if got := channelName(via.ChannelRgbMatrix, display); got != "rgb_matrix" {
- t.Errorf("channelName(3) = %q, want %q", got, "rgb_matrix")
- }
- }
- // The display names must come from the keyboard the command targets, not from
- // whichever one enumeration returned first.
- func TestPrepareTargetUsesTheSelectedKeyboard(t *testing.T) {
- devices := []intdevice.Device{
- {VendorID: 0x6666, ProductID: 0x0001},
- {VendorID: 0x36B0, ProductID: 0x309F, Name: "Wobkey Impact 80"},
- }
- originalDiscover := discoverAll
- originalKeyboardFor := keyboardFor
- originalTarget := targetDevice
- originalZone := targetZone
- t.Cleanup(func() {
- discoverAll = originalDiscover
- keyboardFor = originalKeyboardFor
- targetDevice = originalTarget
- targetZone = originalZone
- })
- discoverAll = func() ([]intdevice.Device, error) { return devices, nil }
- keyboardFor = func(vendorID, productID uint16) (intdevice.Keyboard, bool, error) {
- switch {
- case vendorID == 0x6666:
- return intdevice.Keyboard{Name: "Wobkey Rainy 75", Channels: map[uint16]string{2: "deck"}}, true, nil
- case vendorID == 0x36B0:
- return intdevice.Keyboard{Name: "Wobkey Impact 80", Channels: map[uint16]string{2: "logo"}}, true, nil
- }
- return intdevice.Keyboard{}, false, nil
- }
- targetDevice = "2"
- targetZone = "logo"
- got, err := prepareTarget()
- if err != nil {
- t.Fatalf("prepareTarget() error = %v", err)
- }
- if got.Display[2] != "logo" {
- t.Errorf("display names = %v, want the Impact 80's", got.Display)
- }
- }
- // A board renamed after a profile was written leaves keys that resolve to
- // nothing. Each one must be reported by name, and nothing may be written.
- func TestLoadWarnsForEveryUnresolvableZoneKey(t *testing.T) {
- dir := t.TempDir()
- profile := Profile{
- Name: "renamed",
- Version: 1,
- Zones: map[string]*ZoneSettings{
- "old-logo": {Enabled: true, Effect: "light", Brightness: 100, Speed: 1, Color: "00ff"},
- "old-backlight": {Enabled: true, Effect: "wave", Brightness: 100, Speed: 1, Color: "00ff"},
- },
- }
- originalDir := profilesDir
- t.Cleanup(func() { profilesDir = originalDir })
- profilesDir = dir
- if err := profile.Save(); err != nil {
- t.Fatalf("save profile: %v", err)
- }
- proto := &verifyingProtocol{}
- t.Cleanup(stubTargetForProfileTest(t, proto, "", impact80Display()))
- var stderr bytes.Buffer
- cmd := NewProfileLoadCmd()
- cmd.SetOut(&bytes.Buffer{})
- cmd.SetErr(&stderr)
- cmd.SetArgs([]string{"renamed"})
- if err := cmd.Execute(); err != nil {
- t.Fatalf("load returned error: %v", err)
- }
- for _, want := range []string{"old-logo", "old-backlight"} {
- if !strings.Contains(stderr.String(), want) {
- t.Errorf("stderr = %q, want it to name the unresolved key %q", stderr.String(), want)
- }
- }
- if len(proto.reports) != 0 {
- t.Errorf("reports = %v, want nothing written for unresolvable keys", proto.reports)
- }
- }
- // stubTargetForProfileTest points the profile commands at one protocol, one
- // profile directory and one --zone value, and returns the restore function.
- func stubTargetForProfileTest(t *testing.T, proto rgbProtocol, zoneFlag string, display map[uint16]string) func() {
- t.Helper()
- originalTarget := openTarget
- originalZone := targetZone
- openTarget = func() (rgbProtocol, targetDeviceData, []via.Channel, error) {
- // The board identity matters: the catalog is selected by VID/PID, and a
- // profile that names an effect needs one to resolve it against.
- target := targetDeviceData{
- Device: intdevice.Device{VendorID: 0x36B0, ProductID: 0x309F},
- Display: display,
- }
- requested, err := resolveZoneName(zoneFlag, display)
- if err != nil {
- return nil, target, nil, err
- }
- target.Requested = requested
- resolved, err := resolveChannels(proto, target)
- if err != nil {
- return nil, target, nil, err
- }
- return proto, target, resolved, nil
- }
- targetZone = zoneFlag
- return func() {
- openTarget = originalTarget
- targetZone = originalZone
- }
- }
- // A key that names a channel this keyboard does not have must be reported like
- // any other key it cannot place. "backlight" is the case that matters: it is a
- // display name on the Impact 80 and QMK's subsystem name for channel 1, so it
- // still resolves after a board renames channel 3 — to a channel that is absent.
- func TestLoadWarnsWhenAKeyNamesAnAbsentChannel(t *testing.T) {
- dir := t.TempDir()
- originalDir := profilesDir
- t.Cleanup(func() { profilesDir = originalDir })
- profilesDir = dir
- profile := Profile{
- Name: "renamed",
- Version: 1,
- Zones: map[string]*ZoneSettings{
- "backlight": {Enabled: true, Effect: "light", Brightness: 100, Speed: 1, Color: "00ff"},
- },
- }
- if err := profile.Save(); err != nil {
- t.Fatalf("save profile: %v", err)
- }
- proto := &verifyingProtocol{}
- t.Cleanup(stubTargetForProfileTest(t, proto, "", nil))
- var stderr bytes.Buffer
- cmd := NewProfileLoadCmd()
- cmd.SetOut(&bytes.Buffer{})
- cmd.SetErr(&stderr)
- cmd.SetArgs([]string{"renamed"})
- if err := cmd.Execute(); err != nil {
- t.Fatalf("load returned error: %v", err)
- }
- if !strings.Contains(stderr.String(), "backlight") {
- t.Errorf("stderr = %q, want the key reported by name", stderr.String())
- }
- if len(proto.reports) != 0 {
- t.Errorf("reports = %v, want nothing written for an absent channel", proto.reports)
- }
- }
- // Without a catalog there are no effect names to look the profile's value up
- // in. The load must say that, not claim the name was not found.
- func TestLoadSaysSoWhenTheBoardHasNoCatalog(t *testing.T) {
- dir := t.TempDir()
- originalDir := profilesDir
- t.Cleanup(func() { profilesDir = originalDir })
- profilesDir = dir
- profile := Profile{
- Name: "p",
- Version: 1,
- Zones: map[string]*ZoneSettings{
- "rgb_matrix": {Enabled: true, Effect: "breathing", Brightness: 100, Speed: 1, Color: "00ff"},
- },
- }
- if err := profile.Save(); err != nil {
- t.Fatalf("save profile: %v", err)
- }
- proto := &verifyingProtocol{}
- restore := stubTargetForUnknownBoard(t, proto, dir)
- t.Cleanup(restore)
- var stderr bytes.Buffer
- cmd := NewProfileLoadCmd()
- cmd.SetOut(&bytes.Buffer{})
- cmd.SetErr(&stderr)
- cmd.SetArgs([]string{"p"})
- if err := cmd.Execute(); err != nil {
- t.Fatalf("load returned error: %v", err)
- }
- if !strings.Contains(stderr.String(), "catalog") {
- t.Errorf("stderr = %q, want the missing catalog named", stderr.String())
- }
- if strings.Contains(stderr.String(), `effect "breathing" not found`) {
- t.Errorf("stderr = %q, must not blame the effect name for a missing catalog", stderr.String())
- }
- if len(proto.reports) != 0 {
- t.Errorf("reports = %v, want nothing written without a catalog", proto.reports)
- }
- }
- // stubTargetForUnknownBoard points the commands at a board that has channels but
- // no keyboards.json entry, so it has no catalog.
- func stubTargetForUnknownBoard(t *testing.T, proto rgbProtocol, dir string) func() {
- t.Helper()
- originalTarget := openTarget
- originalDir := profilesDir
- openTarget = func() (rgbProtocol, targetDeviceData, []via.Channel, error) {
- return proto, targetDeviceData{
- Device: intdevice.Device{VendorID: 0x6666, ProductID: 0x0001},
- }, impact80Channels(), nil
- }
- profilesDir = dir
- return func() {
- openTarget = originalTarget
- profilesDir = originalDir
- }
- }
|