Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33
@@ -0,0 +1,47 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/larvit/fejkdata/internal/grammar"
|
||||||
|
)
|
||||||
|
|
||||||
|
// grammarLoneReference is the path a format reads when the grammar parses it as one path
|
||||||
|
// read of one reference from the root, and nothing else.
|
||||||
|
func grammarLoneReference(text string) (string, bool) {
|
||||||
|
toks, err := grammar.ParseFormat(text)
|
||||||
|
if err != nil || len(toks) != 1 || toks[0].Kind != grammar.PathRead || len(toks[0].Arms) != 1 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
sigil, path, err := grammar.RefShape(toks[0].Arms[0])
|
||||||
|
if err != nil || sigil != "/" {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return path, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoneReferenceAgreesWithTheGrammar(t *testing.T) {
|
||||||
|
for _, text := range []string{
|
||||||
|
"{/sv_SE.person}",
|
||||||
|
"{/misc.currency[US Dollar (Next day)]}",
|
||||||
|
"{/t[a as b].c}",
|
||||||
|
"{/t[f(x)]}",
|
||||||
|
"{/a}{/b}",
|
||||||
|
"{/x}}",
|
||||||
|
"{/}",
|
||||||
|
"{/.x}",
|
||||||
|
" {/x}",
|
||||||
|
"{/a|/b}",
|
||||||
|
"{/x as p}",
|
||||||
|
"{//x}",
|
||||||
|
"{/x(1)}",
|
||||||
|
"{.x}",
|
||||||
|
"x",
|
||||||
|
} {
|
||||||
|
path, lone := loneReference(text)
|
||||||
|
want, wantLone := grammarLoneReference(text)
|
||||||
|
if lone != wantLone || path != want {
|
||||||
|
t.Errorf("loneReference(%q) = %q, %v; the grammar reads %q, %v", text, path, lone, want, wantLone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -249,6 +249,7 @@ func TestRunTakesNoOperand(t *testing.T) {
|
|||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{[]string{"sv_SE.person"}, "echo '{/sv_SE.person}' | fejkdata"},
|
{[]string{"sv_SE.person"}, "echo '{/sv_SE.person}' | fejkdata"},
|
||||||
|
{[]string{"/en_US.address"}, "echo '{/en_US.address}' | fejkdata"},
|
||||||
{[]string{"{/en_US.person}"}, "echo '{/en_US.person}' | fejkdata"},
|
{[]string{"{/en_US.person}"}, "echo '{/en_US.person}' | fejkdata"},
|
||||||
{[]string{"name: {/sv_SE.person.last}"}, "echo 'name: {/sv_SE.person.last}' | fejkdata"},
|
{[]string{"name: {/sv_SE.person.last}"}, "echo 'name: {/sv_SE.person.last}' | fejkdata"},
|
||||||
{[]string{"geo.US.locality[O'Fallon].name"}, `echo '{/geo.US.locality[O'\''Fallon].name}' | fejkdata`},
|
{[]string{"geo.US.locality[O'Fallon].name"}, `echo '{/geo.US.locality[O'\''Fallon].name}' | fejkdata`},
|
||||||
@@ -494,6 +495,8 @@ func TestRunTemplateMisuse(t *testing.T) {
|
|||||||
"42": "number",
|
"42": "number",
|
||||||
"42\n": "number",
|
"42\n": "number",
|
||||||
"true\n": "boolean",
|
"true\n": "boolean",
|
||||||
|
"null\n": "only null",
|
||||||
|
" null ": "only null",
|
||||||
"{//sv_SE.person}": "write {/sv_SE.person}",
|
"{//sv_SE.person}": "write {/sv_SE.person}",
|
||||||
} {
|
} {
|
||||||
code, out, errb := runOut(stdin, "--seed", "1")
|
code, out, errb := runOut(stdin, "--seed", "1")
|
||||||
|
|||||||
+29
-6
@@ -48,17 +48,16 @@ func TestDatatypeRejectsAValueItsTypeRejects(t *testing.T) {
|
|||||||
{"a weighted item beside a typed one", `[{"format":"1","datatype":"integer"},{"format":"2","weight":3}]`, `give it "datatype": "integer"`},
|
{"a weighted item beside a typed one", `[{"format":"1","datatype":"integer"},{"format":"2","weight":3}]`, `give it "datatype": "integer"`},
|
||||||
{"an item beside a typed column it reads", `["{/src.score}","5"]`, `write it as {"format":"5","datatype":"integer"}`},
|
{"an item beside a typed column it reads", `["{/src.score}","5"]`, `write it as {"format":"5","datatype":"integer"}`},
|
||||||
{"a typed item beside a string column it reads", `["{/src.code}",{"format":"1","datatype":"integer"}]`, `write it as {"format":"{/src.code}","datatype":"integer"}`},
|
{"a typed item beside a string column it reads", `["{/src.code}",{"format":"1","datatype":"integer"}]`, `write it as {"format":"{/src.code}","datatype":"integer"}`},
|
||||||
{"text beside a typed column it reads", `["{/src.score}","n/a"]`, `to read that column as text, write {"format":"{text}","text":"{/src.score}"}`},
|
{"text beside a typed column it reads", `["{/src.score}","n/a"]`, `to read that column as text, write "{/src.score}" as {"format":"{/src.score}","datatype":"string"}`},
|
||||||
{"a datatype over a typed column", `{"format":"{/src.score}","datatype":"integer"}`, `{/src.score} takes datatype integer from the column it reads; drop "datatype"`},
|
|
||||||
{"a datatype a typed column's values reject", `{"format":"{/src.score}","datatype":"boolean"}`, "{int(1,9)} prints an integer, not a boolean"},
|
{"a datatype a typed column's values reject", `{"format":"{/src.score}","datatype":"boolean"}`, "{int(1,9)} prints an integer, not a boolean"},
|
||||||
{"two typed columns it reads", `["{/src.score}","{/src.flag}"]`, `so to read "{/src.score}" as text, write {"format":"{text}","text":"{/src.score}"}`},
|
{"two typed columns it reads", `["{/src.score}","{/src.flag}"]`, `so to read "{/src.score}" as text, write "{/src.score}" as {"format":"{/src.score}","datatype":"string"}`},
|
||||||
{"a typed column read that holds the datatype declared beside it", `[{"format":"1.5","datatype":"number"},"{/src.score}"]`, `so write "{/src.score}" as {"format":"{/src.score}","datatype":"number"}`},
|
{"a typed column read that holds the datatype declared beside it", `[{"format":"1.5","datatype":"number"},"{/src.score}"]`, `so write "{/src.score}" as {"format":"{/src.score}","datatype":"number"}`},
|
||||||
{"a typed column read holding the datatype of another", `["{/src.ratio}","{/src.score}"]`, `so write "{/src.ratio}" as {"format":"{/src.ratio}","datatype":"integer"}`},
|
{"a typed column read holding the datatype of another", `["{/src.ratio}","{/src.score}"]`, `so write "{/src.ratio}" as {"format":"{/src.ratio}","datatype":"integer"}`},
|
||||||
{"text before a typed column it reads", `["n/a","{/src.score}"]`, `to read that column as text, write {"format":"{text}","text":"{/src.score}"}`},
|
{"text before a typed column it reads", `["n/a","{/src.score}"]`, `to read that column as text, write "{/src.score}" as {"format":"{/src.score}","datatype":"string"}`},
|
||||||
{"a weighted item beside a typed column it reads", `["{/src.score}",{"format":"5","weight":2}]`, `give it "datatype": "integer"`},
|
{"a weighted item beside a typed column it reads", `["{/src.score}",{"format":"5","weight":2}]`, `give it "datatype": "integer"`},
|
||||||
{"a typed column read before the datatype it holds", `["{/src.score}",{"format":"1.5","datatype":"number"}]`, `so write "{/src.score}" as {"format":"{/src.score}","datatype":"number"}`},
|
{"a typed column read before the datatype it holds", `["{/src.score}",{"format":"1.5","datatype":"number"}]`, `so write "{/src.score}" as {"format":"{/src.score}","datatype":"number"}`},
|
||||||
{"a typed column read beside a datatype it does not hold", `["{/src.score}",{"format":"true","datatype":"boolean"}]`, `so to read "{/src.score}" as text, write {"format":"{text}","text":"{/src.score}"}`},
|
{"a typed column read beside a datatype it does not hold", `["{/src.score}",{"format":"true","datatype":"boolean"}]`, `so to read "{/src.score}" as text, write "{/src.score}" as {"format":"{/src.score}","datatype":"string"}`},
|
||||||
{"text beside a weighted typed column read", `[{"format":"{/src.score}","weight":3},"n/a"]`, `to read that column as text, set its "format" to "{text}" and add "text": "{/src.score}"`},
|
{"text beside a weighted typed column read", `[{"format":"{/src.score}","weight":3},"n/a"]`, `to read that column as text, give "{/src.score}" "datatype": "string"`},
|
||||||
{"a value of the column it reads", `{"format":"{/src.code}","datatype":"integer"}`, `"2x" is not an integer`},
|
{"a value of the column it reads", `{"format":"{/src.code}","datatype":"integer"}`, `"2x" is not an integer`},
|
||||||
{"a null read into text", `{"format":"{x}","x":"{/src.score}","datatype":"integer"}`, "reads a null"},
|
{"a null read into text", `{"format":"{x}","x":"{/src.score}","datatype":"integer"}`, "reads a null"},
|
||||||
{"a sample with leading zeros", `{"format":"{digits(3)}","datatype":"integer"}`, "{digits(3)} prints text, not an integer"},
|
{"a sample with leading zeros", `{"format":"{digits(3)}","datatype":"integer"}`, "{digits(3)} prints text, not an integer"},
|
||||||
@@ -233,3 +232,27 @@ func TestASignedZeroIsZero(t *testing.T) {
|
|||||||
t.Errorf("columns = %+v, want each written as its format", c)
|
t.Errorf("columns = %+v, want each written as its format", c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestADeclaredDatatypeOverAColumnRead(t *testing.T) {
|
||||||
|
f := newGenerator(t, writeData(t, map[string]string{
|
||||||
|
"src": `{"format":"","score":[null,{"format":"{int(1,9)}","datatype":"integer"}]}`,
|
||||||
|
"row": `{"format":"","same":{"format":"{/src.score}","datatype":"integer"},"text":{"format":"{/src.score}","datatype":"string"}}`,
|
||||||
|
}), WithSeed(1))
|
||||||
|
empty := 0
|
||||||
|
for i := 0; i < 50; i++ {
|
||||||
|
r, err := f.FakeRecord("row")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
same, text := r.Columns()[0], r.Columns()[1]
|
||||||
|
if same.DataType != DataTypeInteger || text.DataType != DataTypeString || text.Null {
|
||||||
|
t.Fatalf("columns = %+v, want an integer column restated and a string column never null", r.Columns())
|
||||||
|
}
|
||||||
|
if text.Value == "" {
|
||||||
|
empty++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if empty == 0 {
|
||||||
|
t.Error(`50 records never read a null as "", want the string column to render one so`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ func TestPackageImports(t *testing.T) {
|
|||||||
"internal/proven": {"internal/datatype", "internal/grammar", "internal/invariant"},
|
"internal/proven": {"internal/datatype", "internal/grammar", "internal/invariant"},
|
||||||
"internal/rows": {"internal/drawstate", "internal/grammar", "internal/invariant"},
|
"internal/rows": {"internal/drawstate", "internal/grammar", "internal/invariant"},
|
||||||
}
|
}
|
||||||
|
testsAlso := map[string][]string{
|
||||||
|
"cmd/fejkdata": {"internal/grammar"},
|
||||||
|
}
|
||||||
seen := map[string]bool{}
|
seen := map[string]bool{}
|
||||||
eachPackage(t, func(dir string, pkg *build.Package) {
|
eachPackage(t, func(dir string, pkg *build.Package) {
|
||||||
seen[dir] = true
|
seen[dir] = true
|
||||||
@@ -49,6 +52,10 @@ func TestPackageImports(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, imp := range slices.Concat(pkg.Imports, pkg.TestImports, pkg.XTestImports) {
|
for _, imp := range slices.Concat(pkg.Imports, pkg.TestImports, pkg.XTestImports) {
|
||||||
|
may := may
|
||||||
|
if !slices.Contains(pkg.Imports, imp) {
|
||||||
|
may = slices.Concat(may, testsAlso[dir])
|
||||||
|
}
|
||||||
if imp != module && !strings.HasPrefix(imp, module+"/") {
|
if imp != module && !strings.HasPrefix(imp, module+"/") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-10
@@ -175,22 +175,22 @@ func TestIsTemplate(t *testing.T) {
|
|||||||
"{//sv_SE.person}": true,
|
"{//sv_SE.person}": true,
|
||||||
"{/misc.currency[US Dollar (Next day)]}": true,
|
"{/misc.currency[US Dollar (Next day)]}": true,
|
||||||
"/sv_SE.person": false,
|
"/sv_SE.person": false,
|
||||||
"//sv_SE.person": false,
|
|
||||||
} {
|
} {
|
||||||
if got, err := IsTemplate(arg); err != nil || got != want {
|
if got, err := IsTemplate(arg); err != nil || got != want {
|
||||||
t.Errorf("IsTemplate(%q) = %v, %v; want %v", arg, got, err, want)
|
t.Errorf("IsTemplate(%q) = %v, %v; want %v", arg, got, err, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for arg, want := range map[string]string{
|
for arg, want := range map[string]string{
|
||||||
"[abc]": `starts with "["`,
|
"[abc]": `starts with "["`,
|
||||||
"[abc].field": `starts with "["`,
|
"[abc].field": `starts with "["`,
|
||||||
"x[1]y": `"]"`,
|
"x[1]y": `"]"`,
|
||||||
"x[[1]]": `"["`,
|
"x[[1]]": `"["`,
|
||||||
"x[]": "empty",
|
"x[]": "empty",
|
||||||
"a]b": `holds a "]"`,
|
"//sv_SE.person": "write /sv_SE.person",
|
||||||
"a}b": `holds a "}"`,
|
"a]b": `holds a "]"`,
|
||||||
`"abc`: `holds a "\""`,
|
"a}b": `holds a "}"`,
|
||||||
`"a]b`: `holds a "\""`, // the opener the reader typed, not the bracket behind it
|
`"abc`: `holds a "\""`,
|
||||||
|
`"a]b`: `holds a "\""`, // the opener the reader typed, not the bracket behind it
|
||||||
} {
|
} {
|
||||||
if _, err := IsTemplate(arg); err == nil || !strings.Contains(err.Error(), want) {
|
if _, err := IsTemplate(arg); err == nil || !strings.Contains(err.Error(), want) {
|
||||||
t.Errorf("IsTemplate(%q) = %v; want it rejected naming %s", arg, err, want)
|
t.Errorf("IsTemplate(%q) = %v; want it rejected naming %s", arg, err, want)
|
||||||
|
|||||||
+4
-24
@@ -302,27 +302,6 @@ func TestNewErrors(t *testing.T) {
|
|||||||
map[string]string{"a": `{"format":"{x}","x":"1","-":"2"}`},
|
map[string]string{"a": `{"format":"{x}","x":"1","-":"2"}`},
|
||||||
`field "-" is reserved`,
|
`field "-" is reserved`,
|
||||||
},
|
},
|
||||||
// A repeated arm skews an alternation, which weight is the spelling for.
|
|
||||||
"repeated alternation arm": {
|
|
||||||
map[string]string{"a": `{"format":"{x|x}","x":"1"}`},
|
|
||||||
`arm "x" is repeated`,
|
|
||||||
},
|
|
||||||
"repeated arm among others": {
|
|
||||||
map[string]string{"a": `{"format":"{x|y|x}","x":"1","y":"2"}`},
|
|
||||||
`arm "x" is repeated`,
|
|
||||||
},
|
|
||||||
"repeated path arm": {
|
|
||||||
map[string]string{"a": `{"format":"{p.v|p.v}","p":{"format":"{v}","v":"1"}}`},
|
|
||||||
`arm "p.v" is repeated`,
|
|
||||||
},
|
|
||||||
// A reference arm is the only kind that reaches the repeat check by passing
|
|
||||||
// the per-arm checks rather than falling through them.
|
|
||||||
"repeated reference arm": {
|
|
||||||
map[string]string{"a": `"x"`, "b": `"{/a|/a}"`},
|
|
||||||
`arm "/a" is repeated`,
|
|
||||||
},
|
|
||||||
// An arm that is broken on its own terms is reported as that, not as a
|
|
||||||
// repeat: the repeat is a consequence of the real mistake.
|
|
||||||
"repeated arm with no path": {
|
"repeated arm with no path": {
|
||||||
map[string]string{"a": `"{/|/}"`},
|
map[string]string{"a": `"{/|/}"`},
|
||||||
"reference has no path",
|
"reference has no path",
|
||||||
@@ -361,9 +340,10 @@ func TestNewErrors(t *testing.T) {
|
|||||||
"folder named Repeat": {map[string]string{"Repeat/cat": `"1"`}, "Repeat.cat", "1"},
|
"folder named Repeat": {map[string]string{"Repeat/cat": `"1"`}, "Repeat.cat", "1"},
|
||||||
"field with a closing paren": {map[string]string{"a": `{"format":"{b)c}","b)c":"2"}`}, "a", "2"},
|
"field with a closing paren": {map[string]string{"a": `{"format":"{b)c}","b)c":"2"}`}, "a", "2"},
|
||||||
"repeat without a separator": {map[string]string{"a": `{"format":"{x}","repeat":3,"x":"1"}`}, "a", "111"},
|
"repeat without a separator": {map[string]string{"a": `{"format":"{x}","repeat":3,"x":"1"}`}, "a", "111"},
|
||||||
// One name in two separate tokens is two independent draws, not a repeated
|
"one name in two tokens": {map[string]string{"a": `{"format":"{x}{x}","x":"1"}`}, "a", "11"},
|
||||||
// arm; only a repeat within one alternation is rejected.
|
"repeated alternation arm": {map[string]string{"a": `{"format":"{x|x}","x":"1"}`}, "a", "1"},
|
||||||
"one name in two tokens": {map[string]string{"a": `{"format":"{x}{x}","x":"1"}`}, "a", "11"},
|
"repeated path arm": {map[string]string{"a": `{"format":"{p.v|p.v}","p":{"format":"{v}","v":"1"}}`}, "a", "1"},
|
||||||
|
"repeated reference arm": {map[string]string{"a": `"x"`, "b": `"{/a|/a}"`}, "b", "x"},
|
||||||
}
|
}
|
||||||
for name, c := range accepted {
|
for name, c := range accepted {
|
||||||
f, err := New(WithoutShippedData(), WithDataPath(writeData(t, c.files)))
|
f, err := New(WithoutShippedData(), WithDataPath(writeData(t, c.files)))
|
||||||
|
|||||||
+22
-14
@@ -186,8 +186,6 @@ func TestNameErrors(t *testing.T) {
|
|||||||
`"format" is an option and can never be a name`},
|
`"format" is an option and can never be a name`},
|
||||||
{"no reference or field", map[string]string{"card": `{"format":"{word as w}{w}{w}","words":["a","b"]}`},
|
{"no reference or field", map[string]string{"card": `{"format":"{word as w}{w}{w}","words":["a","b"]}`},
|
||||||
`no field "word"; a binding names a field, or a reference`},
|
`no field "word"; a binding names a field, or a reference`},
|
||||||
{"read once through a field", map[string]string{"card": `{"format":"{place as p}{p.x}","place":{"format":"{x}","x":["a","b"]}}`},
|
|
||||||
""},
|
|
||||||
{"read once through a path that does not resolve", map[string]string{"word": `{"format":"{w}","w":["a","b"]}`, "card": `"{/word as a}{a.zz}"`},
|
{"read once through a path that does not resolve", map[string]string{"word": `{"format":"{w}","w":["a","b"]}`, "card": `"{/word as a}{a.zz}"`},
|
||||||
`no field "zz"`},
|
`no field "zz"`},
|
||||||
{"read once through a field path that does not resolve", map[string]string{"card": `{"format":"{place as p}{p.zz}","place":{"format":"{x}","x":["a","b"]}}`},
|
{"read once through a field path that does not resolve", map[string]string{"card": `{"format":"{place as p}{p.zz}","place":{"format":"{x}","x":["a","b"]}}`},
|
||||||
@@ -198,22 +196,10 @@ func TestNameErrors(t *testing.T) {
|
|||||||
""},
|
""},
|
||||||
{"a misspelt name", map[string]string{"word": `["a","b"]`, "card": `"{/word as p}{p}{p}{pp}"`},
|
{"a misspelt name", map[string]string{"word": `["a","b"]`, "card": `"{/word as p}{p}{p}{pp}"`},
|
||||||
`no field or name "pp"; the names bound here are "p"`},
|
`no field or name "pp"; the names bound here are "p"`},
|
||||||
{"read once through a path", map[string]string{"word": `{"format":"{w}","w":["a","b"]}`, "card": `"{/word as a}{a.w} {/word.w}"`},
|
|
||||||
""},
|
|
||||||
{"read once whole, bound to a reference path", map[string]string{"word": `{"format":"{w}","w":["a","b"]}`, "card": `"{/word.w as c}{c} {/word.w}"`},
|
|
||||||
""},
|
|
||||||
{"read once through a step up", map[string]string{"region.json": nameTables()["region.json"], "region.tsv": nameTables()["region.tsv"], "municipality.json": nameTables()["municipality.json"], "municipality.tsv": nameTables()["municipality.tsv"], "card": `"{/municipality as m}{m..region.name}"`},
|
|
||||||
""},
|
|
||||||
{"read once as a calc operand", map[string]string{"word": `["1","2"]`, "card": `"{/word as w}{calc(w * 2)}"`},
|
|
||||||
""},
|
|
||||||
{"read outside a repeat in the root choice", map[string]string{"word": `["a","b"]`, "card": `[{"format":"{/word as p}{p}{p}","repeat":2},"{p}"]`},
|
{"read outside a repeat in the root choice", map[string]string{"word": `["a","b"]`, "card": `[{"format":"{/word as p}{p}{p}","repeat":2},"{p}"]`},
|
||||||
`name "p" is bound at an item of the root choice, inside a repeat`},
|
`name "p" is bound at an item of the root choice, inside a repeat`},
|
||||||
{"an empty name", map[string]string{"word": `["a","b"]`, "card": `"{/word as }"`},
|
{"an empty name", map[string]string{"word": `["a","b"]`, "card": `"{/word as }"`},
|
||||||
"a binding names nothing"},
|
"a binding names nothing"},
|
||||||
{"read once whole", map[string]string{"word": `["a","b"]`, "card": `{"format":"{/word as w}{x}","x":"{w}"}`},
|
|
||||||
""},
|
|
||||||
{"read once whole, as a transform's operand", map[string]string{"word": `["a","b"]`, "card": `"{/word as w}{uppercase(w)}"`},
|
|
||||||
""},
|
|
||||||
{"rendered twice where a path reads it", map[string]string{"word": `{"format":"{w}-{w}","w":["a","b"]}`, "card": `"{/word as n}{n}|{n.w}"`},
|
{"rendered twice where a path reads it", map[string]string{"word": `{"format":"{w}-{w}","w":["a","b"]}`, "card": `"{/word as n}{n}|{n.w}"`},
|
||||||
`{n} renders field "w" twice`},
|
`{n} renders field "w" twice`},
|
||||||
{"a binding in a cell", map[string]string{"word": `["a","b"]`, "t.json": `{"format":"{k}","rows":"t.tsv","key":"k"}`, "t.tsv": "k\tc\nx\t{/word as w}{w}{w}\ny\tz\n"},
|
{"a binding in a cell", map[string]string{"word": `["a","b"]`, "t.json": `{"format":"{k}","rows":"t.tsv","key":"k"}`, "t.tsv": "k\tc\nx\t{/word as w}{w}{w}\ny\tz\n"},
|
||||||
@@ -258,6 +244,28 @@ func TestNameErrors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestANameReadOnceLoads(t *testing.T) {
|
||||||
|
for name, files := range map[string]map[string]string{
|
||||||
|
"through a path": {"word": `{"format":"{w}","w":["a","b"]}`, "card": `"{/word as a}{a.w} {/word.w}"`},
|
||||||
|
"whole, bound to a reference path": {"word": `{"format":"{w}","w":["a","b"]}`, "card": `"{/word.w as c}{c} {/word.w}"`},
|
||||||
|
"through a step up": {"region.json": nameTables()["region.json"], "region.tsv": nameTables()["region.tsv"], "municipality.json": nameTables()["municipality.json"], "municipality.tsv": nameTables()["municipality.tsv"], "card": `"{/municipality as m}{m..region.name}"`},
|
||||||
|
"as a calc operand": {"word": `["1","2"]`, "card": `"{/word as w}{calc(w * 2)}"`},
|
||||||
|
"whole": {"word": `["a","b"]`, "card": `{"format":"{/word as w}{x}","x":"{w}"}`},
|
||||||
|
"whole, as a transform's operand": {"word": `["a","b"]`, "card": `"{/word as w}{uppercase(w)}"`},
|
||||||
|
} {
|
||||||
|
withJSON := map[string]string{}
|
||||||
|
for file, body := range files {
|
||||||
|
if !strings.Contains(file, ".") {
|
||||||
|
file += ".json"
|
||||||
|
}
|
||||||
|
withJSON[file] = body
|
||||||
|
}
|
||||||
|
if _, err := New(WithoutShippedData(), WithDataPath(writeFiles(t, withJSON))); err != nil {
|
||||||
|
t.Errorf("%s: New() = %v, want it to load", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestANameBindsAReferenceFromItsFolder(t *testing.T) {
|
func TestANameBindsAReferenceFromItsFolder(t *testing.T) {
|
||||||
dir := writeData(t, map[string]string{
|
dir := writeData(t, map[string]string{
|
||||||
"sv/word": `["a","b","c","d","e","f","g","h"]`,
|
"sv/word": `["a","b","c","d","e","f","g","h"]`,
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ func TestNodeCompileErrors(t *testing.T) {
|
|||||||
`[1, 2]`, // a choice of numbers
|
`[1, 2]`, // a choice of numbers
|
||||||
`5`, // unsupported node type
|
`5`, // unsupported node type
|
||||||
`[]`, // empty choice
|
`[]`, // empty choice
|
||||||
`["a","a"]`, // a repeated item is a weight
|
|
||||||
`[{"format":"A","weight":-1},"B"]`, // negative weight
|
`[{"format":"A","weight":-1},"B"]`, // negative weight
|
||||||
`[{"format":"A","weight":0},{"format":"B","weight":0}]`, // weights sum to zero
|
`[{"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":1e308},{"format":"B","weight":1e308}]`, // weights overflow to +Inf
|
||||||
|
|||||||
+5
-4
@@ -187,10 +187,11 @@ func TestFakePathNavigation(t *testing.T) {
|
|||||||
|
|
||||||
func TestALeadingSlashIsThePath(t *testing.T) {
|
func TestALeadingSlashIsThePath(t *testing.T) {
|
||||||
f := newGenerator(t, writeData(t, map[string]string{"person": `{"format":"{first}","first":"Ada"}`}), WithSeed(1))
|
f := newGenerator(t, writeData(t, map[string]string{"person": `{"format":"{first}","first":"Ada"}`}), WithSeed(1))
|
||||||
for _, path := range []string{"/person", "//person.first"} {
|
if v := fake(t, f, "/person"); v != "Ada" {
|
||||||
if v := fake(t, f, path); v != "Ada" {
|
t.Errorf("Fake(/person) = %q, want Ada", v)
|
||||||
t.Errorf("Fake(%s) = %q, want Ada", path, v)
|
}
|
||||||
}
|
if _, err := f.Fake("//person"); err == nil || !strings.Contains(err.Error(), "write /person") {
|
||||||
|
t.Errorf("Fake(//person) = %v, want it refused naming /person", err)
|
||||||
}
|
}
|
||||||
r, err := f.FakeRecord("/person")
|
r, err := f.FakeRecord("/person")
|
||||||
if err != nil || len(r.Columns()) != 1 || r.Columns()[0].Value != "Ada" {
|
if err != nil || len(r.Columns()) != 1 || r.Columns()[0].Value != "Ada" {
|
||||||
|
|||||||
@@ -29,25 +29,29 @@ func TestOneItemChoiceIsItsItem(t *testing.T) {
|
|||||||
if r, isTemplate := compiled(t, `[{"format":"","d":"x"}]`).(*template); !isTemplate || !r.isRecord {
|
if r, isTemplate := compiled(t, `[{"format":"","d":"x"}]`).(*template); !isTemplate || !r.isRecord {
|
||||||
t.Error("a one-item choice of a record is no record, want the record it holds")
|
t.Error("a one-item choice of a record is no record, want the record it holds")
|
||||||
}
|
}
|
||||||
|
if _, err := resolved(t, `[{"format":"a","weight":2}]`); err == nil || !strings.Contains(err.Error(), "no effect here") {
|
||||||
|
t.Errorf("a weight on a one-item choice's item = %v, want it refused as doing nothing", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRepeatedChoiceItemIsRejected(t *testing.T) {
|
func TestARepeatedItemCountsAsWritten(t *testing.T) {
|
||||||
for src, want := range map[string]string{
|
for _, src := range []string{`["a", "a", "b"]`, `[{"format":"{x}","x":"1"},{"format":"{x}","x":"1"}]`, `{"format":"{w}","w":["", "", "x"]}`, `{"format":"","w":[null,null,"a"]}`, `{"format":"{x|x|y}","x":"1","y":"2"}`} {
|
||||||
`["a", "a", "b"]`: `{ "format": "a", "weight": 2 }`,
|
if _, err := resolved(t, src); err != nil {
|
||||||
`[{"format":"{x}","x":"1"},{"format":"{x}","x":"1"}]`: "repeats item",
|
t.Errorf("compile(%s) = %v, want it loaded", src, err)
|
||||||
`{"format":"{w}","w":["", "", "x"]}`: `{ "format": "", "weight": 2 }`,
|
|
||||||
`{"format":"","w":[null,null,"a"]}`: "a null takes no weight",
|
|
||||||
} {
|
|
||||||
if _, err := resolved(t, src); err == nil || !strings.Contains(err.Error(), want) {
|
|
||||||
t.Errorf("compile(%s) = %v, want an error naming %s", src, err, want)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, err := resolved(t, `[{"format":"a","weight":2}, "b"]`); err != nil {
|
f, n, as := engine(1), compiled(t, `["a", "a", "b"]`), 0
|
||||||
t.Errorf("compile(weighted a, b) = %v", err)
|
for i := 0; i < 600; i++ {
|
||||||
|
if renderOnce(f.drawState, n) == "a" {
|
||||||
|
as++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if as < 340 || as > 460 {
|
||||||
|
t.Errorf(`["a","a","b"] drew a %d times in 600, want about 400`, as)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInertObjectIsRejected(t *testing.T) {
|
func TestASeparatorWithoutARepeatIsRejected(t *testing.T) {
|
||||||
for src, want := range map[string]string{
|
for src, want := range map[string]string{
|
||||||
`{"format":"{x}","x":"v","separator":","}`: "separator",
|
`{"format":"{x}","x":"v","separator":","}`: "separator",
|
||||||
} {
|
} {
|
||||||
@@ -269,6 +269,9 @@ func TestFakeStructErrors(t *testing.T) {
|
|||||||
{&struct {
|
{&struct {
|
||||||
A int `fake:"{\"format\":\"{int(1,9)}\",\"datatype\":\"integer\"}"`
|
A int `fake:"{\"format\":\"{int(1,9)}\",\"datatype\":\"integer\"}"`
|
||||||
}{}, `its Go type int sets the datatype; drop "datatype"`},
|
}{}, `its Go type int sets the datatype; drop "datatype"`},
|
||||||
|
{&struct {
|
||||||
|
A string `fake:"{\"format\":\"x\",\"datatype\":\"string\"}"`
|
||||||
|
}{}, `its Go type string sets the datatype; drop "datatype"`},
|
||||||
{&struct {
|
{&struct {
|
||||||
A int `fake:"[null,\"{int(1,9)}\"]"`
|
A int `fake:"[null,\"{int(1,9)}\"]"`
|
||||||
}{}, "can draw null, which int cannot hold; make it *int"},
|
}{}, "can draw null, which int cannot hold; make it *int"},
|
||||||
|
|||||||
Reference in New Issue
Block a user