A game about forced loneliness, made by TACStudios
1using System.Collections;
2using System.Linq;
3using NUnit.Framework;
4using UnityEngine;
5
6public class TextEditorTests
7{
8 TextEditor m_TextEditor;
9
10 static IEnumerable textWithCodePointBoundaryIndices
11 {
12 get
13 {
14 yield return new TestCaseData(null, new[] { 0 });
15 yield return new TestCaseData("", new[] { 0 });
16 yield return new TestCaseData("abc", new[] { 0, 1, 2, 3 });
17
18 yield return new TestCaseData("\U0001f642", new[] { 0, 2 }).SetName("(U+1F642)");
19 yield return new TestCaseData("\U0001f642\U0001f643", new[] { 0, 2, 4 }).SetName("(U+1F642)(U+1F643)");
20 yield return new TestCaseData("a\U0001f642b\U0001f643c", new[] { 0, 1, 3, 4, 6, 7 }).SetName("a(U+1F642)b(U+1F643)c");
21
22 yield return new TestCaseData("Hello 😁 World", new[] { 0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14 }).SetName("Hello (U+1F601) World");
23 }
24 }
25
26 static IEnumerable textWithWordStartAndEndIndices
27 {
28 get
29 {
30 yield return new TestCaseData(null, new int[0], new int[0]);
31 yield return new TestCaseData("", new int[0], new int[0]);
32 yield return new TestCaseData(" ", new int[0], new int[0]);
33 yield return new TestCaseData("one two three", new[] { 0, 4, 8 }, new[] { 3, 7, 13 });
34#if !UNITY_PLAYSTATION
35 //yield return new TestCaseData("\U00010000 \U00010001 \U00010002\U00010003", new[] { 0, 3, 6 }, new[] { 2, 5, 10 }).SetName("(U+10000) (U+10001) (U+10002)(U+10003)"); // Disabled for Instability https://jira.unity3d.com/browse/UUM-71065
36 //yield return new TestCaseData("Hello 😁 World", new[] { 0, 6, 9 }, new[] { 5, 8, 14 }).SetName("Hello (U+1F601) World"); // Disabled for Instability https://jira.unity3d.com/browse/UUM-71070
37#endif
38 }
39 }
40
41 // A sequences of punctuation characters is currently considered a word when deleting
42 static IEnumerable textWithWordStartAndEndIndicesWherePunctuationIsAWord
43 {
44 get
45 {
46 yield return new TestCaseData(" ,. abc,. ", new[] { 1, 4, 7 }, new[] { 3, 7, 9 });
47 }
48 }
49
50 // but not when moving/selecting
51 static IEnumerable textWithWordStartAndEndIndicesWherePunctuationIsNotAWord
52 {
53 get
54 {
55 yield return new TestCaseData(" ,. abc,. ", new[] { 4 }, new[] { 7 });
56 }
57 }
58
59 static IEnumerable textWithLineStartIndices
60 {
61 get
62 {
63 yield return new TestCaseData("\n\na\nbc\n\nd\n ", new[] { 0, 1, 2, 4, 7, 8, 10 }).SetName("(LF)(LF)a(LF)bc(LF)(LF)d(LF)");
64 yield return new TestCaseData("\n\na\nbc\n\U0001f642\n\U0001f642\U0001f643\n\n ", new[] { 0, 1, 2, 4, 7, 10, 15, 16 }).SetName("(LF)(LF)a(LF)bc(LF)(U+1F642)(LF)(U+1F642)(U+1F643)(LF)(LF) ");
65 yield return new TestCaseData("\n\na\nbc\n🙂\n🙂🙃\n\n ", new[] { 0, 1, 2, 4, 7, 10, 15, 16 }).SetName("(LF)(LF)a(LF)bc(LF)(U+1F642)(LF)(U+1F642)(U+1F643)(LF)(LF) ");
66 }
67 }
68
69 static IEnumerable textWithLineEndIndices
70 {
71 get
72 {
73 yield return new TestCaseData("\n\na\nbc\n\nd\n ", new[] { 0, 1, 3, 6, 7, 9, 11 }).SetName("(LF)(LF)a(LF)bc(LF)(LF)d(LF) ");
74 yield return new TestCaseData("\n\na\nbc\n\U0001f642\n\U0001f642\U0001f643\n\n ", new[] { 0, 1, 3, 6, 9, 14, 15, 17 }).SetName("(LF)(LF)a(LF)bc(LF)(U+1F642)(LF)(U+1F642)(U+1F643)(LF)(LF) ");
75 yield return new TestCaseData("\n\na\nbc\n🙂\n🙂🙃\n\n ", new[] { 0, 1, 3, 6, 9, 14, 15, 17 }).SetName("(LF)(LF)a(LF)bc(LF)(U+1F642)(LF)(U+1F642)(U+1F643)(LF)(LF) ");
76 }
77 }
78
79 static IEnumerable textWithExpectedCursorAndSelectIndicesWhenSelectingCurrentWordAtIndex
80 {
81 get
82 {
83 yield return new TestCaseData(null, new[] { 0 }, new[] { 0 });
84 yield return new TestCaseData("", new[] { 0 }, new[] { 0 });
85 yield return new TestCaseData(" ", new[] { 1, 1 }, new[] { 0, 0 });
86 yield return new TestCaseData("a", new[] { 1, 1 }, new[] { 0, 0 });
87 yield return new TestCaseData("ab", new[] { 2, 2, 2 }, new[] { 0, 0, 0 });
88 yield return new TestCaseData("ab cd", new[] { 2, 2, 4, 4, 6, 6, 6 }, new[] { 0, 0, 2, 2, 4, 4, 4 });
89 yield return new TestCaseData("a,, ,, ,,b", new[] { 1, 3, 3, 5, 5, 7, 7, 9, 9, 11, 11, 12, 12 }, new[] { 0, 1, 1, 3, 3, 5, 5, 7, 7, 9, 9, 11, 11 });
90 }
91 }
92
93 [SetUp]
94 public void TestSetup()
95 {
96 m_TextEditor = new TextEditor();
97 m_TextEditor.DetectFocusChange();
98 }
99
100 [Test]
101 public void SetText_MovesCursorAndSelectIndicesToNextCodePointIndexIfInvalid()
102 {
103 m_TextEditor.text = "ab";
104 m_TextEditor.UpdateTextHandle();
105
106 m_TextEditor.stringCursorIndex = m_TextEditor.stringSelectIndex = 1;
107
108 m_TextEditor.text = "\U0001f642";
109 m_TextEditor.UpdateTextHandle();
110
111 Assert.AreEqual(2, m_TextEditor.stringCursorIndex, "cursorIndex at invalid code point index");
112 Assert.AreEqual(2, m_TextEditor.stringSelectIndex, "selectIndex at invalid code point index");
113 }
114
115 [Test, TestCaseSource("textWithCodePointBoundaryIndices")]
116 public void SetCursorAndSelectIndices_MovesToNextCodePointIndexIfInvalid(string text, int[] codePointIndices)
117 {
118 m_TextEditor.text = text;
119 m_TextEditor.UpdateTextHandle();
120
121 for (var index = 0; index <= GetLength(text); index++)
122 {
123 m_TextEditor.stringCursorIndex = index;
124 m_TextEditor.stringSelectIndex = index;
125
126 var nextCodePointIndex = index == GetLength(text) ? index : codePointIndices.First(codePointIndex => codePointIndex > index);
127 if (codePointIndices.Contains(index))
128 Assert.AreEqual(index, m_TextEditor.stringCursorIndex, string.Format("cursorIndex {0} should not change if it's already at a valid code point index", index));
129 else
130 Assert.AreEqual(nextCodePointIndex, m_TextEditor.stringCursorIndex, string.Format("cursorIndex {0} did not move to next code point index", index));
131 if (codePointIndices.Contains(index))
132 Assert.AreEqual(index, m_TextEditor.stringSelectIndex, string.Format("selectIndex {0} should not change if it's already at a valid code point index", index));
133 else
134 Assert.AreEqual(nextCodePointIndex, m_TextEditor.stringSelectIndex, string.Format("selectIndex {0} did not move to next code point index", index));
135 }
136 }
137
138 [Test]
139 [TestCaseSource("textWithWordStartAndEndIndices")]
140 [TestCaseSource("textWithWordStartAndEndIndicesWherePunctuationIsAWord")]
141 public void DeleteWordBack_DeletesBackToPreviousWordStart(string text, int[] wordStartIndices, int[] wordEndIndices)
142 {
143 for (var stringIndex = 0; stringIndex <= GetLength(text); stringIndex++)
144 {
145 m_TextEditor.text = text;
146 m_TextEditor.UpdateTextHandle();
147 m_TextEditor.stringCursorIndex = m_TextEditor.stringSelectIndex = stringIndex;
148 var oldCursorIndex = m_TextEditor.stringCursorIndex;
149 var oldSelectIndex = m_TextEditor.stringSelectIndex;
150
151 m_TextEditor.DeleteWordBack();
152
153 var previousWordStart = wordStartIndices.Reverse().FirstOrDefault(i => i < oldCursorIndex);
154 Assert.AreEqual(previousWordStart, m_TextEditor.stringCursorIndex, string.Format("cursorIndex {0} did not move to previous word start", oldCursorIndex));
155 Assert.AreEqual(previousWordStart, m_TextEditor.stringSelectIndex, string.Format("selectIndex {0} did not move to previous word start", oldSelectIndex));
156 if (text != null)
157 Assert.AreEqual(text.Remove(previousWordStart, oldCursorIndex - previousWordStart), m_TextEditor.text, string.Format("wrong resulting text for cursorIndex {0}", oldCursorIndex));
158 }
159 }
160
161 [Test]
162 [TestCaseSource("textWithWordStartAndEndIndices")]
163 [TestCaseSource("textWithWordStartAndEndIndicesWherePunctuationIsAWord")]
164 public void DeleteWordForward_DeletesForwardToNextWordStart(string text, int[] wordStartIndices, int[] wordEndIndices)
165 {
166 for (var stringIndex = 0; stringIndex <= GetLength(text); stringIndex++)
167 {
168 m_TextEditor.text = text;
169 m_TextEditor.UpdateTextHandle();
170 m_TextEditor.stringCursorIndex = m_TextEditor.stringSelectIndex = stringIndex;
171 var oldCursorIndex = m_TextEditor.stringCursorIndex;
172 var oldSelectIndex = m_TextEditor.stringSelectIndex;
173
174 m_TextEditor.DeleteWordForward();
175
176 Assert.AreEqual(oldCursorIndex, m_TextEditor.stringCursorIndex, string.Format("cursorIndex {0} should not change", oldCursorIndex));
177 Assert.AreEqual(oldSelectIndex, m_TextEditor.stringSelectIndex, string.Format("selectIndex {0} should not change", oldSelectIndex));
178 if (text != null)
179 {
180 var nextWordStart = oldCursorIndex == text.Length ? text.Length : wordStartIndices.Concat(new[] { text.Length }).First(i => i > oldCursorIndex);
181 Assert.AreEqual(text.Remove(oldCursorIndex, nextWordStart - oldCursorIndex), m_TextEditor.text, string.Format("wrong resulting text for cursorIndex {0}", oldCursorIndex));
182 }
183 }
184 }
185
186 [Test, TestCaseSource("textWithCodePointBoundaryIndices")]
187 public void Delete_RemovesCodePointRightOfCursor(string text, int[] codePointIndices)
188 {
189 for (var i = 0; i < codePointIndices.Length; i++)
190 {
191 var codePointIndex = codePointIndices[i];
192 m_TextEditor.text = text;
193 m_TextEditor.UpdateTextHandle();
194 m_TextEditor.stringCursorIndex = m_TextEditor.stringSelectIndex = codePointIndex;
195
196 m_TextEditor.Delete();
197
198 var nextCodePointIndex = i < codePointIndices.Length - 1 ? codePointIndices[i + 1] : codePointIndex;
199 Assert.AreEqual(codePointIndex, m_TextEditor.stringCursorIndex, "cursorIndex should not change");
200 Assert.AreEqual(codePointIndex, m_TextEditor.stringSelectIndex, "selectIndex should not change");
201 var expectedText = text == null? "" : text.Remove(codePointIndex, nextCodePointIndex - codePointIndex);
202 Assert.AreEqual(expectedText, m_TextEditor.text, string.Format("wrong resulting text for cursorIndex {0}", codePointIndex));
203 }
204 }
205
206 [Test, TestCaseSource("textWithCodePointBoundaryIndices")]
207 public void Backspace_RemovesCodePointLeftOfCursor(string text, int[] codePointIndices)
208 {
209 for (var i = codePointIndices.Length - 1; i >= 0; i--)
210 {
211 var codePointIndex = codePointIndices[i];
212 m_TextEditor.text = text;
213 m_TextEditor.UpdateTextHandle();
214 m_TextEditor.m_TextEditing.stringCursorIndex = m_TextEditor.m_TextEditing.stringSelectIndex = codePointIndex;
215 var oldCursorIndex = m_TextEditor.m_TextEditing.stringCursorIndex;
216 var oldSelectIndex = m_TextEditor.m_TextEditing.stringSelectIndex;
217
218 m_TextEditor.Backspace();
219 m_TextEditor.UpdateTextHandle();
220
221 var previousCodePointIndex = i > 0 ? codePointIndices[i - 1] : codePointIndex;
222 var codePointLength = codePointIndex - previousCodePointIndex;
223 Assert.AreEqual(oldCursorIndex - codePointLength, m_TextEditor.stringCursorIndex, string.Format("cursorIndex {0} did not move to before removed code point", oldCursorIndex));
224 Assert.AreEqual(oldSelectIndex - codePointLength, m_TextEditor.stringSelectIndex, string.Format("selectIndex {0} did not move to before removed code point", oldSelectIndex));
225 var expectedText = text == null ? "" : text.Remove(previousCodePointIndex, codePointLength);
226 Assert.AreEqual(expectedText, m_TextEditor.text);
227 }
228 }
229
230 [Test, TestCaseSource("textWithCodePointBoundaryIndices")]
231 public void MoveRight_SkipsInvalidCodePointIndices(string text, int[] codePointIndices)
232 {
233 m_TextEditor.text = text;
234 m_TextEditor.UpdateTextHandle();
235 m_TextEditor.cursorIndex = m_TextEditor.selectIndex = 0;
236
237 foreach (var expectedIndex in codePointIndices.Skip(1))
238 {
239 var oldCursorIndex = m_TextEditor.stringCursorIndex;
240 var oldSelectIndex = m_TextEditor.stringSelectIndex;
241
242 m_TextEditor.MoveRight();
243
244 Assert.AreEqual(expectedIndex, m_TextEditor.stringCursorIndex, string.Format("cursorIndex {0} did not move to next code point index", oldCursorIndex));
245 Assert.AreEqual(expectedIndex, m_TextEditor.stringSelectIndex, string.Format("selectIndex {0} did not move to next code point index", oldSelectIndex));
246 }
247
248 var length = GetLength(text);
249 Assert.AreEqual(length, m_TextEditor.stringCursorIndex, "cursorIndex did not reach end");
250 Assert.AreEqual(length, m_TextEditor.stringSelectIndex, "selectIndex did not reach end");
251
252 m_TextEditor.MoveRight();
253
254 Assert.AreEqual(length, m_TextEditor.stringCursorIndex, "cursorIndex at end should not change");
255 Assert.AreEqual(length, m_TextEditor.stringSelectIndex, "selectIndex at end should not change");
256 }
257
258 [Test, TestCaseSource("textWithCodePointBoundaryIndices")]
259 public void MoveLeft_SkipsInvalidCodePointIndices(string text, int[] codePointIndices)
260 {
261 m_TextEditor.text = text;
262 m_TextEditor.UpdateTextHandle();
263 m_TextEditor.cursorIndex = m_TextEditor.selectIndex = GetLength(text);
264
265 foreach (var expectedIndex in codePointIndices.Reverse().Skip(1))
266 {
267 var oldCursorIndex = m_TextEditor.stringCursorIndex;
268 var oldSelectIndex = m_TextEditor.stringSelectIndex;
269
270 m_TextEditor.MoveLeft();
271
272 Assert.AreEqual(expectedIndex, m_TextEditor.stringCursorIndex, string.Format("cursorIndex {0} did not move to previous code point index", oldCursorIndex));
273 Assert.AreEqual(expectedIndex, m_TextEditor.stringSelectIndex, string.Format("selectIndex {0} did not move to previous code point index", oldSelectIndex));
274 }
275
276 Assert.AreEqual(0, m_TextEditor.stringCursorIndex, "cursorIndex did not reach start");
277 Assert.AreEqual(0, m_TextEditor.stringSelectIndex, "selectIndex did not reach start");
278
279 m_TextEditor.MoveLeft();
280
281 Assert.AreEqual(0, m_TextEditor.stringCursorIndex, "cursorIndex at start should not change");
282 Assert.AreEqual(0, m_TextEditor.stringSelectIndex, "selectIndex at start should not change");
283 }
284
285 [Test, TestCaseSource("textWithLineStartIndices")]
286 public void MoveLineStart_MovesCursorAfterPreviousLineFeed(string text, int[] lineStartIndices)
287 {
288 m_TextEditor.text = text;
289 m_TextEditor.UpdateTextHandle();
290
291 for (var index = 0; index <= GetLength(text); index++)
292 {
293 m_TextEditor.stringCursorIndex = m_TextEditor.stringSelectIndex = index;
294 var oldCursorIndex = m_TextEditor.stringCursorIndex;
295 var oldSelectIndex = m_TextEditor.stringSelectIndex;
296
297 m_TextEditor.MoveLineStart();
298
299 var lineStart = lineStartIndices.Reverse().FirstOrDefault(i => i <= oldCursorIndex);
300 Assert.AreEqual(lineStart, m_TextEditor.stringCursorIndex, string.Format("cursorIndex {0} did not move to line start", oldCursorIndex));
301 Assert.AreEqual(lineStart, m_TextEditor.stringSelectIndex, string.Format("selectIndex {0} did not move to line start", oldSelectIndex));
302 }
303 }
304
305 [Test, TestCaseSource("textWithLineEndIndices")]
306 public void MoveLineEnd_MovesCursorBeforeNextLineFeed(string text, int[] lineEndIndices)
307 {
308 m_TextEditor.text = text;
309 m_TextEditor.UpdateTextHandle();
310
311 for (var index = 0; index <= GetLength(text); index++)
312 {
313 m_TextEditor.stringCursorIndex = m_TextEditor.stringSelectIndex = index;
314 var oldCursorIndex = m_TextEditor.stringCursorIndex;
315 var oldSelectIndex = m_TextEditor.stringSelectIndex;
316
317 m_TextEditor.MoveLineEnd();
318
319 var lineEnd = lineEndIndices.First(i => i >= oldCursorIndex);
320 Assert.AreEqual(lineEnd, m_TextEditor.stringCursorIndex, string.Format("cursorIndex {0} did not move to line end", oldCursorIndex));
321 Assert.AreEqual(lineEnd, m_TextEditor.stringSelectIndex, string.Format("selectIndex {0} did not move to line end", oldSelectIndex));
322 }
323 }
324
325 [Test]
326 public void MoveTextStart_MovesCursorToStartOfText()
327 {
328 m_TextEditor.text = "Hello World";
329 m_TextEditor.UpdateTextHandle();
330 m_TextEditor.cursorIndex = m_TextEditor.selectIndex = 5;
331
332 m_TextEditor.MoveTextStart();
333
334 Assert.AreEqual(0, m_TextEditor.cursorIndex, "cursorIndex did not move to start of text");
335 Assert.AreEqual(0, m_TextEditor.selectIndex, "selectIndex did not move to start of text");
336 }
337
338 [Test]
339 public void MoveTextEnd_MovesCursorToEndOfText()
340 {
341 m_TextEditor.text = "Hello World";
342 m_TextEditor.UpdateTextHandle();
343 m_TextEditor.cursorIndex = m_TextEditor.selectIndex = 5;
344
345 m_TextEditor.MoveTextEnd();
346
347 Assert.AreEqual(m_TextEditor.text.Length, m_TextEditor.cursorIndex, "cursorIndex did not move to end of text");
348 Assert.AreEqual(m_TextEditor.text.Length, m_TextEditor.selectIndex, "selectIndex did not move to end of text");
349 }
350
351 [Test, TestCaseSource("textWithCodePointBoundaryIndices")]
352 public void SelectLeft_ExpandSelectionToPreviousCodePoint(string text, int[] codePointIndices)
353 {
354 m_TextEditor.text = text;
355 m_TextEditor.UpdateTextHandle();
356 m_TextEditor.stringCursorIndex = m_TextEditor.stringSelectIndex = GetLength(text);
357
358 foreach (var expectedCursorIndex in codePointIndices.Reverse().Skip(1))
359 {
360 var oldCursorIndex = m_TextEditor.stringCursorIndex;
361 var oldSelectIndex = m_TextEditor.stringSelectIndex;
362
363 m_TextEditor.SelectLeft();
364
365 Assert.AreEqual(expectedCursorIndex, m_TextEditor.stringCursorIndex, string.Format("cursorIndex {0} did not move to previous code point index", oldCursorIndex));
366 Assert.AreEqual(oldSelectIndex, m_TextEditor.stringSelectIndex, "selectIndex should not change");
367 }
368
369 Assert.AreEqual(0, m_TextEditor.stringCursorIndex, "cursorIndex did not reach start");
370
371 m_TextEditor.SelectLeft();
372
373 Assert.AreEqual(0, m_TextEditor.stringCursorIndex, "cursorIndex at start should not change");
374 }
375
376 [Test, TestCaseSource("textWithCodePointBoundaryIndices")]
377 public void SelectRight_ExpandSelectionToNextCodePoint(string text, int[] codePointIndices)
378 {
379 m_TextEditor.text = text;
380 m_TextEditor.UpdateTextHandle();
381 m_TextEditor.stringCursorIndex = m_TextEditor.stringSelectIndex = 0;
382
383 foreach (var expectedCursorIndex in codePointIndices.Skip(1))
384 {
385 var oldCursorIndex = m_TextEditor.stringCursorIndex;
386 var oldSelectIndex = m_TextEditor.stringSelectIndex;
387
388 m_TextEditor.SelectRight();
389
390 Assert.AreEqual(expectedCursorIndex, m_TextEditor.stringCursorIndex, string.Format("cursorIndex {0} did not move to next code point index", oldCursorIndex));
391 Assert.AreEqual(oldSelectIndex, m_TextEditor.stringSelectIndex, "selectIndex should not change");
392 }
393
394 Assert.AreEqual(GetLength(text), m_TextEditor.stringCursorIndex, "cursorIndex did not reach end");
395
396 m_TextEditor.SelectRight();
397
398 Assert.AreEqual(GetLength(text), m_TextEditor.stringCursorIndex, "cursorIndex at end should not change");
399 }
400
401 [Test]
402 [TestCaseSource("textWithWordStartAndEndIndices")]
403 [TestCaseSource("textWithWordStartAndEndIndicesWherePunctuationIsNotAWord")]
404 public void MoveWordRight_MovesCursorToNextWordEnd(string text, int[] wordStartIndices, int[] wordEndIndices)
405 {
406 if (text != null && text.Any(char.IsSurrogate))
407 return; // char.IsLetterOrDigit(string, int) does not currently work correctly with surrogates
408
409 m_TextEditor.text = text;
410 m_TextEditor.UpdateTextHandle();
411
412 for (var index = 0; index <= GetLength(text); index++)
413 {
414 m_TextEditor.cursorIndex = m_TextEditor.selectIndex = index;
415 var oldCursorIndex = m_TextEditor.cursorIndex;
416 var oldSelectIndex = m_TextEditor.selectIndex;
417
418 m_TextEditor.MoveWordRight();
419
420 var nextWordEnd = wordEndIndices.FirstOrDefault(i => i > oldCursorIndex);
421 if (nextWordEnd == 0)
422 nextWordEnd = GetLength(text);
423 Assert.AreEqual(nextWordEnd, m_TextEditor.cursorIndex, string.Format("cursorIndex {0} did not move to next word start", oldCursorIndex));
424 Assert.AreEqual(nextWordEnd, m_TextEditor.selectIndex, string.Format("selectIndex {0} did not move to next word start", oldSelectIndex));
425 }
426 }
427
428 [Test]
429 [TestCaseSource("textWithWordStartAndEndIndices")]
430 [TestCaseSource("textWithWordStartAndEndIndicesWherePunctuationIsAWord")]
431 public void MoveToStartOfNextWord_MovesCursorToNextWordStart(string text, int[] wordStartIndices, int[] wordEndIndices)
432 {
433 m_TextEditor.text = text;
434 m_TextEditor.UpdateTextHandle();
435
436 for (var index = 0; index <= GetLength(text); index++)
437 {
438 m_TextEditor.stringCursorIndex = m_TextEditor.stringSelectIndex = index;
439 var oldCursorIndex = m_TextEditor.stringCursorIndex;
440 var oldSelectIndex = m_TextEditor.stringSelectIndex;
441
442 m_TextEditor.MoveToStartOfNextWord();
443
444 var length = GetLength(text);
445 var nextWordStart = oldCursorIndex == length ? length : wordStartIndices.Concat(new[] { length }).First(i => i > oldCursorIndex);
446 Assert.AreEqual(nextWordStart, m_TextEditor.stringCursorIndex, string.Format("cursorIndex {0} did not move to start of next word", oldCursorIndex));
447 Assert.AreEqual(nextWordStart, m_TextEditor.stringSelectIndex, string.Format("selectIndex {0} did not move to start of next word", oldSelectIndex));
448 }
449 }
450
451 [Test]
452 [TestCaseSource("textWithWordStartAndEndIndices")]
453 [TestCaseSource("textWithWordStartAndEndIndicesWherePunctuationIsAWord")]
454 public void MoveToEndOfPreviousWord_MovesCursorToPreviousWordStart(string text, int[] wordStartIndices, int[] wordEndIndices)
455 {
456 m_TextEditor.text = text;
457 m_TextEditor.UpdateTextHandle();
458
459 for (var index = 0; index <= GetLength(text); index++)
460 {
461 m_TextEditor.stringCursorIndex = m_TextEditor.stringSelectIndex = index;
462 var oldCursorIndex = m_TextEditor.stringCursorIndex;
463 var oldSelectIndex = m_TextEditor.stringSelectIndex;
464
465 m_TextEditor.MoveToEndOfPreviousWord();
466
467 var previousWordStart = wordStartIndices.Reverse().FirstOrDefault(i => i < oldCursorIndex);
468 Assert.AreEqual(previousWordStart, m_TextEditor.stringCursorIndex, string.Format("cursorIndex {0} did not move to previous word start", oldCursorIndex));
469 Assert.AreEqual(previousWordStart, m_TextEditor.stringSelectIndex, string.Format("selectIndex {0} did not move to previous word start", oldSelectIndex));
470 }
471 }
472
473 [Test]
474 [TestCaseSource("textWithWordStartAndEndIndices")]
475 [TestCaseSource("textWithWordStartAndEndIndicesWherePunctuationIsAWord")]
476 public void FindStartOfNextWord_ReturnsIndexOfNextWordStart(string text, int[] wordStartIndices, int[] wordEndIndices)
477 {
478 if (text != null && text.Any(char.IsSurrogate))
479 return; // char.IsLetterOrDigit(string, int) does not currently work correctly with surrogates
480
481 m_TextEditor.text = text;
482 m_TextEditor.UpdateTextHandle();
483
484 for (var index = 0; index <= GetLength(text); index++)
485 {
486 var length = GetLength(text);
487 var nextWordStart = index == length ? length : wordStartIndices.Concat(new[] {length}).First(i => i > index);
488 Assert.AreEqual(nextWordStart, m_TextEditor.FindStartOfNextWord(index));
489 }
490 }
491
492 [Test]
493 [TestCaseSource("textWithWordStartAndEndIndices")]
494 [TestCaseSource("textWithWordStartAndEndIndicesWherePunctuationIsNotAWord")]
495 public void MoveWordLeft_MovesCursorToPreviousWordStart(string text, int[] wordStartIndices, int[] wordEndIndices)
496 {
497 if (text != null && text.Any(char.IsSurrogate))
498 return; // char.IsLetterOrDigit(string, int) does not currently work correctly with surrogates
499
500 m_TextEditor.text = text;
501 m_TextEditor.UpdateTextHandle();
502
503 for (var index = 0; index <= GetLength(text); index++)
504 {
505 m_TextEditor.cursorIndex = m_TextEditor.selectIndex = index;
506 var oldCursorIndex = m_TextEditor.cursorIndex;
507 var oldSelectIndex = m_TextEditor.selectIndex;
508
509 m_TextEditor.MoveWordLeft();
510
511 var previousWordStart = wordStartIndices.Reverse().FirstOrDefault(i => i < oldCursorIndex);
512 Assert.AreEqual(previousWordStart, m_TextEditor.cursorIndex, string.Format("cursorIndex {0} did not move to previous word start", oldCursorIndex));
513 Assert.AreEqual(previousWordStart, m_TextEditor.selectIndex, string.Format("selectIndex {0} did not move to previous word start", oldSelectIndex));
514 }
515 }
516
517 [Test, TestCaseSource("textWithExpectedCursorAndSelectIndicesWhenSelectingCurrentWordAtIndex")]
518 public void SelectCurrentWord(string text, int[] expectedCursorIndices, int[] expectedSelectIndices)
519 {
520 m_TextEditor.text = text;
521 m_TextEditor.UpdateTextHandle();
522
523 for (var index = 0; index <= GetLength(text); index++)
524 {
525 m_TextEditor.cursorIndex = m_TextEditor.selectIndex = index;
526 var oldCursorIndex = m_TextEditor.cursorIndex;
527
528 m_TextEditor.SelectCurrentWord();
529
530 Assert.AreEqual(expectedCursorIndices[index], m_TextEditor.cursorIndex, string.Format("wrong cursorIndex for initial cursorIndex {0}", oldCursorIndex));
531 Assert.AreEqual(expectedSelectIndices[index], m_TextEditor.selectIndex, string.Format("wrong selectIndex for initial cursorIndex {0}", oldCursorIndex));
532 }
533 }
534
535 [Test]
536 public void HandleKeyEvent_WithControlAKeyDownEvent_MovesCursorToStartOfLineOnMacOS_SelectsAllElsewhere()
537 {
538 const string text = "foo";
539 m_TextEditor.text = text;
540 m_TextEditor.UpdateTextHandle();
541 m_TextEditor.MoveLineEnd();
542 var controlAKeyDownEvent = new Event { type = EventType.KeyDown, keyCode = KeyCode.A, modifiers = EventModifiers.Control };
543
544 m_TextEditor.HandleKeyEvent(controlAKeyDownEvent);
545
546 if (SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX)
547 {
548 Assert.That(m_TextEditor.SelectedText, Is.Empty, "Selected text was not empty");
549 Assert.That(m_TextEditor.cursorIndex, Is.EqualTo(0), "Cursor did not move to start of line");
550 }
551 else
552 Assert.That(m_TextEditor.SelectedText, Is.EqualTo(text), "Text was not selected");
553 }
554
555 [Test]
556 public void HandleKeyEvent_WithCommandAKeyDownEvent_SelectsAllOnMacOS()
557 {
558 if (SystemInfo.operatingSystemFamily != OperatingSystemFamily.MacOSX)
559 Assert.Ignore("Test is only applicable on macOS");
560
561 const string text = "foo";
562 m_TextEditor.text = text;
563 m_TextEditor.UpdateTextHandle();
564 var commandAKeyDownEvent = new Event { type = EventType.KeyDown, keyCode = KeyCode.A, modifiers = EventModifiers.Command };
565
566 m_TextEditor.HandleKeyEvent(commandAKeyDownEvent);
567
568 Assert.That(m_TextEditor.SelectedText, Is.EqualTo(text), "Text was not selected");
569 }
570
571 int GetLength(string text) => text == null ? 0 : text.Length;
572}