1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5// Include test fixture. 6GEN_INCLUDE(['../testing/chromevox_unittest_base.js']); 7 8UnserializableSpan = function() {}; 9 10StatelessSerializableSpan = function() {}; 11 12NonStatelessSerializableSpan = function(value) { 13 this.value = value; 14}; 15 16/** 17 * @param {!Object} obj object containing the 18 * serializable representation. 19 * @return {!Object} The Spannable. 20 */ 21NonStatelessSerializableSpan.fromJson = function(obj) { 22 return new NonStatelessSerializableSpan(obj.value / 2); 23}; 24 25/** 26 * @return {Object} the json serializable form. 27 */ 28NonStatelessSerializableSpan.prototype.toJson = function() { 29 return {value: this.value * 2}; 30}; 31 32/** 33 * Test fixture. 34 * @constructor 35 * @extends {ChromeVoxUnitTestBase} 36 */ 37function CvoxSpannableUnitTest() {} 38 39CvoxSpannableUnitTest.prototype = { 40 __proto__: ChromeVoxUnitTestBase.prototype, 41 42 /** @override */ 43 closureModuleDeps: [ 44 'cvox.Spannable', 45 ], 46 47 /** @override */ 48 setUp: function() { 49 cvox.Spannable.registerStatelessSerializableSpan( 50 StatelessSerializableSpan, 'StatelessSerializableSpan'); 51 52 cvox.Spannable.registerSerializableSpan( 53 NonStatelessSerializableSpan, 'NonStatelessSerializableSpan', 54 NonStatelessSerializableSpan.fromJson, 55 NonStatelessSerializableSpan.prototype.toJson); 56 } 57}; 58 59TEST_F('CvoxSpannableUnitTest', 'ToStringUnannotated', function() { 60 assertEquals('', new cvox.Spannable().toString()); 61 assertEquals('hello world', new cvox.Spannable('hello world').toString()); 62}); 63 64/** Tests that toString works correctly on annotated strings. */ 65TEST_F('CvoxSpannableUnitTest', 'ToStringAnnotated', function() { 66 var spannable = new cvox.Spannable('Hello Google'); 67 spannable.setSpan('http://www.google.com/', 6, 12); 68 assertEquals('Hello Google', spannable.toString()); 69}); 70 71/** Tests the length calculation. */ 72TEST_F('CvoxSpannableUnitTest', 'GetLength', function() { 73 var spannable = new cvox.Spannable('Hello'); 74 spannable.setSpan({}, 0, 3); 75 assertEquals(5, spannable.getLength()); 76 spannable.append(' world'); 77 assertEquals(11, spannable.getLength()); 78 spannable.append(new cvox.Spannable(' from cvox.Spannable')); 79 assertEquals(31, spannable.getLength()); 80}); 81 82/** Tests that a span can be added and retrieved at the beginning. */ 83TEST_F('CvoxSpannableUnitTest', 'SpanBeginning', function() { 84 var annotation = {}; 85 var spannable = new cvox.Spannable('Hello world'); 86 spannable.setSpan(annotation, 0, 5); 87 assertSame(annotation, spannable.getSpan(0)); 88 assertSame(annotation, spannable.getSpan(3)); 89 assertUndefined(spannable.getSpan(5)); 90 assertUndefined(spannable.getSpan(8)); 91}); 92 93/** Tests that a span can be added and retrieved at the beginning. */ 94TEST_F('CvoxSpannableUnitTest', 'SpanEnd', function() { 95 var annotation = {}; 96 var spannable = new cvox.Spannable('Hello world'); 97 spannable.setSpan(annotation, 6, 11); 98 assertUndefined(spannable.getSpan(3)); 99 assertUndefined(spannable.getSpan(5)); 100 assertSame(annotation, spannable.getSpan(6)); 101 assertSame(annotation, spannable.getSpan(10)); 102}); 103 104/** Tests that a zero-length span is not retrieved. */ 105TEST_F('CvoxSpannableUnitTest', 'SpanZeroLength', function() { 106 var annotation = {}; 107 var spannable = new cvox.Spannable('Hello world'); 108 spannable.setSpan(annotation, 3, 3); 109 assertUndefined(spannable.getSpan(2)); 110 assertUndefined(spannable.getSpan(3)); 111 assertUndefined(spannable.getSpan(4)); 112}); 113 114/** Tests that a removed span is not returned. */ 115TEST_F('CvoxSpannableUnitTest', 'RemoveSpan', function() { 116 var annotation = {}; 117 var spannable = new cvox.Spannable('Hello world'); 118 spannable.setSpan(annotation, 0, 3); 119 assertSame(annotation, spannable.getSpan(1)); 120 spannable.removeSpan(annotation); 121 assertUndefined(spannable.getSpan(1)); 122}); 123 124/** Tests that adding a span in one place removes it from another. */ 125TEST_F('CvoxSpannableUnitTest', 'SetSpanMoves', function() { 126 var annotation = {}; 127 var spannable = new cvox.Spannable('Hello world'); 128 spannable.setSpan(annotation, 0, 3); 129 assertSame(annotation, spannable.getSpan(1)); 130 assertUndefined(spannable.getSpan(4)); 131 spannable.setSpan(annotation, 3, 6); 132 assertUndefined(spannable.getSpan(1)); 133 assertSame(annotation, spannable.getSpan(4)); 134}); 135 136/** Tests that setSpan objects to out-of-range arguments. */ 137TEST_F('CvoxSpannableUnitTest', 'SetSpanRangeError', function() { 138 var spannable = new cvox.Spannable('Hello world'); 139 140 // Start index out of range. 141 assertException('expected range error', function() { 142 spannable.setSpan({}, -1, 0); 143 }, 'RangeError'); 144 145 // End index out of range. 146 assertException('expected range error', function() { 147 spannable.setSpan({}, 0, 12); 148 }, 'RangeError'); 149 150 // End before start. 151 assertException('expected range error', function() { 152 spannable.setSpan({}, 1, 0); 153 }, 'RangeError'); 154}); 155 156/** 157 * Tests that multiple spans can be retrieved at one point. 158 * The first one added which applies should be returned by getSpan. 159 */ 160TEST_F('CvoxSpannableUnitTest', 'MultipleSpans', function() { 161 var annotation1 = { number: 1 }; 162 var annotation2 = { number: 2 }; 163 assertNotSame(annotation1, annotation2); 164 var spannable = new cvox.Spannable('Hello world'); 165 spannable.setSpan(annotation1, 1, 4); 166 spannable.setSpan(annotation2, 2, 7); 167 assertSame(annotation1, spannable.getSpan(1)); 168 assertThat([annotation1], eqJSON(spannable.getSpans(1))); 169 assertSame(annotation1, spannable.getSpan(3)); 170 assertThat([annotation1, annotation2], eqJSON(spannable.getSpans(3))); 171 assertSame(annotation2, spannable.getSpan(6)); 172 assertThat([annotation2], eqJSON(spannable.getSpans(6))); 173}); 174 175/** Tests that appending appends the strings. */ 176TEST_F('CvoxSpannableUnitTest', 'AppendToString', function() { 177 var spannable = new cvox.Spannable('Google'); 178 assertEquals('Google', spannable.toString()); 179 spannable.append(' Chrome'); 180 assertEquals('Google Chrome', spannable.toString()); 181 spannable.append(new cvox.Spannable('Vox')); 182 assertEquals('Google ChromeVox', spannable.toString()); 183}); 184 185/** 186 * Tests that appending Spannables combines annotations. 187 */ 188TEST_F('CvoxSpannableUnitTest', 'AppendAnnotations', function() { 189 var annotation1 = { number: 1 }; 190 var annotation2 = { number: 2 }; 191 assertNotSame(annotation1, annotation2); 192 var left = new cvox.Spannable('hello'); 193 left.setSpan(annotation1, 0, 3); 194 var right = new cvox.Spannable(' world'); 195 right.setSpan(annotation2, 0, 3); 196 left.append(right); 197 assertSame(annotation1, left.getSpan(1)); 198 assertSame(annotation2, left.getSpan(6)); 199}); 200 201/** 202 * Tests that a span's bounds can be retrieved. 203 */ 204TEST_F('CvoxSpannableUnitTest', 'GetSpanStartAndEnd', function() { 205 var annotation = {}; 206 var spannable = new cvox.Spannable('potato wedges'); 207 spannable.setSpan(annotation, 8, 12); 208 assertEquals(8, spannable.getSpanStart(annotation)); 209 assertEquals(12, spannable.getSpanEnd(annotation)); 210}); 211 212/** 213 * Tests that an absent span's bounds are reported correctly. 214 */ 215TEST_F('CvoxSpannableUnitTest', 'GetSpanStartAndEndAbsent', function() { 216 var annotation = {}; 217 var spannable = new cvox.Spannable('potato wedges'); 218 assertUndefined(spannable.getSpanStart(annotation)); 219 assertUndefined(spannable.getSpanEnd(annotation)); 220}); 221 222/** 223 * Test that a zero length span can still be found. 224 */ 225TEST_F('CvoxSpannableUnitTest', 'GetSpanStartAndEndZeroLength', function() { 226 var annotation = {}; 227 var spannable = new cvox.Spannable('potato wedges'); 228 spannable.setSpan(annotation, 8, 8); 229 assertEquals(8, spannable.getSpanStart(annotation)); 230 assertEquals(8, spannable.getSpanEnd(annotation)); 231}); 232 233/** 234 * Tests that == (but not ===) objects are treated distinctly when getting 235 * span bounds. 236 */ 237TEST_F('CvoxSpannableUnitTest', 'GetSpanStartAndEndEquality', function() { 238 // Note that 0 == '' and '' == 0 in JavaScript. 239 var spannable = new cvox.Spannable('wat'); 240 spannable.setSpan(0, 0, 0); 241 spannable.setSpan('', 1, 3); 242 assertEquals(0, spannable.getSpanStart(0)); 243 assertEquals(0, spannable.getSpanEnd(0)); 244 assertEquals(1, spannable.getSpanStart('')); 245 assertEquals(3, spannable.getSpanEnd('')); 246}); 247 248/** 249 * Tests that substrings have the correct character sequence. 250 */ 251TEST_F('CvoxSpannableUnitTest', 'Substring', function() { 252 var assertSubstringResult = function(expected, initial, start, opt_end) { 253 var spannable = new cvox.Spannable(initial); 254 var substring = spannable.substring(start, opt_end); 255 assertEquals(expected, substring.toString()); 256 }; 257 assertSubstringResult('Page', 'Google PageRank', 7, 11); 258 assertSubstringResult('Goog', 'Google PageRank', 0, 4); 259 assertSubstringResult('Rank', 'Google PageRank', 11, 15); 260 assertSubstringResult('Rank', 'Google PageRank', 11); 261}); 262 263/** 264 * Tests that substring arguments are validated properly. 265 */ 266TEST_F('CvoxSpannableUnitTest', 'SubstringRangeError', function() { 267 var assertRangeError = function(initial, start, opt_end) { 268 var spannable = new cvox.Spannable(initial); 269 assertException('expected range error', function() { 270 spannable.substring(start, opt_end); 271 }, 'RangeError'); 272 }; 273 assertRangeError('Google PageRank', -1, 5); 274 assertRangeError('Google PageRank', 0, 99); 275 assertRangeError('Google PageRank', 5, 2); 276}); 277 278/** 279 * Tests that spans in the substring range are preserved. 280 */ 281TEST_F('CvoxSpannableUnitTest', 'SubstringSpansIncluded', function() { 282 var assertSpanIncluded = function(expectedSpanStart, expectedSpanEnd, 283 initial, initialSpanStart, initialSpanEnd, start, opt_end) { 284 var annotation = {}; 285 var spannable = new cvox.Spannable(initial); 286 spannable.setSpan(annotation, initialSpanStart, initialSpanEnd); 287 var substring = spannable.substring(start, opt_end); 288 assertEquals(expectedSpanStart, substring.getSpanStart(annotation)); 289 assertEquals(expectedSpanEnd, substring.getSpanEnd(annotation)); 290 }; 291 assertSpanIncluded(1, 5, 'potato wedges', 8, 12, 7); 292 assertSpanIncluded(1, 5, 'potato wedges', 8, 12, 7, 13); 293 assertSpanIncluded(1, 5, 'potato wedges', 8, 12, 7, 12); 294 assertSpanIncluded(0, 4, 'potato wedges', 8, 12, 8, 12); 295 assertSpanIncluded(0, 3, 'potato wedges', 0, 3, 0); 296 assertSpanIncluded(0, 3, 'potato wedges', 0, 3, 0, 3); 297 assertSpanIncluded(0, 3, 'potato wedges', 0, 3, 0, 6); 298 assertSpanIncluded(0, 5, 'potato wedges', 8, 13, 8); 299 assertSpanIncluded(0, 5, 'potato wedges', 8, 13, 8, 13); 300 assertSpanIncluded(1, 6, 'potato wedges', 8, 13, 7, 13); 301 302 // Note: we should keep zero-length spans, even at the edges of the range. 303 assertSpanIncluded(0, 0, 'potato wedges', 0, 0, 0, 0); 304 assertSpanIncluded(0, 0, 'potato wedges', 0, 0, 0, 6); 305 assertSpanIncluded(1, 1, 'potato wedges', 8, 8, 7, 13); 306 assertSpanIncluded(6, 6, 'potato wedges', 6, 6, 0, 6); 307}); 308 309/** 310 * Tests that spans outside the range are omitted. 311 * It's fine to keep zero-length spans at the ends, though. 312 */ 313TEST_F('CvoxSpannableUnitTest', 'SubstringSpansExcluded', function() { 314 var assertSpanExcluded = function(initial, spanStart, spanEnd, 315 start, opt_end) { 316 var annotation = {}; 317 var spannable = new cvox.Spannable(initial); 318 spannable.setSpan(annotation, spanStart, spanEnd); 319 var substring = spannable.substring(start, opt_end); 320 assertUndefined(substring.getSpanStart(annotation)); 321 assertUndefined(substring.getSpanEnd(annotation)); 322 }; 323 assertSpanExcluded('potato wedges', 8, 12, 0, 6); 324 assertSpanExcluded('potato wedges', 7, 12, 0, 6); 325 assertSpanExcluded('potato wedges', 0, 6, 8); 326 assertSpanExcluded('potato wedges', 6, 6, 8); 327}); 328 329/** 330 * Tests that spans which cross the boundary are clipped. 331 */ 332TEST_F('CvoxSpannableUnitTest', 'SubstringSpansClipped', function() { 333 var assertSpanIncluded = function(expectedSpanStart, expectedSpanEnd, 334 initial, initialSpanStart, initialSpanEnd, start, opt_end) { 335 var annotation = {}; 336 var spannable = new cvox.Spannable(initial); 337 spannable.setSpan(annotation, initialSpanStart, initialSpanEnd); 338 var substring = spannable.substring(start, opt_end); 339 assertEquals(expectedSpanStart, substring.getSpanStart(annotation)); 340 assertEquals(expectedSpanEnd, substring.getSpanEnd(annotation)); 341 }; 342 assertSpanIncluded(0, 4, 'potato wedges', 7, 13, 8, 12); 343 assertSpanIncluded(0, 0, 'potato wedges', 0, 6, 0, 0); 344 assertSpanIncluded(0, 0, 'potato wedges', 0, 6, 6, 6); 345 346 // The first of the above should produce "edge". 347 assertEquals('edge', 348 new cvox.Spannable('potato wedges').substring(8, 12).toString()); 349}); 350 351/** 352 * Tests that whitespace is trimmed. 353 */ 354TEST_F('CvoxSpannableUnitTest', 'Trim', function() { 355 var assertTrimResult = function(expected, initial) { 356 assertEquals(expected, new cvox.Spannable(initial).trim().toString()); 357 }; 358 assertTrimResult('John F. Kennedy', 'John F. Kennedy'); 359 assertTrimResult('John F. Kennedy', ' John F. Kennedy'); 360 assertTrimResult('John F. Kennedy', 'John F. Kennedy '); 361 assertTrimResult('John F. Kennedy', ' \r\t \nJohn F. Kennedy\n\n \n'); 362 assertTrimResult('', ''); 363 assertTrimResult('', ' \t\t \n\r'); 364}); 365 366/** 367 * Tests that trim keeps, drops and clips spans. 368 */ 369TEST_F('CvoxSpannableUnitTest', 'TrimSpans', function() { 370 var spannable = new cvox.Spannable(' \t Kennedy\n'); 371 spannable.setSpan('tab', 1, 2); 372 spannable.setSpan('jfk', 3, 10); 373 spannable.setSpan('jfk-newline', 3, 11); 374 var trimmed = spannable.trim(); 375 assertUndefined(trimmed.getSpanStart('tab')); 376 assertUndefined(trimmed.getSpanEnd('tab')); 377 assertEquals(0, trimmed.getSpanStart('jfk')); 378 assertEquals(7, trimmed.getSpanEnd('jfk')); 379 assertEquals(0, trimmed.getSpanStart('jfk-newline')); 380 assertEquals(7, trimmed.getSpanEnd('jfk-newline')); 381}); 382 383/** 384 * Tests that when a string is all whitespace, we trim off the *end*. 385 */ 386TEST_F('CvoxSpannableUnitTest', 'TrimAllWhitespace', function() { 387 var spannable = new cvox.Spannable(' '); 388 spannable.setSpan('cursor 1', 0, 0); 389 spannable.setSpan('cursor 2', 2, 2); 390 var trimmed = spannable.trim(); 391 assertEquals(0, trimmed.getSpanStart('cursor 1')); 392 assertEquals(0, trimmed.getSpanEnd('cursor 1')); 393 assertUndefined(trimmed.getSpanStart('cursor 2')); 394 assertUndefined(trimmed.getSpanEnd('cursor 2')); 395}); 396 397/** 398 * Tests finding a span which is an instance of a given class. 399 */ 400TEST_F('CvoxSpannableUnitTest', 'GetSpanInstanceOf', function() { 401 function ExampleConstructorBase() {} 402 function ExampleConstructor1() {} 403 function ExampleConstructor2() {} 404 function ExampleConstructor3() {} 405 ExampleConstructor1.prototype = new ExampleConstructorBase(); 406 ExampleConstructor2.prototype = new ExampleConstructorBase(); 407 ExampleConstructor3.prototype = new ExampleConstructorBase(); 408 var ex1 = new ExampleConstructor1(); 409 var ex2 = new ExampleConstructor2(); 410 var spannable = new cvox.Spannable('Hello world'); 411 spannable.setSpan(ex1, 0, 0); 412 spannable.setSpan(ex2, 1, 1); 413 assertEquals(ex1, spannable.getSpanInstanceOf(ExampleConstructor1)); 414 assertEquals(ex2, spannable.getSpanInstanceOf(ExampleConstructor2)); 415 assertUndefined(spannable.getSpanInstanceOf(ExampleConstructor3)); 416 assertEquals(ex1, spannable.getSpanInstanceOf(ExampleConstructorBase)); 417}); 418 419/** Tests trimming only left or right. */ 420TEST_F('CvoxSpannableUnitTest', 'TrimLeftOrRight', function() { 421 var spannable = new cvox.Spannable(' '); 422 spannable.setSpan('cursor 1', 0, 0); 423 spannable.setSpan('cursor 2', 2, 2); 424 var trimmed = spannable.trimLeft(); 425 assertEquals(0, trimmed.getSpanStart('cursor 1')); 426 assertEquals(0, trimmed.getSpanEnd('cursor 1')); 427 assertUndefined(trimmed.getSpanStart('cursor 2')); 428 assertUndefined(trimmed.getSpanEnd('cursor 2')); 429 430 var spannable2 = new cvox.Spannable('0 '); 431 spannable2.setSpan('cursor 1', 0, 0); 432 spannable2.setSpan('cursor 2', 2, 2); 433 var trimmed2 = spannable2.trimLeft(); 434 assertEquals(0, trimmed2.getSpanStart('cursor 1')); 435 assertEquals(0, trimmed2.getSpanEnd('cursor 1')); 436 assertEquals(2, trimmed2.getSpanStart('cursor 2')); 437 assertEquals(2, trimmed2.getSpanEnd('cursor 2')); 438 trimmed2 = trimmed2.trimRight(); 439 assertEquals(0, trimmed2.getSpanStart('cursor 1')); 440 assertEquals(0, trimmed2.getSpanEnd('cursor 1')); 441 assertUndefined(trimmed2.getSpanStart('cursor 2')); 442 assertUndefined(trimmed2.getSpanEnd('cursor 2')); 443 444 var spannable3 = new cvox.Spannable(' 0'); 445 spannable3.setSpan('cursor 1', 0, 0); 446 spannable3.setSpan('cursor 2', 2, 2); 447 var trimmed3 = spannable3.trimRight(); 448 assertEquals(0, trimmed3.getSpanStart('cursor 1')); 449 assertEquals(0, trimmed3.getSpanEnd('cursor 1')); 450 assertEquals(2, trimmed3.getSpanStart('cursor 2')); 451 assertEquals(2, trimmed3.getSpanEnd('cursor 2')); 452 trimmed3 = trimmed3.trimLeft(); 453 assertUndefined(trimmed3.getSpanStart('cursor 1')); 454 assertUndefined(trimmed3.getSpanEnd('cursor 1')); 455 assertEquals(0, trimmed3.getSpanStart('cursor 2')); 456 assertEquals(0, trimmed3.getSpanEnd('cursor 2')); 457}); 458 459TEST_F('CvoxSpannableUnitTest', 'Serialize', function() { 460 var fresh = new cvox.Spannable('text'); 461 var freshStatelessSerializable = new StatelessSerializableSpan(); 462 var freshNonStatelessSerializable = new NonStatelessSerializableSpan(14); 463 fresh.setSpan(new UnserializableSpan(), 0, 1); 464 fresh.setSpan(freshStatelessSerializable, 0, 2); 465 fresh.setSpan(freshNonStatelessSerializable, 3, 4); 466 var thawn = cvox.Spannable.fromJson(fresh.toJson()); 467 var thawnStatelessSerializable = thawn.getSpanInstanceOf( 468 StatelessSerializableSpan); 469 var thawnNonStatelessSerializable = thawn.getSpanInstanceOf( 470 NonStatelessSerializableSpan); 471 assertThat('text', eqJSON(thawn.toString())); 472 assertUndefined(thawn.getSpanInstanceOf(UnserializableSpan)); 473 assertThat( 474 fresh.getSpanStart(freshStatelessSerializable), 475 eqJSON(thawn.getSpanStart(thawnStatelessSerializable))); 476 assertThat( 477 fresh.getSpanEnd(freshStatelessSerializable), 478 eqJSON(thawn.getSpanEnd(thawnStatelessSerializable))); 479 assertThat(freshNonStatelessSerializable, 480 eqJSON(thawnNonStatelessSerializable)); 481}); 482