package fejkdata import ( "regexp" "strings" "testing" ) func TestStringIsAFormat(t *testing.T) { f := engine(1) for src, want := range map[string]string{ `"Malmö"`: "Malmö", `"100 Main St, Apt 1A #0"`: "100 Main St, Apt 1A #0", `"{{x}}"`: "{x}", } { if got := mustRender(t, f, src); got != want { t.Errorf("render(%s) = %q, want %q", src, got, want) } } if got := mustRender(t, f, `"{digits(3)}"`); !regexp.MustCompile(`^[0-9]{3}$`).MatchString(got) { t.Errorf(`render("{digits(3)}") = %q, want three digits`, got) } if _, err := linked(t, `"{x}"`); err == nil || !strings.Contains(err.Error(), `no field "x"`) { t.Errorf(`compile("{x}") = %v, want a no-field error`, err) } } func TestTextIsLiteral(t *testing.T) { if got := mustRender(t, engine(1), `{"format":"100 Main St #1 {x}","x":"A"}`); got != "100 Main St #1 A" { t.Fatalf("text = %q, want it verbatim", got) } } func TestBraceEscapes(t *testing.T) { f := engine(1) for src, want := range map[string]string{ `{"format":"{{","x":"v"}`: "{", `{"format":"}}","x":"v"}`: "}", `{"format":"{{x}}","x":"v"}`: "{x}", `{"format":"{{{x}}}","x":"v"}`: "{v}", `{"format":"a{{{{b}}}}","x":"v"}`: "a{{b}}", } { if got := mustRender(t, f, src); got != want { t.Errorf("render(%s) = %q, want %q", src, got, want) } } for src, want := range map[string]string{ `"x}y"`: "}}", `"}"`: "}}", `{"format":"{a{b}","a":"Q"}`: "'{'", `"{x"`: "unterminated", } { if _, err := linked(t, src); err == nil || !strings.Contains(err.Error(), want) { t.Errorf("compile(%s) = %v, want an error mentioning %s", src, err, want) } } } func TestTokenSubstitution(t *testing.T) { if got := mustRender(t, engine(1), `{"format":"{x}sson","x":"Erik"}`); got != "Eriksson" { t.Fatalf("token = %q, want Eriksson", got) } } func TestAlternationDrawsOneField(t *testing.T) { f := engine(3) seen := map[string]bool{} for i := 0; i < 100; i++ { seen[mustRender(t, f, `{"format":"{a|b}","a":"A","b":"B"}`)] = true } if !seen["A"] || !seen["B"] || len(seen) != 2 { t.Fatalf("alternation produced %v, want both A and B", seen) } } func TestAlternationThreeWay(t *testing.T) { f := engine(4) seen := map[string]bool{} for i := 0; i < 200; i++ { seen[mustRender(t, f, `{"format":"{a|b|c}","a":"A","b":"B","c":"C"}`)] = true } if !seen["A"] || !seen["B"] || !seen["C"] || len(seen) != 3 { t.Fatalf("3-way alternation produced %v, want A, B and C", seen) } } func TestHashIsLiteral(t *testing.T) { f := engine(1) cases := map[string]string{ `{"format":"","x":"v"}`: "", `{"format":"#","x":"v"}`: "#", `{"format":"##","x":"v"}`: "##", `{"format":"#0#1#A#a","x":"v"}`: "#0#1#A#a", `{"format":"#{x}","x":"v"}`: "#v", } for tmpl, want := range cases { if got := mustRender(t, f, tmpl); got != want { t.Errorf("render(%s) = %q, want %q", tmpl, got, want) } } } func TestMultibyteFormat(t *testing.T) { // Scanning is rune-aware: multibyte literals coexist with class chars and // tokens without corrupting indices. got := mustRender(t, engine(2), `{"format":"Öster{x}-{digits(1)}å","x":"väg"}`) if !regexp.MustCompile(`^Österväg-[0-9]å$`).MatchString(got) { t.Fatalf("multibyte format = %q", got) } } func TestFormatCompileErrors(t *testing.T) { for _, bad := range []string{ `"{x"`, // unterminated brace `"x}y"`, // a lone } must be written }} `{"format":"{a{b}","a":"Q"}`, // a brace inside a token `"{x}"`, // a bare string has no fields to name `{"format":"{y}","x":"Q"}`, // token names a missing field `"{}"`, // empty token name `{"format":"{a|}","a":"Q"}`, // empty alternation segment `"{luhn(}"`, // malformed function token `"{lower()}"`, // wrong arity `"{nope()}"`, // unknown function `"{luhn(x)}"`, // function given args it takes none of `"{int(1)}"`, // wrong arity `"{float(1,2)}"`, // wrong arity } { if _, err := linked(t, bad); err == nil { t.Errorf("compile(%s) = nil error, want error", bad) } } }