package api import "testing" // What a car's page shows — which tabs, which rows of the Information tab, and // the order those rows are laid out in — is stored on the car as key lists, so // the validation has to keep them to keys the page actually renders. // Information itself stays out of the hideable tabs: a car with no tabs left // would be a dead end. func TestNormalizeHiddenTabs(t *testing.T) { got, err := normalizeKeys([]string{" fuel ", "parts", "fuel", ""}, hideableCarTabs, "tab") if err != nil { t.Fatalf("normalizeKeys: %v", err) } want := []string{"fuel", "parts"} // trimmed, blanks dropped, deduped assertKeys(t, got, want) // Clearing the list is how a car goes back to showing everything. if empty, err := normalizeKeys(nil, hideableCarTabs, "tab"); err != nil || len(empty) != 0 { t.Errorf("normalizeKeys(nil) = %v, %v; want empty and no error", empty, err) } // Information is the car itself; hiding it would leave a page with no tabs. if _, err := normalizeKeys([]string{"info"}, hideableCarTabs, "tab"); err == nil { t.Error("normalizeKeys allowed hiding the info tab, want an error") } // A key from a stale or wrong client is an error, not something to drop // quietly while the tab stays visible. if _, err := normalizeKeys([]string{"fuel", "nonsense"}, hideableCarTabs, "tab"); err == nil { t.Error("normalizeKeys accepted an unknown tab, want an error") } // The hideable set is the contract the web app's HIDEABLE_TABS mirrors: // every tab the car page renders beside Information. for _, key := range []string{ "provider", "services", "technical", "maintenance", "fuel", "charging", "documents", "parts", "reminders", } { if !hideableCarTabs[key] { t.Errorf("tab %q should be hideable", key) } } if len(hideableCarTabs) != 9 { t.Errorf("hideableCarTabs has %d entries, want the 9 tabs beside Information", len(hideableCarTabs)) } } func TestNormalizeHiddenFields(t *testing.T) { got, err := normalizeKeys([]string{"vin", " differentialOil ", "vin"}, hideableCarFields, "field") if err != nil { t.Fatalf("normalizeKeys: %v", err) } assertKeys(t, got, []string{"vin", "differentialOil"}) if _, err := normalizeKeys([]string{"oilSpec", "nonsense"}, hideableCarFields, "field"); err == nil { t.Error("normalizeKeys accepted an unknown field, want an error") } // A tab key is not a field key — the two sets are validated separately. if _, err := normalizeKeys([]string{"fuel"}, hideableCarFields, "field"); err == nil { t.Error("normalizeKeys accepted a tab key as a field, want an error") } // Every Information row the web app renders must be hideable; unlike the // tabs there is no row the page has to keep. for _, key := range []string{ "oilSpec", "transmissionOil", "differentialOil", "brakeFluid", "coolant", "odometer", "serviceInterval", "nextDue", "registrationPlate", "registrationCountry", "vin", "fuelType", "buildDate", "firstRegistration", } { if !hideableCarFields[key] { t.Errorf("field %q should be hideable", key) } } if len(hideableCarFields) != 14 { t.Errorf("hideableCarFields has %d entries, want the 14 Information rows", len(hideableCarFields)) } } // The tabs arrange against a wider set than they hide against: Information // cannot be switched off, but it can be moved off the front of the bar. func TestNormalizeTabOrder(t *testing.T) { got, err := normalizeKeys([]string{"reminders", "info", "fuel"}, arrangeableCarTabs, "tab") if err != nil { t.Fatalf("normalizeKeys: %v", err) } assertKeys(t, got, []string{"reminders", "info", "fuel"}) // Everything the bar renders has to be arrangeable — the hideable tabs plus // Information, and nothing else. for key := range hideableCarTabs { if !arrangeableCarTabs[key] { t.Errorf("tab %q should be arrangeable", key) } } if !arrangeableCarTabs["info"] { t.Error("the info tab should be arrangeable even though it cannot be hidden") } if len(arrangeableCarTabs) != len(hideableCarTabs)+1 { t.Errorf("arrangeableCarTabs has %d entries, want the hideable tabs plus Information", len(arrangeableCarTabs)) } // A field key is not a tab key, and an invented tab is still an error. if _, err := normalizeKeys([]string{"vin"}, arrangeableCarTabs, "tab"); err == nil { t.Error("normalizeKeys accepted a field key as a tab, want an error") } if _, err := normalizeKeys([]string{"info", "nonsense"}, arrangeableCarTabs, "tab"); err == nil { t.Error("normalizeKeys accepted an unknown tab in an arrangement, want an error") } } // The arrangement of the Information rows shares the field key set — every row // can be moved — but not the meaning: here the order of the list is the point, // so it has to survive validation exactly as it was sent. func TestNormalizeFieldOrder(t *testing.T) { got, err := normalizeKeys([]string{"vin", "odometer", "oilSpec"}, hideableCarFields, "field") if err != nil { t.Fatalf("normalizeKeys: %v", err) } assertKeys(t, got, []string{"vin", "odometer", "oilSpec"}) // A key repeated by a client that lost track keeps its first position; a // second entry for the same row would put it in two places at once. got, err = normalizeKeys([]string{"vin", "odometer", "vin"}, hideableCarFields, "field") if err != nil { t.Fatalf("normalizeKeys: %v", err) } assertKeys(t, got, []string{"vin", "odometer"}) // A partial arrangement is fine — the rows it leaves out follow the arranged // ones — but an invented row is still an error. if _, err := normalizeKeys([]string{"vin", "nonsense"}, hideableCarFields, "field"); err == nil { t.Error("normalizeKeys accepted an unknown field in an arrangement, want an error") } } // The connected service's headline readings arrange the same way, against the // key set derived from what that panel renders. func TestNormalizeMetricOrder(t *testing.T) { got, err := normalizeKeys([]string{"evRangeWithAc", "odometer"}, arrangeableCarMetrics, "reading") if err != nil { t.Fatalf("normalizeKeys: %v", err) } assertKeys(t, got, []string{"evRangeWithAc", "odometer"}) // Every reading the panel can show has to be arrangeable, the two that are // not measures included. for _, key := range []string{ "odometer", "fuelLevel", "fuelRange", "batteryLevel", "evRange", "evRangeWithAc", "chargingStatus", "location", } { if !arrangeableCarMetrics[key] { t.Errorf("reading %q should be arrangeable", key) } } // A field key is not a reading key — a car's two arrangements are separate. if _, err := normalizeKeys([]string{"vin"}, arrangeableCarMetrics, "reading"); err == nil { t.Error("normalizeKeys accepted a field key as a reading, want an error") } } func assertKeys(t *testing.T, got, want []string) { t.Helper() if len(got) != len(want) { t.Fatalf("keys = %v, want %v", got, want) } for i := range got { if got[i] != want[i] { t.Fatalf("keys = %v, want %v", got, want) } } }