Tests: a reference path is one draw per render in both views, a repeat iteration draws anew, a group draws apart, and a bare reference beside a path is refused
This commit is contained in:
+8
-1
@@ -404,6 +404,13 @@ func TestPathIntoARepeatingLevelIsRejected(t *testing.T) {
|
||||
if err == nil || !strings.Contains(err.Error(), "repeat") {
|
||||
t.Fatalf("New = %v, want a path into a repeating level rejected", err)
|
||||
}
|
||||
_, err = New(WithoutShippedData(), WithDataPath(writeData(t, map[string]string{
|
||||
"cat": `{"format":"[{p.a}]","p":{"format":"{a}","a":"{/word.w}","group":"g"}}`,
|
||||
"word": `{"format":"{w}","w":["x","y"]}`,
|
||||
})))
|
||||
if err == nil || !strings.Contains(err.Error(), `the level "p" carries a group`) {
|
||||
t.Fatalf("New = %v, want a path into a level carrying a group rejected", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPathIntoAPlainTemplateNamesTheMissingField(t *testing.T) {
|
||||
@@ -718,6 +725,6 @@ func TestReadFieldPanicsOnAnUnheldPath(t *testing.T) {
|
||||
t.Fatal("not a template")
|
||||
}
|
||||
mustPanic(t, "unheld arm with a path", func() {
|
||||
readField(engine(1).rand, tm, nil, nil, arm{name: "w.x", key: "w", tail: []string{"x"}, path: "w.x"})
|
||||
readField(engine(1).rand, tm, nil, drawScope{}, arm{name: "w.x", key: "w", tail: []string{"x"}, path: "w.x"})
|
||||
})
|
||||
}
|
||||
|
||||
+23
-1
@@ -38,18 +38,40 @@ func TestInertObjectIsRejected(t *testing.T) {
|
||||
`{"format":"{x}","x":"v","separator":","}`: "separator",
|
||||
`{"format":"{x}","x":"v","repeat":2,"separator":""}`: "default",
|
||||
`{"format":"","n":{"format":"1","datatype":"string"}}`: `datatype "string" is the default`,
|
||||
`{"format":"{x}","x":{"format":"{y}","y":"v","group":""}}`: `group "" is the default`,
|
||||
`{"format":"{x}","x":{"format":"{y}","y":"v","group":1}}`: "group must be a string",
|
||||
} {
|
||||
if _, err := compile(parse(t, src)); err == nil || !strings.Contains(err.Error(), want) {
|
||||
t.Errorf("compile(%s) = %v, want an error mentioning %s", src, err, want)
|
||||
}
|
||||
}
|
||||
for _, ok := range []string{`[{"format":"a","weight":2},"b"]`, `{"format":"ab","repeat":2}`, `{"format":"{x}","x":"v"}`, `"{digits(3)}"`} {
|
||||
for _, ok := range []string{`[{"format":"a","weight":2},"b"]`, `{"format":"ab","repeat":2}`, `{"format":"{x}","x":"v"}`, `"{digits(3)}"`, `{"format":"{/cat.x}","group":"g"}`} {
|
||||
if _, err := compile(parse(t, ok)); err != nil {
|
||||
t.Errorf("compile(%s) = %v", ok, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAGroupThatSplitsNothingIsRejected(t *testing.T) {
|
||||
files := map[string]string{"mail": `"{/word.w}@example.com"`, "word": `{"format":"{w}","w":["a","b"]}`}
|
||||
for src, want := range map[string]string{
|
||||
`{"format":"{x}","x":{"format":"{y}","y":["a","b"],"group":"g"}}`: `group "g" splits nothing`,
|
||||
`{"format":"{x}","x":{"format":"{/word}","group":"g"}}`: `group "g" splits nothing`,
|
||||
`{"format":"{/word.w}","group":"g"}`: "",
|
||||
`{"format":"{x}","x":{"format":"{/mail}","group":"g"}}`: "",
|
||||
`{"format":"{x}","x":{"format":"{y}","y":"{/word.w}","repeat":2,"group":"g"}}`: "",
|
||||
} {
|
||||
files["cat"] = src
|
||||
_, err := New(WithoutShippedData(), WithDataPath(writeData(t, files)))
|
||||
switch {
|
||||
case want == "" && err != nil:
|
||||
t.Errorf("New(%s) = %v, want a group over a reference path accepted, however deep", src, err)
|
||||
case want != "" && (err == nil || !strings.Contains(err.Error(), want)):
|
||||
t.Errorf("New(%s) = %v, want an error mentioning %s", src, err, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInlineFolderSigilsAreRejected(t *testing.T) {
|
||||
f := shipped(t)
|
||||
for _, input := range []string{"{.sv_SE.person.last}", "{..sv_SE.person.last}"} {
|
||||
|
||||
+61
-31
@@ -200,13 +200,22 @@ func TestRecordWritesTypedAndNullColumns(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordRejectsOverlappingReferenceColumns(t *testing.T) {
|
||||
cat := `{"format":"","a":[{"format":"A={b}","b":"1"},{"format":"A={b}","b":"2"}]}`
|
||||
cat := `{"format":"{a}","a":[{"format":"A={b}","b":"1"},{"format":"A={b}","b":"2"}]}`
|
||||
for _, c := range []struct{ name, row string }{
|
||||
{"through a column repeat, which draws anew", `{"format":"","whole":"{/cat.a}","inner":{"format":"{/cat.a.b}","repeat":2,"separator":"-"}}`},
|
||||
{"in a group of its own", `{"format":"","whole":{"format":"{/cat.a}","group":"g"},"inner":"{/cat.a.b}"}`},
|
||||
} {
|
||||
f := newGenerator(t, writeData(t, map[string]string{"cat": cat, "row": c.row}), WithSeed(1))
|
||||
if _, err := f.FakeRecord("row"); err != nil {
|
||||
t.Errorf("%s: FakeRecord = %v, want it accepted", c.name, err)
|
||||
}
|
||||
}
|
||||
for _, c := range []struct{ name, row string }{
|
||||
{"sibling columns", `{"format":"","whole":"{/cat.a}","inner":"{/cat.a.b}"}`},
|
||||
{"through a nested template", `{"format":"","whole":"{/cat.a}","inner":{"format":"{/cat.a.b} {x}","x":"1"}}`},
|
||||
{"through a column repeat", `{"format":"","whole":"{/cat.a}","inner":{"format":"{/cat.a.b}","repeat":2,"separator":"-"}}`},
|
||||
{"through a choice variant", `{"format":"","whole":"{/cat.a}","inner":[{"format":"{/cat.a.b} {x}","x":"1"},{"format":"{/cat.a.b}! {x}","x":"2"}]}`},
|
||||
{"as a builtin operand", `{"format":"","whole":"{uppercase(/cat.a)}","inner":"{/cat.a.b}"}`},
|
||||
{"a bare reference beside a path", `{"format":"","whole":"{/cat}","inner":"{/cat.a.b}"}`},
|
||||
} {
|
||||
f := newGenerator(t, writeData(t, map[string]string{"cat": cat, "row": c.row}), WithSeed(1))
|
||||
_, err := f.FakeRecord("row")
|
||||
@@ -311,9 +320,10 @@ func TestRecordRejectsFieldDescent(t *testing.T) {
|
||||
func TestRecordSharesAReferenceAcrossColumns(t *testing.T) {
|
||||
dir := writeData(t, map[string]string{
|
||||
"currency": `[{"format":"{code}","code":"AUD","symbol":"$"},{"format":"{code}","code":"EUR","symbol":"€"}]`,
|
||||
"price": `{"format":"","code":"{/currency.code}","symbol":"{/currency.symbol}"}`,
|
||||
"price": `{"format":"{code} {symbol}","code":"{/currency.code}","symbol":"{/currency.symbol}"}`,
|
||||
})
|
||||
f := newGenerator(t, dir, WithSeed(1))
|
||||
symbols := map[string]string{"AUD": "$", "EUR": "€"}
|
||||
for i := 0; i < 100; i++ {
|
||||
r, err := f.FakeRecord("price")
|
||||
if err != nil {
|
||||
@@ -323,48 +333,63 @@ func TestRecordSharesAReferenceAcrossColumns(t *testing.T) {
|
||||
for _, c := range r.Columns() {
|
||||
m[c.Name] = c.Value
|
||||
}
|
||||
switch m["code"] {
|
||||
case "AUD":
|
||||
if m["symbol"] != "$" {
|
||||
t.Fatalf("record %q: code AUD but symbol %q, want one currency draw across columns", r.JSON(), m["symbol"])
|
||||
if symbol, known := symbols[m["code"]]; !known || m["symbol"] != symbol {
|
||||
t.Fatalf("record %s, want one currency draw across columns", r.JSON())
|
||||
}
|
||||
case "EUR":
|
||||
if m["symbol"] != "€" {
|
||||
t.Fatalf("record %q: code EUR but symbol %q, want one currency draw across columns", r.JSON(), m["symbol"])
|
||||
}
|
||||
default:
|
||||
t.Fatalf("record %q has unexpected code %q", r.JSON(), m["code"])
|
||||
v := fake(t, f, "price")
|
||||
if code, symbol, _ := strings.Cut(v, " "); symbol == "" || symbols[code] != symbol {
|
||||
t.Fatalf("Fake(price) = %q, want its fields one currency draw, as the record's columns are", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecordSharesAReferenceIntoAColumnRepeat(t *testing.T) {
|
||||
func TestRepeatIterationsDrawReferencesAnew(t *testing.T) {
|
||||
dir := writeData(t, map[string]string{
|
||||
"currency": `[{"format":"{code}","code":"AUD"},{"format":"{code}","code":"EUR"}]`,
|
||||
"order": `{"format":"","codes":{"format":"{/currency.code}","repeat":3,"separator":"-"}}`,
|
||||
"party": `{"format":"{host}: {guests}","guests":{"format":"{/person.first} {/person.last}","repeat":3,"separator":", "},"host":"{/person.first} {/person.last}"}`,
|
||||
"person": drawPeople,
|
||||
})
|
||||
f := newGenerator(t, dir, WithSeed(1))
|
||||
for i := 0; i < 50; i++ {
|
||||
r, err := f.FakeRecord("order")
|
||||
differed := map[[2]int]bool{}
|
||||
check := func(view string, names []string) {
|
||||
t.Helper()
|
||||
if len(names) != 4 {
|
||||
t.Fatalf("%s rendered %q, want a host and three guests", view, names)
|
||||
}
|
||||
for i, name := range names {
|
||||
if !onePerson(name) {
|
||||
t.Fatalf("%s rendered %q, want each name one person", view, names)
|
||||
}
|
||||
for j := range names[:i] {
|
||||
differed[[2]int{j, i}] = differed[[2]int{j, i}] || names[j] != name
|
||||
}
|
||||
}
|
||||
}
|
||||
for i := 0; i < 100; i++ {
|
||||
r, err := f.FakeRecord("party")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
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.Columns()[0].Value)
|
||||
guests, host := r.Columns()[0].Value, r.Columns()[1].Value
|
||||
check("FakeRecord", append([]string{host}, strings.Split(guests, ", ")...))
|
||||
host, guests, _ = strings.Cut(fake(t, f, "party"), ": ")
|
||||
check("Fake", append([]string{host}, strings.Split(guests, ", ")...))
|
||||
}
|
||||
for pair, ok := range differed {
|
||||
if !ok {
|
||||
t.Errorf("names %v never differed in 200 renders; the host and each repeat iteration are a draw of their own, so four people", pair)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecordBareReferenceStaysIndependent(t *testing.T) {
|
||||
func TestRecordGroupsDrawApart(t *testing.T) {
|
||||
dir := writeData(t, map[string]string{
|
||||
"currency": `[{"format":"{code}","code":"AUD"},{"format":"{code}","code":"EUR"}]`,
|
||||
"order": `{"format":"","whole":"{/currency}","code":"{/currency.code}"}`,
|
||||
"person": drawPeople,
|
||||
"transfer": `{"format":"{from_first} {from_last} to {to_first} {to_last}","from_first":{"format":"{/person.first}","group":"from"},"from_last":{"format":"{/person.last}","group":"from"},"to_first":{"format":"{/person.first}","group":"to"},"to_last":{"format":"{/person.last}","group":"to"}}`,
|
||||
})
|
||||
f := newGenerator(t, dir, WithSeed(1))
|
||||
sawMismatch := false
|
||||
apart := map[string]bool{}
|
||||
for i := 0; i < 100; i++ {
|
||||
r, err := f.FakeRecord("order")
|
||||
r, err := f.FakeRecord("transfer")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -372,13 +397,18 @@ func TestRecordBareReferenceStaysIndependent(t *testing.T) {
|
||||
for _, c := range r.Columns() {
|
||||
m[c.Name] = c.Value
|
||||
}
|
||||
if m["whole"] != m["code"] {
|
||||
sawMismatch = true
|
||||
break
|
||||
from, to, _ := strings.Cut(fake(t, f, "transfer"), " to ")
|
||||
for view, pair := range map[string][2]string{"FakeRecord": {m["from_first"] + " " + m["from_last"], m["to_first"] + " " + m["to_last"]}, "Fake": {from, to}} {
|
||||
if !onePerson(pair[0]) || !onePerson(pair[1]) {
|
||||
t.Fatalf("%s drew %q and %q, want each group one person", view, pair[0], pair[1])
|
||||
}
|
||||
apart[view] = apart[view] || pair[0] != pair[1]
|
||||
}
|
||||
}
|
||||
if !sawMismatch {
|
||||
t.Fatal("a bare {/currency} column never disagreed with a tailed {/currency.code} column; a bare reference should draw independently")
|
||||
for _, view := range []string{"FakeRecord", "Fake"} {
|
||||
if !apart[view] {
|
||||
t.Errorf("%s: groups from and to drew one person in 100 renders, want a draw each", view)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+60
-2
@@ -255,15 +255,73 @@ func TestBareReferenceDrawsEachTime(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestReferenceOverlapIsRejected(t *testing.T) {
|
||||
p := `[{"format":"{first}","first":"A","last":"1"},{"format":"{first}","first":"B","last":"2"}]`
|
||||
for name, file := range map[string]string{
|
||||
"head beside a path": `{"format":"{/cat.p} {/cat.p.first}","p":[{"format":"{first}","first":"A"},{"format":"{first}","first":"B"}]}`,
|
||||
"sibling path beside a reference path": `{"format":"{p.first} {/cat.p.last}","p":[{"format":"{first}","first":"A","last":"1"},{"format":"{first}","first":"B","last":"2"}]}`,
|
||||
"head beside a path": `{"format":"{/cat.p} {/cat.p.first}","p":` + p + `}`,
|
||||
"sibling path beside a reference path": `{"format":"{p.first} {/cat.p.last}","p":` + p + `}`,
|
||||
"sibling fields reading a level and a path into it": `{"format":"{a} {b}","a":"{/cat.p}","b":"{/cat.p.first}","p":` + p + `}`,
|
||||
"a field rendering the level a nested reference reads into": `{"format":"{x} {p}","x":"{/cat.p.first}","p":` + p + `}`,
|
||||
} {
|
||||
_, err := New(WithoutShippedData(), WithDataPath(writeData(t, map[string]string{"cat": file})))
|
||||
if err == nil || !strings.Contains(err.Error(), "reads a path into") {
|
||||
t.Errorf("%s: New = %v, want the overlap rejected", name, err)
|
||||
}
|
||||
}
|
||||
if _, err := New(WithoutShippedData(), WithDataPath(writeData(t, map[string]string{
|
||||
"cat": `{"format":"{a} {b}","a":{"format":"{/cat.p}","group":"g"},"b":"{/cat.p.first}","p":` + p + `}`,
|
||||
}))); err != nil {
|
||||
t.Errorf("New = %v, want a level and a path into it accepted in groups of their own", err)
|
||||
}
|
||||
}
|
||||
|
||||
const drawPeople = `[{"format":"{first} {last}","first":"Ada","last":"Lovelace"},{"format":"{first} {last}","first":"Bo","last":"Ek"},{"format":"{first} {last}","first":"Cy","last":"Young"}]`
|
||||
|
||||
// onePerson reports whether name is the first name and surname of one drawPeople row.
|
||||
func onePerson(name string) bool {
|
||||
first, last, _ := strings.Cut(name, " ")
|
||||
return last != "" && map[string]string{"Ada": "Lovelace", "Bo": "Ek", "Cy": "Young"}[first] == last
|
||||
}
|
||||
|
||||
func TestAReferencePathIsOneDrawPerRender(t *testing.T) {
|
||||
dir := writeData(t, map[string]string{
|
||||
"apart": `{"format":"{a} & {b}","a":{"format":"{/person.first} {/person.last}","group":"x"},"b":{"format":"{/person.first} {/person.last}","group":"y"}}`,
|
||||
"contact": `{"format":"{first} {last} <{email}>","email":"{lowercase(/person.first)}.{lowercase(/person.last)}@example.com","first":"{/person.first}","last":"{/person.last}"}`,
|
||||
"nested": `{"format":"{/person.first} {inner}","inner":"{/person.last}"}`,
|
||||
"pair": `{"format":"{a} & {b}","a":"{/person.first} {/person.last}","b":"{/person.first} {/person.last}"}`,
|
||||
"person": drawPeople,
|
||||
})
|
||||
f := newGenerator(t, dir, WithSeed(1))
|
||||
apart := false
|
||||
for i := 0; i < 100; i++ {
|
||||
if got := fake(t, f, "nested"); !onePerson(got) {
|
||||
t.Fatalf("nested = %q, want a nested template's path one draw with its parent's", got)
|
||||
}
|
||||
name, email, _ := strings.Cut(fake(t, f, "contact"), " <")
|
||||
first, last, _ := strings.Cut(name, " ")
|
||||
if !onePerson(name) || email != strings.ToLower(first)+"."+strings.ToLower(last)+"@example.com>" {
|
||||
t.Fatalf("contact = %q <%s, want first, last and email one person", name, email)
|
||||
}
|
||||
r, err := f.FakeRecord("contact")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c := r.Columns()
|
||||
if !onePerson(c[1].Value+" "+c[2].Value) || c[0].Value != strings.ToLower(c[1].Value)+"."+strings.ToLower(c[2].Value)+"@example.com" {
|
||||
t.Fatalf("contact record %s, want first, last and email one person", r.JSON())
|
||||
}
|
||||
a, b, _ := strings.Cut(fake(t, f, "pair"), " & ")
|
||||
if !onePerson(a) || a != b {
|
||||
t.Fatalf("pair = %q & %q, want both fields one person", a, b)
|
||||
}
|
||||
a, b, _ = strings.Cut(fake(t, f, "apart"), " & ")
|
||||
if !onePerson(a) || !onePerson(b) {
|
||||
t.Fatalf("apart = %q & %q, want each group one person", a, b)
|
||||
}
|
||||
apart = apart || a != b
|
||||
}
|
||||
if !apart {
|
||||
t.Error("groups x and y drew one person in 100 renders, want a draw each")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRelativeReferences(t *testing.T) {
|
||||
|
||||
+2
-2
@@ -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), nil)
|
||||
return renderOnce(f.rand, compiled(t, s))
|
||||
}
|
||||
|
||||
func TestStringIsAFormat(t *testing.T) {
|
||||
@@ -315,7 +315,7 @@ func TestGrowIsALowerBound(t *testing.T) {
|
||||
t.Fatalf("format %q did not compile to a template", format)
|
||||
}
|
||||
for i := 0; i < 50; i++ {
|
||||
if got := len(expand(f.rand, tmpl, nil)); got < tmpl.grow {
|
||||
if got := len(expand(f.rand, tmpl, drawScope{set: &drawSet{}})); got < tmpl.grow {
|
||||
t.Errorf("format %q: expand emitted %d bytes, below grow %d", format, got, tmpl.grow)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user