diff --git a/cmd/fejkdata/main_test.go b/cmd/fejkdata/main_test.go index d56625a..a97ba54 100644 --- a/cmd/fejkdata/main_test.go +++ b/cmd/fejkdata/main_test.go @@ -531,7 +531,7 @@ func TestRunRecordMisuse(t *testing.T) { args []string want string }{ - {[]string{"--format", "yaml", "users"}, "--format takes text, json, csv or sql"}, + {[]string{"--format", "yaml", "users"}, "--format takes csv, json, sql or text"}, {[]string{"--format", "json", "--separator", ",", "users"}, "--separator joins text values"}, {[]string{"--table", "t", "users"}, "--table names the INSERT target"}, {[]string{"--format", "json", "--table", "t", "users"}, "--table names the INSERT target"}, @@ -554,6 +554,25 @@ func TestRunRecordOnABareValueIsRuntimeError(t *testing.T) { } } +func TestRunRecordRejectsATopLevelRepeat(t *testing.T) { + dir := t.TempDir() + content := `{"format":"{a}-","repeat":3,"separator":"|","a":["x","y"]}` + if err := os.WriteFile(filepath.Join(dir, "rep.json"), []byte(content), 0o644); err != nil { + t.Fatal(err) + } + code, _, errb := runOut("--format", "json", "--data-path", dir, "rep") + if code != 1 || !strings.Contains(errb, "carries repeat 3") { + t.Errorf("record on a repeating path = %d, %q; want exit 1 naming the repeat", code, errb) + } + code, _, errb = runOut("--format", "json", content) + if code != 2 || !strings.Contains(errb, "carries repeat 3") { + t.Errorf("record on a repeating inline template = %d, %q; want exit 2 naming the repeat", code, errb) + } + if code, out, _ := runOut("--seed", "1", "--data-path", dir, "rep"); code != 0 || strings.TrimSpace(out) != "x-|x-|x-" { + t.Errorf("text view = %d, %q; want the repeat still composed", code, out) + } +} + func TestRunNumbersFollowTheShell(t *testing.T) { _, want, _ := runOut("--seed", "7", "--repeat", "3", "sv_SE.word") code, got, errb := runOut("--seed", "007", "--repeat", "+3", "sv_SE.word") diff --git a/readme_test.go b/readme_test.go index 6bd4fec..4cc569e 100644 --- a/readme_test.go +++ b/readme_test.go @@ -59,7 +59,7 @@ func TestReadmeRecordExample(t *testing.T) { if err != nil { t.Fatal(err) } - got := r.Fields() + got := r.Columns() if len(got) != 2 || got[0].Name != "first" || got[0].Value != "Bo" || got[1].Name != "last" || got[1].Value != "Lovelace" { t.Fatalf("record columns = %v, want first=Bo, last=Lovelace with seed 1", got) } diff --git a/record_test.go b/record_test.go index 472fb56..ab38a2a 100644 --- a/record_test.go +++ b/record_test.go @@ -25,9 +25,9 @@ func TestRecordProjectsFieldsAsColumns(t *testing.T) { if err != nil { t.Fatal(err) } - got := r.Fields() + got := r.Columns() if len(got) != 2 || got[0].Name != "first" || got[1].Name != "last" { - t.Fatalf("Record.Fields() = %v, want columns first, last in name order", got) + t.Fatalf("Record.Columns() = %v, want columns first, last in name order", got) } if (got[0].Value != "Ada" && got[0].Value != "Bo") || (got[1].Value != "Lovelace" && got[1].Value != "Ek") { t.Fatalf("columns = %v, want the field values", got) @@ -55,9 +55,9 @@ func TestRecordSkipsReferenceBindings(t *testing.T) { if err != nil { t.Fatal(err) } - got := r.Fields() + got := r.Columns() if len(got) != 1 || got[0].Name != "id" { - t.Fatalf("Record.Fields() = %v, want only the id column (a {/path} is not a column)", got) + t.Fatalf("Record.Columns() = %v, want only the id column (a {/path} is not a column)", got) } } @@ -96,7 +96,7 @@ func TestRecordJSON(t *testing.T) { if len(m) != 2 || (m["first"] != "Ada" && m["first"] != "Bo") { t.Fatalf("Record.JSON() = %s, want two addressable columns", r.JSON()) } - for _, f := range r.Fields() { + for _, f := range r.Columns() { if m[f.Name] != f.Value { t.Fatalf("JSON column %q = %q, want %q", f.Name, m[f.Name], f.Value) } @@ -119,7 +119,7 @@ func TestRecordCSV(t *testing.T) { if err != nil { t.Fatalf("CSVLine() is not valid CSV: %v\n%q", err, r.CSVLine()) } - if len(rec) != 1 || rec[0] != r.Fields()[0].Value { + if len(rec) != 1 || rec[0] != r.Columns()[0].Value { t.Fatalf("CSVLine() = %v, want the field value round-tripped", rec) } } @@ -164,7 +164,7 @@ func TestRecordSharesAReferenceAcrossColumns(t *testing.T) { t.Fatal(err) } m := map[string]string{} - for _, c := range r.Fields() { + for _, c := range r.Columns() { m[c.Name] = c.Value } switch m["code"] { @@ -193,9 +193,9 @@ func TestRecordSharesAReferenceIntoAColumnRepeat(t *testing.T) { if err != nil { t.Fatal(err) } - parts := strings.Split(r.Fields()[0].Value, "-") + parts := strings.Split(r.Columns()[0].Value, "-") if len(parts) != 3 || parts[0] != parts[1] || parts[1] != parts[2] { - t.Fatalf("codes column = %q, want one shared draw across its repeat", r.Fields()[0].Value) + t.Fatalf("codes column = %q, want one shared draw across its repeat", r.Columns()[0].Value) } } } @@ -213,7 +213,7 @@ func TestRecordBareReferenceStaysIndependent(t *testing.T) { t.Fatal(err) } m := map[string]string{} - for _, c := range r.Fields() { + for _, c := range r.Columns() { m[c.Name] = c.Value } if m["whole"] != m["code"] { @@ -248,16 +248,16 @@ func TestFakeRecordAndTemplate(t *testing.T) { if err != nil { t.Fatalf("FakeRecord: %v", err) } - if len(want.Fields()) != 2 { - t.Fatalf("FakeRecord fields = %v, want two columns", want.Fields()) + if len(want.Columns()) != 2 { + t.Fatalf("FakeRecord columns = %v, want two columns", want.Columns()) } reusable, err := f.NewRecordTemplate(in) if err != nil { t.Fatalf("NewRecordTemplate: %v", err) } for i := 0; i < 20; i++ { - if got := reusable.Fake(); len(got.Fields()) != 2 { - t.Fatalf("RecordTemplate.Fake() = %v, want two columns", got.Fields()) + if got := reusable.Fake(); len(got.Columns()) != 2 { + t.Fatalf("RecordTemplate.Fake() = %v, want two columns", got.Columns()) } } } @@ -268,11 +268,36 @@ func TestInlineRecordErrors(t *testing.T) { input string want string }{ - {`"hello"`, "needs at least one field"}, - {`["a","b"]`, "a choice"}, + {`"hello"`, "has no fields, so no columns"}, + {`["a","b"]`, "names a choice, not a template"}, + {`{"format":"{a}-","repeat":3,"separator":"|","a":["x","y"]}`, "carries repeat 3"}, } { if _, err := f.FakeRecord(c.input); err == nil || !strings.Contains(err.Error(), c.want) { t.Errorf("FakeRecord(%q) = %v, want an error naming %q", c.input, err, c.want) } } } + +func TestRecordRejectsATopLevelRepeat(t *testing.T) { + dir := writeData(t, map[string]string{ + "rep": `{"format":"{a}-","repeat":3,"separator":"|","a":["x","y"]}`, + }) + f := newGenerator(t, dir, WithSeed(1)) + if _, err := f.Record("rep"); err == nil || !strings.Contains(err.Error(), "carries repeat 3") { + t.Errorf("Record on a repeating template = %v, want an error naming the repeat", err) + } + if v, err := f.Fake("rep"); err != nil || v != "x-|x-|x-" { + t.Errorf("Fake(rep) = %q, %v, want the repeat still composed for the string view", v, err) + } +} + +func TestRecordTemplateRejectsATopLevelRepeat(t *testing.T) { + f := recordCat(t) + in := `{"format":"{a}-","repeat":3,"separator":"|","a":["x","y"]}` + if _, err := f.NewRecordTemplate(in); err == nil || !strings.Contains(err.Error(), "carries repeat 3") { + t.Errorf("NewRecordTemplate on a repeating template = %v, want an error naming the repeat", err) + } + if _, err := f.NewTemplate(in); err != nil { + t.Errorf("NewTemplate on the same input = %v, want the string view to still compile", err) + } +} diff --git a/template_test.go b/template_test.go index aa92a8a..080972e 100644 --- a/template_test.go +++ b/template_test.go @@ -38,7 +38,7 @@ func compiled(t *testing.T, s string) node { func mustRender(t *testing.T, f *Generator, s string) string { t.Helper() - return render(f.rand, compiled(t, s)) + return render(f.rand, compiled(t, s), nil) } func TestStringIsAFormat(t *testing.T) {