diff --git a/builtins_test.go b/builtins_test.go index 2092704..9c2db06 100644 --- a/builtins_test.go +++ b/builtins_test.go @@ -203,7 +203,7 @@ func mustPanic(t *testing.T, name string, call func()) { } func TestClassBuiltinArgs(t *testing.T) { - for _, bad := range []string{`"{digits(0)}"`, `"{digits(2000000000)}"`, `"{upper(-1)}"`, `"{lower(x)}"`, `"{digits()}"`, `"{upper(1,2)}"`} { + for _, bad := range []string{`"{digits(2000000000)}"`, `"{upper(-1)}"`, `"{lower(x)}"`, `"{digits()}"`, `"{upper(1,2)}"`} { if _, err := resolved(t, bad); err == nil { t.Errorf("compile(%s) = nil error, want the arg rejected", bad) } @@ -379,13 +379,10 @@ func TestFunctionTokenLuhn(t *testing.T) { func TestBuiltinCompileErrors(t *testing.T) { for _, bad := range []string{ - `"{digits(0)}"`, // count must be positive `"{upper(x)}"`, // count must be an integer `"{int(a,b)}"`, // non-integer args `"{int(5,1)}"`, // min > max - `"{hex(0)}"`, // count must be positive `"{nanoid(-1)}"`, // negative count - `"{base64(0)}"`, // count must be positive `"{float(1,2,-1)}"`, // negative decimals `"{float(NaN,NaN,2)}"`, // bounds must be finite `"{float(Inf,Inf,2)}"`, // same-sign infinities diff --git a/calc_test.go b/calc_test.go index 82961a1..a544daf 100644 --- a/calc_test.go +++ b/calc_test.go @@ -333,3 +333,9 @@ func TestATypedColumnProvesACalcOverAName(t *testing.T) { t.Fatalf("New = %v, want the typed column refused over an operand that is not always a number", err) } } + +func TestARepeatOfZeroIsNoConstant(t *testing.T) { + if got := mustRender(t, engine(1), `{"format":"{calc(x / y)}","x":"1","y":{"format":"0","repeat":0}}`); got != "NaN" { + t.Errorf("a divisor rendering nothing = %q, want NaN", got) + } +} diff --git a/cmd/fejkdata/main_test.go b/cmd/fejkdata/main_test.go index 2fd421d..1993c4c 100644 --- a/cmd/fejkdata/main_test.go +++ b/cmd/fejkdata/main_test.go @@ -516,9 +516,11 @@ func TestRunNoShippedData(t *testing.T) { if code != 0 || strings.TrimSpace(out) == "" { t.Errorf("run = %d, out=%q", code, out) } - code, _, errb = runOut("{/sv_SE.person}", "--no-shipped-data") - if code != 2 || !strings.Contains(errb, "--no-shipped-data needs at least one --data-path") { - t.Errorf("--no-shipped-data alone = %d, %q, want misuse naming --data-path", code, errb) + if code, out, errb := runOut("{digits(3)}", "--no-shipped-data"); code != 0 || len(out) != 3 { + t.Errorf("--no-shipped-data alone = %d, %q, %q, want a template reading no data rendered", code, out, errb) + } + if code, _, errb := runOut("{/sv_SE.person}", "--no-shipped-data"); code != 2 || !strings.Contains(errb, "no entry") { + t.Errorf("--no-shipped-data alone = %d, %q, want a reference to nothing refused", code, errb) } } diff --git a/inline_test.go b/inline_test.go index 62026df..e82b21c 100644 --- a/inline_test.go +++ b/inline_test.go @@ -104,7 +104,7 @@ func TestFakeTemplateErrors(t *testing.T) { }{ {`{"x":"Q"}`, "missing string \"format\""}, {`"{x}"`, `no field "x"`}, - {`"{digits(0)}"`, "must be positive"}, + {`"{digits(-1)}"`, "must not be negative"}, {`name: {/no.such.path}`, "no entry"}, {`name: {..nope}`, "write {/nope}"}, } { diff --git a/loading_test.go b/loading_test.go index e85e266..c4fa6d5 100644 --- a/loading_test.go +++ b/loading_test.go @@ -59,12 +59,6 @@ func TestNewLoadsAnyDirName(t *testing.T) { } } -func TestNewEmptyDirErrors(t *testing.T) { - if _, err := New(WithoutShippedData(), WithDataPath(writeData(t, nil))); err == nil { - t.Fatal("New(empty dir) = nil error") - } -} - // TestFoldersBecomeDotPaths checks the core of the new model: a subfolder is a // namespace, so data//person.json is reachable as ".person". func TestFoldersBecomeDotPaths(t *testing.T) { diff --git a/name_test.go b/name_test.go index f2671fb..fa120ca 100644 --- a/name_test.go +++ b/name_test.go @@ -166,6 +166,22 @@ func TestANameInAnInlineTemplate(t *testing.T) { } } +func TestAChoiceItemKeepsTheNamesItBinds(t *testing.T) { + f, n, both := engine(1), compiled(t, `{"format":"{x}","x":[{"format":"{v as w}{w}{w}","v":["a","b"]},"c"]}`), 0 + for i := 0; i < 100; i++ { + switch renderOnce(f.drawState, n) { + case "aa", "bb": + both++ + case "c": + default: + t.Fatal("a name bound in a choice's item read two picks") + } + } + if both == 0 { + t.Error("100 renders never drew the item binding a name") + } +} + func TestNameErrors(t *testing.T) { cases := []struct { name string @@ -177,7 +193,7 @@ func TestNameErrors(t *testing.T) { {"bound inside a repeat written first", map[string]string{"word": `["a","b"]`, "card": `{"format":"{a}{z}","a":{"format":"{/word as w}{w}","repeat":2},"z":"{/word as w}{w}"}`}, `name "w" is bound outside this repeat too`}, {"bound in a choice's item", map[string]string{"word": `["a","b"]`, "card": `{"format":"{x}{w}","x":["{/word as w}","b"]}`}, - `a choice's item binds no name`}, + `name "w" is bound at field "x", inside an item of a choice, which a read outside that item cannot see`}, {"bound outside the repeat too", map[string]string{"word": `["a","b"]`, "card": `{"format":"{/word as w}{x}","x":{"format":"{/word as w}{w}","repeat":2}}`}, `name "w" is bound outside this repeat too`}, {"a field too", map[string]string{"word": `["a","b"]`, "card": `{"format":"{/word as w}{w}","w":"x"}`}, diff --git a/node_test.go b/node_test.go index ed3544b..c7a7bbf 100644 --- a/node_test.go +++ b/node_test.go @@ -53,7 +53,6 @@ func TestNodeCompileErrors(t *testing.T) { `[{"format":"A","weight":0},{"format":"B","weight":0}]`, // weights sum to zero `[{"format":"A","weight":1e308},{"format":"B","weight":1e308}]`, // weights overflow to +Inf `{"format":"A","weight":"heavy"}`, // non-numeric weight - `{"format":"x","repeat":0}`, // repeat below 1 `{"format":"x","repeat":-2}`, // negative repeat `{"format":"x","repeat":1.5}`, // non-integer repeat `{"format":"x","repeat":"two"}`, // non-numeric repeat diff --git a/shipped_data_test.go b/shipped_data_test.go index 7b37f3e..2c2ab85 100644 --- a/shipped_data_test.go +++ b/shipped_data_test.go @@ -1,7 +1,6 @@ package fejkdata import ( - "errors" "reflect" "slices" "strings" @@ -54,10 +53,21 @@ func TestUserDataMayReferenceShipped(t *testing.T) { } } -func TestWithoutShippedDataNeedsASource(t *testing.T) { - _, err := New(WithoutShippedData()) - if err == nil || !strings.Contains(err.Error(), "WithDataPath") || !errors.Is(err, ErrNoData) { - t.Fatalf("New(WithoutShippedData()) = %v, want ErrNoData naming WithDataPath", err) +func TestAGeneratorWithNoDataRendersWhatReadsNone(t *testing.T) { + for name, opts := range map[string][]Option{ + "no source": {WithoutShippedData()}, + "empty folder": {WithoutShippedData(), WithDataPath(t.TempDir())}, + } { + f, err := New(opts...) + if err != nil { + t.Fatalf("%s: New = %v, want a generator holding no data", name, err) + } + if v, err := f.FakeTemplate("{digits(3)}"); err != nil || len(v) != 3 { + t.Errorf("%s: FakeTemplate = %q, %v, want three digits", name, v, err) + } + if _, err := f.Fake("x"); err == nil || !strings.Contains(err.Error(), "no entry") { + t.Errorf("%s: Fake(x) = %v, want a reference to nothing refused", name, err) + } } } diff --git a/spellings_test.go b/spellings_test.go index 8f9147e..b15b203 100644 --- a/spellings_test.go +++ b/spellings_test.go @@ -51,7 +51,9 @@ func TestAPartWithNoEffectRendersAsWithoutIt(t *testing.T) { `{"format":"{word as w}x","word":["a","b"]}`: `"x"`, `null`: `""`, `{"format":"{p}","p":{"format":"{x}","x":[null,"a"]}}`: `{"format":"{p}","p":{"format":"{x}","x":["","a"]}}`, - `"{int(5,5)}"`: `"5"`, + `"{int(5,5)}"`: `"5"`, + `{"format":"{x}","x":["a","b"],"repeat":0}`: `""`, + `"{digits(0)}{upper(0)}{lower(0)}{hex(0)}{base64(0)}{nanoid(0)}"`: `""`, `"{float(1,1,2)}"`: `"1.00"`, `"{date(1990-01-01,1990-01-01,'2006-01-02')}"`: `"1990-01-01"`, `"{date(1990-01-01,1990-12-31,'x')}"`: `"x"`,