diff --git a/edge_test.go b/edge_test.go index c99e518..105c341 100644 --- a/edge_test.go +++ b/edge_test.go @@ -89,6 +89,35 @@ func TestDescendIntoLiteralErrors(t *testing.T) { } } +// TestPathThroughChoice pins the rule that makes a dotted path predictable: a +// choice consumes no segment, so a path may step through one only when every +// variant carries the rest of it. Otherwise the same path would render or fail +// depending on which variant the rng picked. +func TestPathThroughChoice(t *testing.T) { + dir := writeData(t, map[string]string{ + "every": `[{"format":"{f}","f":["1"]},{"format":"{f}","f":["2"]}]`, + "notall": `[{"format":"{f}","f":["1"]},["plain"]]`, + }) + f := newFakes(t, dir, WithSeed(1)) + for i := 0; i < 200; i++ { + if got := fake(t, f, "every.f"); got != "1" && got != "2" { + t.Fatalf("every.f = %q, want 1 or 2", got) + } + } + var first string + for i := 0; i < 200; i++ { + _, err := f.Fake("notall.f") + if err == nil { + t.Fatal("notall.f = nil error, want the same failure every call") + } + if i == 0 { + first = err.Error() + } else if err.Error() != first { + t.Fatalf("notall.f error varies between calls:\n %s\n %s", first, err.Error()) + } + } +} + // --- category root shapes --- func TestCategoryRootShapes(t *testing.T) { diff --git a/loading_test.go b/loading_test.go index 4b2a847..4646935 100644 --- a/loading_test.go +++ b/loading_test.go @@ -4,6 +4,7 @@ import ( "os" "path/filepath" "reflect" + "slices" "testing" ) @@ -14,9 +15,11 @@ func TestList(t *testing.T) { "person": `{"format":"{first} {last}","first":["A"],"last":["B"]}`, "word": `["x", "y"]`, "geo/city": `["Z"]`, + // Only the fields every variant carries are addressable, so "extra" is not. + "coin": `[{"format":"{code}","code":["A"],"name":["Aa"]},{"format":"{code}","code":["B"],"name":["Bb"],"extra":["x"]}]`, }) got := newFakes(t, dir, WithSeed(1)).List() - want := []string{"geo.city", "person", "person.first", "person.last", "word"} + want := []string{"coin", "coin.code", "coin.name", "geo.city", "person", "person.first", "person.last", "word"} if !reflect.DeepEqual(got, want) { t.Fatalf("List() = %v, want %v", got, want) } @@ -131,3 +134,26 @@ func TestMultiPathMergesFolders(t *testing.T) { t.Fatalf("sv_SE.shared = %q, want b (last loaded wins)", got) } } + +// TestListedPathsAllRender is the contract between List and Fake over the shipped +// tree: everything List advertises renders, every time, and the sub-fields the +// README advertises are discoverable. +func TestListedPathsAllRender(t *testing.T) { + f := newFakes(t, "data", WithSeed(4)) + paths := f.List() + if len(paths) < 50 { + t.Fatalf("List() = %d paths, want the whole shipped tree", len(paths)) + } + for _, p := range paths { + for i := 0; i < 20; i++ { + if _, err := f.Fake(p); err != nil { + t.Fatalf("Fake(%q) = %v, but List() advertises it", p, err) + } + } + } + for _, p := range []string{"misc.car.maker", "misc.country.alpha2", "misc.currency.symbol", "misc.httpstatus.code", "misc.mimetype.ext"} { + if !slices.Contains(paths, p) { + t.Errorf("List() omits %q, which the README advertises and Fake renders", p) + } + } +}