1// Copyright 2016 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package bidirule
6
7import (
8	"fmt"
9	"testing"
10
11	"golang.org/x/text/internal/testtext"
12	"golang.org/x/text/unicode/bidi"
13)
14
15const (
16	strL   = "ABC"    // Left to right - most letters in LTR scripts
17	strR   = "עברית"  // Right to left - most letters in non-Arabic RTL scripts
18	strAL  = "دبي"    // Arabic letters - most letters in the Arabic script
19	strEN  = "123"    // European Number (0-9, and Extended Arabic-Indic numbers)
20	strES  = "+-"     // European Number Separator (+ and -)
21	strET  = "$"      // European Number Terminator (currency symbols, the hash sign, the percent sign and so on)
22	strAN  = "\u0660" // Arabic Number; this encompasses the Arabic-Indic numbers, but not the Extended Arabic-Indic numbers
23	strCS  = ","      // Common Number Separator (. , / : et al)
24	strNSM = "\u0300" // Nonspacing Mark - most combining accents
25	strBN  = "\u200d" // Boundary Neutral - control characters (ZWNJ, ZWJ, and others)
26	strB   = "\u2029" // Paragraph Separator
27	strS   = "\u0009" // Segment Separator
28	strWS  = " "      // Whitespace, including the SPACE character
29	strON  = "@"      // Other Neutrals, including @, &, parentheses, MIDDLE DOT
30)
31
32type ruleTest struct {
33	in  string
34	dir bidi.Direction
35	n   int // position at which the rule fails
36	err error
37
38	// For tests that split the string in two.
39	pSrc  int   // number of source bytes to consume first
40	szDst int   // size of destination buffer
41	nSrc  int   // source bytes consumed and bytes written
42	err0  error // error after first run
43}
44
45func init() {
46	for rule, cases := range testCases {
47		for i, tc := range cases {
48			if tc.err == nil {
49				testCases[rule][i].n = len(tc.in)
50			}
51		}
52	}
53}
54
55func doTests(t *testing.T, fn func(t *testing.T, tc ruleTest)) {
56	for rule, cases := range testCases {
57		for i, tc := range cases {
58			name := fmt.Sprintf("%d/%d:%+q:%s", rule, i, tc.in, tc.in)
59			testtext.Run(t, name, func(t *testing.T) {
60				fn(t, tc)
61			})
62		}
63	}
64}
65
66func TestDirection(t *testing.T) {
67	doTests(t, func(t *testing.T, tc ruleTest) {
68		dir := Direction([]byte(tc.in))
69		if dir != tc.dir {
70			t.Errorf("dir was %v; want %v", dir, tc.dir)
71		}
72	})
73}
74
75func TestDirectionString(t *testing.T) {
76	doTests(t, func(t *testing.T, tc ruleTest) {
77		dir := DirectionString(tc.in)
78		if dir != tc.dir {
79			t.Errorf("dir was %v; want %v", dir, tc.dir)
80		}
81	})
82}
83
84func TestValid(t *testing.T) {
85	doTests(t, func(t *testing.T, tc ruleTest) {
86		got := Valid([]byte(tc.in))
87		want := tc.err == nil
88		if got != want {
89			t.Fatalf("Valid: got %v; want %v", got, want)
90		}
91
92		got = ValidString(tc.in)
93		want = tc.err == nil
94		if got != want {
95			t.Fatalf("Valid: got %v; want %v", got, want)
96		}
97	})
98}
99
100func TestSpan(t *testing.T) {
101	doTests(t, func(t *testing.T, tc ruleTest) {
102		// Skip tests that test for limited destination buffer size.
103		if tc.szDst > 0 {
104			return
105		}
106
107		r := New()
108		src := []byte(tc.in)
109
110		n, err := r.Span(src[:tc.pSrc], tc.pSrc == len(tc.in))
111		if err != tc.err0 {
112			t.Errorf("err0 was %v; want %v", err, tc.err0)
113		}
114		if n != tc.nSrc {
115			t.Fatalf("nSrc was %d; want %d", n, tc.nSrc)
116		}
117
118		n, err = r.Span(src[n:], true)
119		if err != tc.err {
120			t.Errorf("error was %v; want %v", err, tc.err)
121		}
122		if got := n + tc.nSrc; got != tc.n {
123			t.Errorf("n was %d; want %d", got, tc.n)
124		}
125	})
126}
127
128func TestTransform(t *testing.T) {
129	doTests(t, func(t *testing.T, tc ruleTest) {
130		r := New()
131
132		src := []byte(tc.in)
133		dst := make([]byte, len(tc.in))
134		if tc.szDst > 0 {
135			dst = make([]byte, tc.szDst)
136		}
137
138		// First transform operates on a zero-length string for most tests.
139		nDst, nSrc, err := r.Transform(dst, src[:tc.pSrc], tc.pSrc == len(tc.in))
140		if err != tc.err0 {
141			t.Errorf("err0 was %v; want %v", err, tc.err0)
142		}
143		if nDst != nSrc {
144			t.Fatalf("nDst (%d) and nSrc (%d) should match", nDst, nSrc)
145		}
146		if nSrc != tc.nSrc {
147			t.Fatalf("nSrc was %d; want %d", nSrc, tc.nSrc)
148		}
149
150		dst1 := make([]byte, len(tc.in))
151		copy(dst1, dst[:nDst])
152
153		nDst, nSrc, err = r.Transform(dst1[nDst:], src[nSrc:], true)
154		if err != tc.err {
155			t.Errorf("error was %v; want %v", err, tc.err)
156		}
157		if nDst != nSrc {
158			t.Fatalf("nDst (%d) and nSrc (%d) should match", nDst, nSrc)
159		}
160		n := nSrc + tc.nSrc
161		if n != tc.n {
162			t.Fatalf("n was %d; want %d", n, tc.n)
163		}
164		if got, want := string(dst1[:n]), tc.in[:tc.n]; got != want {
165			t.Errorf("got %+q; want %+q", got, want)
166		}
167	})
168}
169