| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- package rgb
- import (
- "os"
- "path/filepath"
- "testing"
- "netdome.biz/paul/qmk-rgb/internal/via"
- )
- func writeNames(t *testing.T, body string) *Names {
- t.Helper()
- path := filepath.Join(t.TempDir(), "board.json")
- if err := os.WriteFile(path, []byte(body), 0o644); err != nil {
- t.Fatal(err)
- }
- names, err := LoadNamesFile(path)
- if err != nil {
- t.Fatalf("LoadNamesFile() error = %v", err)
- }
- return names
- }
- // A name the user wrote is theirs, and the tool has to be able to say so wherever
- // it prints the name: nothing in a file can be read back off a keyboard, so a name
- // is the one value it cannot verify, and a user's name is a fact about them.
- func TestNamesCarryTheirSource(t *testing.T) {
- names := writeNames(t, `{
- "vendorId": "0x1234", "productId": "0x5678",
- "channels": {"rgb_matrix": {"7": "rainbow_moving_chevron"}}
- }`)
- catalog := names.Apply(nil)
- if got := catalog.EffectName(via.ChannelRgbMatrix, 7); got != "rainbow_moving_chevron" {
- t.Errorf("EffectName() = %q, want the name the user wrote", got)
- }
- if got := catalog.EffectSource(via.ChannelRgbMatrix, 7); got != SourceUser {
- t.Errorf("EffectSource() = %q, want %q", got, SourceUser)
- }
- }
- // A board with no definition file is exactly the board a user writes names for, so
- // a names file alone has to produce a catalog. The manufacturer's file is the one
- // that is missing, not the names.
- func TestNamesAloneMakeACatalogForABoardWithNoDefinition(t *testing.T) {
- names := writeNames(t, `{
- "vendorId": "0x1234", "productId": "0x5678",
- "channels": {"rgblight": {"0": "none", "1": "wave"}}
- }`)
- catalog := names.Apply(nil)
- effects := catalog.Effects(via.ChannelRgblight)
- if len(effects) != 2 {
- t.Fatalf("effects = %v, want two", effects)
- }
- if effects[0].ID != 0 || effects[0].Name != "none" {
- t.Errorf("effects[0] = %v, want ID 0 named none", effects[0])
- }
- if effects[1].ID != 1 || effects[1].Name != "wave" {
- t.Errorf("effects[1] = %v, want ID 1 named wave", effects[1])
- }
- // A name the user wrote resolves back to its number, or naming it would be
- // decoration.
- id, ok := catalog.EffectID(via.ChannelRgblight, "wave")
- if !ok || id != 1 {
- t.Errorf("EffectID(\"wave\") = %d, %t, want 1, true", id, ok)
- }
- }
- // A user's name replaces the vendor's for that one effect and leaves the rest of
- // the file alone: overriding one name must not mean restating the ones that were
- // already right.
- func TestNamesOverrideOneEffectAndLeaveTheRest(t *testing.T) {
- base := NewCatalog("Test Board", map[via.Channel][]Effect{
- via.ChannelRgbMatrix: {
- {ID: 0, Name: "none"},
- {ID: 1, Name: "solid_color"},
- {ID: 2, Name: "breathing"},
- },
- })
- names := writeNames(t, `{
- "vendorId": "0x1234", "productId": "0x5678",
- "channels": {"rgb_matrix": {"2": "pulsing"}}
- }`)
- catalog := names.Apply(base)
- if got := catalog.EffectName(via.ChannelRgbMatrix, 2); got != "pulsing" {
- t.Errorf("EffectName(2) = %q, want the user's name", got)
- }
- if got := catalog.EffectSource(via.ChannelRgbMatrix, 2); got != SourceUser {
- t.Errorf("EffectSource(2) = %q, want %q", got, SourceUser)
- }
- if got := catalog.EffectName(via.ChannelRgbMatrix, 1); got != "solid_color" {
- t.Errorf("EffectName(1) = %q, want the vendor's name left alone", got)
- }
- if got := catalog.EffectSource(via.ChannelRgbMatrix, 1); got != SourceVendor {
- t.Errorf("EffectSource(1) = %q, want an empty source for the vendor's", got)
- }
- }
- // A definition may stop short of the board's highest ID, and a user naming that
- // one is telling the tool something the file did not. So the entry is added rather
- // than refused.
- func TestNamesAddAnEffectTheDefinitionDoesNotHave(t *testing.T) {
- base := NewCatalog("Test Board", map[via.Channel][]Effect{
- via.ChannelRgbMatrix: {{ID: 0, Name: "none"}},
- })
- names := writeNames(t, `{
- "vendorId": "0x1234", "productId": "0x5678",
- "channels": {"rgb_matrix": {"46": "riverflow"}}
- }`)
- catalog := names.Apply(base)
- effects := catalog.Effects(via.ChannelRgbMatrix)
- if len(effects) != 2 {
- t.Fatalf("effects = %v, want the definition's and the user's", effects)
- }
- if effects[1].ID != 46 || effects[1].Name != "riverflow" {
- t.Errorf("effects[1] = %v, want ID 46 named riverflow", effects[1])
- }
- }
- // Applying names must not reach back into the definition it was given, or a second
- // lookup for the same board would see the first one's overrides.
- func TestApplyDoesNotMutateTheCatalogItWasGiven(t *testing.T) {
- base := NewCatalog("Test Board", map[via.Channel][]Effect{
- via.ChannelRgbMatrix: {{ID: 1, Name: "solid_color"}},
- })
- names := writeNames(t, `{
- "vendorId": "0x1234", "productId": "0x5678",
- "channels": {"rgb_matrix": {"1": "steady"}}
- }`)
- names.Apply(base)
- if got := base.EffectName(via.ChannelRgbMatrix, 1); got != "solid_color" {
- t.Errorf("the definition's catalog now says %q, want it untouched", got)
- }
- }
- // A file that is not a names file is a user error worth reporting. Answering
- // "no names" instead would read as the keyboard having none.
- func TestLoadNamesFileRejectsAChannelThatIsNotOne(t *testing.T) {
- path := filepath.Join(t.TempDir(), "board.json")
- if err := os.WriteFile(path, []byte(`{
- "vendorId": "0x1234", "productId": "0x5678",
- "channels": {"keymap": {"1": "x"}}
- }`), 0o644); err != nil {
- t.Fatal(err)
- }
- if _, err := LoadNamesFile(path); err == nil {
- t.Error("LoadNamesFile() = nil error, want a channel that is not a lighting channel refused")
- }
- }
- func TestLoadNamesFileRejectsAnEffectIDThatIsNotOne(t *testing.T) {
- path := filepath.Join(t.TempDir(), "board.json")
- if err := os.WriteFile(path, []byte(`{
- "vendorId": "0x1234", "productId": "0x5678",
- "channels": {"rgb_matrix": {"two": "x"}}
- }`), 0o644); err != nil {
- t.Fatal(err)
- }
- if _, err := LoadNamesFile(path); err == nil {
- t.Error("LoadNamesFile() = nil error, want an effect ID that is not one refused")
- }
- }
- // An effect ID is a JSON object key, so it arrives as a string, and both spellings
- // the tool prints elsewhere are worth accepting.
- func TestEffectIDIsReadInEitherSpelling(t *testing.T) {
- tests := []struct {
- raw string
- want uint8
- }{
- {"7", 7},
- {"0x07", 7},
- {"46", 46},
- {"0x2E", 46},
- }
- for _, tt := range tests {
- got, err := parseEffectID(tt.raw)
- if err != nil {
- t.Errorf("parseEffectID(%q) error = %v", tt.raw, err)
- continue
- }
- if got != tt.want {
- t.Errorf("parseEffectID(%q) = %d, want %d", tt.raw, got, tt.want)
- }
- }
- }
|