142 lines
4.7 KiB
Go
142 lines
4.7 KiB
Go
package models
|
|
|
|
import "testing"
|
|
|
|
/*
|
|
Reading the catalogue's nutrition strings.
|
|
|
|
The catalogue holds nutrition as display lines — "Energy 350kcal" — and the app
|
|
needs figures. These are the shapes those lines actually come in, and the ones
|
|
they come in when a scrape goes sideways.
|
|
|
|
The rule throughout: never invent a number, and never lose label text. A line
|
|
that cannot be read is carried whole rather than dropped, because it is
|
|
something a manufacturer printed on a packet and this code is not the authority
|
|
on what belongs on a food label.
|
|
*/
|
|
|
|
func TestAPlainLineBecomesAFigure(t *testing.T) {
|
|
panel := NutritionFromLines([]string{"Energy 350kcal"})
|
|
if !panel.HasValues() {
|
|
t.Fatal("nothing parsed")
|
|
}
|
|
got := panel.Items[0]
|
|
if got.Name != "Energy" || got.Value != 350 || got.Unit != "kcal" {
|
|
t.Fatalf("got %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestTheSeparatorsThatActuallyOccur(t *testing.T) {
|
|
// Colons, multi-word names and a space before the unit are all in the wild,
|
|
// and each one used to be a whole line lost.
|
|
for _, tc := range []struct {
|
|
line string
|
|
name string
|
|
value float64
|
|
unit string
|
|
}{
|
|
{"Protein: 6.5 g", "Protein", 6.5, "g"},
|
|
{"Total Sugars 22g", "Total Sugars", 22, "g"},
|
|
{"Saturated Fat 11.5 g", "Saturated Fat", 11.5, "g"},
|
|
{"Sodium 310mg", "Sodium", 310, "mg"},
|
|
{"Energy - 520 kcal", "Energy", 520, "kcal"},
|
|
} {
|
|
panel := NutritionFromLines([]string{tc.line})
|
|
if !panel.HasValues() {
|
|
t.Errorf("%q parsed to nothing", tc.line)
|
|
continue
|
|
}
|
|
got := panel.Items[0]
|
|
if got.Name != tc.name || got.Value != tc.value || got.Unit != tc.unit {
|
|
t.Errorf("%q → %+v, want {%s %v %s}", tc.line, got, tc.name, tc.value, tc.unit)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestADecimalSurvives(t *testing.T) {
|
|
// Saturated fat is 11.5g as often as 11g. Rounding to please a type would be
|
|
// editing a food label.
|
|
panel := NutritionFromLines([]string{"Saturated Fat 11.5g"})
|
|
if panel.Items[0].Value != 11.5 {
|
|
t.Fatalf("got %v, want 11.5", panel.Items[0].Value)
|
|
}
|
|
}
|
|
|
|
func TestALineWithNoNumberIsKeptWhole(t *testing.T) {
|
|
// Real label text. Dropping it loses something a manufacturer printed;
|
|
// forcing a 0 into it states something false about the food.
|
|
panel := NutritionFromLines([]string{"Contains permitted natural colour"})
|
|
if !panel.HasValues() {
|
|
t.Fatal("the line was dropped")
|
|
}
|
|
got := panel.Items[0]
|
|
if got.Name != "Contains permitted natural colour" {
|
|
t.Fatalf("the text was mangled: %+v", got)
|
|
}
|
|
if got.Value != 0 || got.Unit != "" {
|
|
t.Fatalf("a figure was invented for a line that had none: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestAUnitlessFigureKeepsItsNumber(t *testing.T) {
|
|
// "Servings per pack 4" has a real number and no unit.
|
|
panel := NutritionFromLines([]string{"Servings per pack 4"})
|
|
got := panel.Items[0]
|
|
if got.Name != "Servings per pack" || got.Value != 4 || got.Unit != "" {
|
|
t.Fatalf("got %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestPercentAndMicrogramsAreUnits(t *testing.T) {
|
|
for _, tc := range []struct{ line, unit string }{
|
|
{"Vitamin C 45%", "%"},
|
|
{"Vitamin B12 1.2µg", "µg"},
|
|
} {
|
|
panel := NutritionFromLines([]string{tc.line})
|
|
if !panel.HasValues() || panel.Items[0].Unit != tc.unit {
|
|
t.Errorf("%q → %+v, want unit %q", tc.line, panel.Items[0], tc.unit)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBlanksAndBareNumbersAreNotRows(t *testing.T) {
|
|
// A blank is nothing. A bare "100" has no label, and an unnamed row on a
|
|
// nutrition panel is noise a shopper cannot use.
|
|
if panel := NutritionFromLines([]string{"", " ", "100"}); panel != nil {
|
|
t.Fatalf("made a panel out of nothing: %+v", panel)
|
|
}
|
|
}
|
|
|
|
func TestNoLinesMeansNoPanel(t *testing.T) {
|
|
// nil rather than an empty panel, so a caller can tell "no nutrition known"
|
|
// from "a panel that happens to be empty" — the endpoint omits the first.
|
|
if NutritionFromLines(nil) != nil {
|
|
t.Fatal("an absent panel was reported as present")
|
|
}
|
|
if NutritionFromLines([]string{}) != nil {
|
|
t.Fatal("an absent panel was reported as present")
|
|
}
|
|
}
|
|
|
|
func TestAnEmptyPanelIsNotWorthSending(t *testing.T) {
|
|
// `items: []` on a product page renders as an empty box, which reads as
|
|
// "this food has no nutrition" rather than "we do not know yet".
|
|
var absent *NutritionPanel
|
|
if absent.HasValues() {
|
|
t.Fatal("nil reported as having values")
|
|
}
|
|
if (&NutritionPanel{Per: "100g"}).HasValues() {
|
|
t.Fatal("a panel with a basis and no rows reported as having values")
|
|
}
|
|
}
|
|
|
|
func TestTheDerivedPanelDoesNotGuessItsBasis(t *testing.T) {
|
|
// The strings never carried one. "100g" is the common case and not the only
|
|
// one, and a wrong basis makes every figure beneath it a misstatement rather
|
|
// than an unknown.
|
|
panel := NutritionFromLines([]string{"Energy 350kcal"})
|
|
if panel.Per != "" || panel.Servingsize != "" {
|
|
t.Fatalf("invented a basis: per=%q servingsize=%q", panel.Per, panel.Servingsize)
|
|
}
|
|
}
|