1// Copyright 2019 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 5const {assert} = chai; 6 7import {TextRange} from '../../../../front_end/text_utils/TextRange.js'; 8 9interface ExpectedTextRange { 10 startLine: number; 11 startColumn: number; 12 endLine: number; 13 endColumn: number; 14} 15 16function assertIsTextRangeAndEqualsRange(range: TextRange, expectedRange: ExpectedTextRange, description: string) { 17 const prefix = description.length ? `${description}, but ` : ''; 18 assert.isTrue(range instanceof TextRange, `${prefix}range is not a TextRange`); 19 assert.strictEqual(range.startLine, expectedRange.startLine, `${prefix}range's startLine differs from expectation`); 20 assert.strictEqual( 21 range.startColumn, expectedRange.startColumn, `${prefix}range's startColumn differs from expectation`); 22 assert.strictEqual(range.endLine, expectedRange.endLine, `${prefix}range's endLine differs from expectation`); 23 assert.strictEqual(range.endColumn, expectedRange.endColumn, `${prefix}range's endColumn differs from expectation`); 24} 25 26function assertIsUnitTextRange(range: TextRange, line: number, column: number, description: string) { 27 const prefix = description.length ? `${description}, but ` : ''; 28 assert.isTrue(range instanceof TextRange, `${prefix}range is not a TextRange`); 29 assert.strictEqual(range.startLine, range.endLine, `${prefix}the range is not a unit range: start/end lines differ`); 30 assert.strictEqual( 31 range.startColumn, range.endColumn, `${prefix}the range is not a unit range: start/end columns differ`); 32 assert.strictEqual(range.startLine, line, `${prefix}the line was not set correctly`); 33 assert.strictEqual(range.startColumn, column, `${prefix}the column was not set correctly`); 34} 35 36describe('TextRange', () => { 37 it('can be instantiated successfully', () => { 38 const startLine = 1; 39 const startColumn = 2; 40 const endLine = 3; 41 const endColumn = 4; 42 const textRange = new TextRange(startLine, startColumn, endLine, endColumn); 43 assert.strictEqual(textRange.startLine, startLine, 'the start line was not set or retrieved correctly'); 44 assert.strictEqual(textRange.startColumn, startColumn, 'the start column was not set or retrieved correctly'); 45 assert.strictEqual(textRange.endLine, endLine, 'the end line was not set or retrieved correctly'); 46 assert.strictEqual(textRange.endColumn, endColumn, 'the end column was not set or retrieved correctly'); 47 }); 48 49 it('can be created from a location', () => { 50 const line = 1; 51 const column = 2; 52 const textRange = TextRange.createFromLocation(line, column); 53 assertIsUnitTextRange(textRange, line, column, 'range created from a location should be a unit range'); 54 }); 55 56 it('can be created from a serialized text range', () => { 57 const range = {startLine: 1, startColumn: 2, endLine: 3, endColumn: 4}; 58 const textRange = TextRange.fromObject(range); 59 assertIsTextRangeAndEqualsRange(textRange, range, 'deserializing should preserve the range'); 60 const serializedRange = textRange.serializeToObject(); 61 const deserializedTextRange = TextRange.fromObject(serializedRange); 62 assertIsTextRangeAndEqualsRange(deserializedTextRange, range, 'deserializing should preserve the range'); 63 }); 64 65 it('can be checked for emptiness', () => { 66 const textRange = TextRange.fromObject({startLine: 1, startColumn: 2, endLine: 1, endColumn: 2}); 67 assert.isTrue(textRange.isEmpty(), 'the range was non-empty'); 68 }); 69 70 describe('immediatelyPrecedes()', () => { 71 it('can handle non-range inputs', () => { 72 const textRange = TextRange.fromObject({startLine: 1, startColumn: 2, endLine: 3, endColumn: 4}); 73 assert.isFalse(textRange.immediatelyPrecedes(), 'invalid ranges should not be judged as immediatelly preceeding'); 74 }); 75 76 it('can judge immediate preceedence correctly', () => { 77 const textRangeA = TextRange.fromObject({startLine: 1, startColumn: 2, endLine: 3, endColumn: 4}); 78 const textRangeB = TextRange.fromObject({startLine: 3, startColumn: 4, endLine: 5, endColumn: 6}); 79 const textRangeC = TextRange.fromObject({startLine: 5, startColumn: 6, endLine: 7, endColumn: 8}); 80 assert.isTrue(textRangeA.immediatelyPrecedes(textRangeB), 'range A should immediatelly preceed range B'); 81 assert.isTrue(textRangeB.immediatelyPrecedes(textRangeC), 'range B should immediatelly preceed range C'); 82 assert.isFalse(textRangeB.immediatelyPrecedes(textRangeA), 'range B should not immediatelly preceed range A'); 83 assert.isFalse(textRangeA.immediatelyPrecedes(textRangeC), 'range A should not immediatelly preceed range C'); 84 }); 85 }); 86 87 describe('immediatelyFollows()', () => { 88 it('can handle non-range inputs', () => { 89 const textRange = TextRange.fromObject({startLine: 1, startColumn: 2, endLine: 3, endColumn: 4}); 90 assert.isFalse(textRange.immediatelyFollows(), 'invalid ranges should not be judged as \'immediatelly follows\''); 91 }); 92 93 it('can judge \'immediatelly follows\' relationship correctly', () => { 94 const textRangeA = TextRange.fromObject({startLine: 1, startColumn: 2, endLine: 3, endColumn: 4}); 95 const textRangeB = TextRange.fromObject({startLine: 3, startColumn: 4, endLine: 5, endColumn: 6}); 96 const textRangeC = TextRange.fromObject({startLine: 5, startColumn: 6, endLine: 7, endColumn: 8}); 97 assert.isTrue(textRangeB.immediatelyFollows(textRangeA), 'range B should immediatelly follow range A'); 98 assert.isTrue(textRangeC.immediatelyFollows(textRangeB), 'range C should immediatelly follow range B'); 99 assert.isFalse(textRangeA.immediatelyFollows(textRangeB), 'range A should not immediatelly follow range B'); 100 assert.isFalse(textRangeC.immediatelyFollows(textRangeA), 'range C should not immediatelly follow range A'); 101 }); 102 }); 103 104 describe('follows()', () => { 105 it('can judge \'follows\' relationship correctly', () => { 106 const textRangeA = TextRange.fromObject({startLine: 1, startColumn: 2, endLine: 3, endColumn: 4}); 107 const textRangeB = TextRange.fromObject({startLine: 3, startColumn: 4, endLine: 5, endColumn: 6}); 108 const textRangeC = TextRange.fromObject({startLine: 5, startColumn: 6, endLine: 7, endColumn: 8}); 109 assert.isTrue(textRangeB.follows(textRangeA), 'range B should follow range A'); 110 assert.isTrue(textRangeC.follows(textRangeB), 'range C should follow range B'); 111 assert.isFalse(textRangeA.follows(textRangeB), 'range A should not follow range B'); 112 assert.isTrue(textRangeC.follows(textRangeA), 'range C should follow range A'); 113 }); 114 }); 115 116 it('can report the line count', () => { 117 const textRangeA = TextRange.fromObject({startLine: 1, startColumn: 2, endLine: 1, endColumn: 2}); 118 const textRangeB = TextRange.fromObject({startLine: 1, startColumn: 2, endLine: 2, endColumn: 2}); 119 const textRangeC = TextRange.fromObject({startLine: 1, startColumn: 2, endLine: 12, endColumn: 2}); 120 assert.strictEqual(textRangeA.linesCount, 0, 'line count was wrong'); 121 assert.strictEqual(textRangeB.linesCount, 1, 'line count was wrong'); 122 assert.strictEqual(textRangeC.linesCount, 11, 'line count was wrong'); 123 }); 124 125 it('can be collapsed to start', () => { 126 const rangeA = {startLine: 1, startColumn: 2, endLine: 1, endColumn: 2}; 127 const textRangeA = TextRange.fromObject(rangeA); 128 const rangeB = {startLine: 4, startColumn: 2, endLine: 2, endColumn: 2}; 129 const textRangeB = TextRange.fromObject(rangeB); 130 const textRangeACollapsed = textRangeA.collapseToStart(); 131 assertIsUnitTextRange( 132 textRangeACollapsed, rangeA.startLine, rangeA.startColumn, 133 'collapsing to start should produce a unit range at start'); 134 const textRangeBCollapsed = textRangeB.collapseToStart(); 135 assertIsUnitTextRange( 136 textRangeBCollapsed, rangeB.startLine, rangeB.startColumn, 137 'collapsing to start should produce a unit range at start'); 138 assertIsTextRangeAndEqualsRange(textRangeA, rangeA, 'original TextRange should be unchanged'); 139 assertIsTextRangeAndEqualsRange(textRangeB, rangeB, 'original TextRange should be unchanged'); 140 }); 141 142 it('can be collapsed to end', () => { 143 const rangeA = {startLine: 1, startColumn: 2, endLine: 1, endColumn: 2}; 144 const textRangeA = TextRange.fromObject(rangeA); 145 const rangeB = {startLine: 4, startColumn: 2, endLine: 2, endColumn: 2}; 146 const textRangeB = TextRange.fromObject(rangeB); 147 const textRangeACollapsed = textRangeA.collapseToEnd(); 148 assertIsUnitTextRange( 149 textRangeACollapsed, rangeA.endLine, rangeA.endColumn, 'collapsing to end should produce a unit range at end'); 150 const textRangeBCollapsed = textRangeB.collapseToEnd(); 151 assertIsUnitTextRange( 152 textRangeBCollapsed, rangeB.endLine, rangeB.endColumn, 'collapsing to end should produce a unit range at end'); 153 assertIsTextRangeAndEqualsRange(textRangeA, rangeA, 'original TextRange should be unchanged'); 154 assertIsTextRangeAndEqualsRange(textRangeB, rangeB, 'original TextRange should be unchanged'); 155 }); 156 157 it('can be normalized', () => { 158 const rangeA = {startLine: 1, startColumn: 2, endLine: 3, endColumn: 4}; 159 const textRangeA = TextRange.fromObject(rangeA); 160 const rangeB = {startLine: 3, startColumn: 4, endLine: 1, endColumn: 2}; 161 const textRangeB = TextRange.fromObject(rangeB); 162 const textRangeANormalized = textRangeA.normalize(); 163 const textRangeBNormalized = textRangeB.normalize(); 164 assertIsTextRangeAndEqualsRange(textRangeANormalized, rangeA, 'normalizing should keep range A unchanged'); 165 assert.notStrictEqual(textRangeANormalized, textRangeA, 'range should have been cloned'); 166 assertIsTextRangeAndEqualsRange(textRangeBNormalized, rangeA, 'range B should be normalized'); 167 assertIsTextRangeAndEqualsRange(textRangeA, rangeA, 'range A should be unchanged'); 168 assertIsTextRangeAndEqualsRange(textRangeB, rangeB, 'range B should be unchanged'); 169 }); 170 171 it('can be cloned', () => { 172 const rangeA = {startLine: 1, startColumn: 2, endLine: 3, endColumn: 4}; 173 const textRangeA = TextRange.fromObject(rangeA); 174 const textRangeB = textRangeA.clone(); 175 assertIsTextRangeAndEqualsRange(textRangeB, rangeA, 'cloned range should be equal'); 176 assert.notStrictEqual(textRangeB, textRangeA, 'cloned range should be different object'); 177 assertIsTextRangeAndEqualsRange(textRangeA, rangeA, 'original range should be unchanged'); 178 }); 179 180 it('can be checked for equality', () => { 181 const rangeA = {startLine: 1, startColumn: 2, endLine: 3, endColumn: 4}; 182 const textRangeA = TextRange.fromObject(rangeA); 183 const textRangeB = TextRange.fromObject(rangeA); 184 assert.isTrue(textRangeA.equal(textRangeA), 'range A is equal to itself'); 185 assert.isTrue(textRangeA.equal(textRangeB), 'range A and B are equal'); 186 }); 187 188 it('can be compared', () => { 189 const textRangeA = TextRange.fromObject({startLine: 1, startColumn: 2, endLine: 3, endColumn: 4}); 190 const textRangeB = TextRange.fromObject({startLine: 1, startColumn: 4, endLine: 3, endColumn: 4}); 191 const textRangeC = TextRange.fromObject({startLine: 2, startColumn: 2, endLine: 3, endColumn: 4}); 192 const textRangeD = TextRange.fromObject({startLine: 3, startColumn: 1, endLine: 3, endColumn: 4}); 193 194 assert.strictEqual(textRangeA.compareTo(textRangeA), 0, 'A should be equal to itself'); 195 assert.strictEqual(textRangeA.compareTo(textRangeB), -1, 'A should be before B'); 196 assert.strictEqual(textRangeB.compareTo(textRangeA), 1, 'B should be after A'); 197 assert.strictEqual(textRangeA.compareTo(textRangeC), -1, 'A should be before C'); 198 assert.strictEqual(textRangeC.compareTo(textRangeA), 1, 'C should be after A'); 199 assert.strictEqual(textRangeC.compareTo(textRangeD), -1, 'C should be before D'); 200 assert.strictEqual(textRangeD.compareTo(textRangeC), 1, 'D should be after C'); 201 }); 202 203 it('can be compared with TextRange.comparator', () => { 204 const textRangeA = TextRange.fromObject({startLine: 1, startColumn: 2, endLine: 3, endColumn: 4}); 205 const textRangeB = TextRange.fromObject({startLine: 1, startColumn: 4, endLine: 3, endColumn: 4}); 206 const textRangeC = TextRange.fromObject({startLine: 2, startColumn: 2, endLine: 3, endColumn: 4}); 207 const textRangeD = TextRange.fromObject({startLine: 3, startColumn: 1, endLine: 3, endColumn: 4}); 208 209 assert.strictEqual(TextRange.comparator(textRangeA, textRangeA), 0, 'A should be equal to itself'); 210 assert.strictEqual(TextRange.comparator(textRangeA, textRangeB), -1, 'A should be before B'); 211 assert.strictEqual(TextRange.comparator(textRangeB, textRangeA), 1, 'B should be after A'); 212 assert.strictEqual(TextRange.comparator(textRangeA, textRangeC), -1, 'A should be before C'); 213 assert.strictEqual(TextRange.comparator(textRangeC, textRangeA), 1, 'C should be after A'); 214 assert.strictEqual(TextRange.comparator(textRangeC, textRangeD), -1, 'C should be before D'); 215 assert.strictEqual(TextRange.comparator(textRangeD, textRangeC), 1, 'D should be after C'); 216 }); 217 218 it('can be compared to a position', () => { 219 const textRangeA = TextRange.fromObject({startLine: 1, startColumn: 2, endLine: 3, endColumn: 4}); 220 assert.strictEqual(textRangeA.compareToPosition(0, 3), -1, 'position before range should compare less'); 221 assert.strictEqual(textRangeA.compareToPosition(1, 1), -1, 'position before range should compare less'); 222 assert.strictEqual(textRangeA.compareToPosition(1, 2), 0, 'start position should compare equal'); 223 assert.strictEqual(textRangeA.compareToPosition(1, 4), 0, 'position in range should compare equal'); 224 assert.strictEqual(textRangeA.compareToPosition(3, 4), 0, 'end position should compare equal'); 225 assert.strictEqual(textRangeA.compareToPosition(3, 5), 1, 'position after range should compare greater'); 226 assert.strictEqual(textRangeA.compareToPosition(4, 4), 1, 'position after range should compare greater'); 227 }); 228 229 it('can be adjusted relative to a position', () => { 230 const textRange = TextRange.fromObject({startLine: 4, startColumn: 3, endLine: 6, endColumn: 7}); 231 const relativeTextRangeA = textRange.relativeTo(2, 2); 232 const expectedRangeA = {startLine: 2, startColumn: 3, endLine: 4, endColumn: 7}; 233 assertIsTextRangeAndEqualsRange( 234 relativeTextRangeA, expectedRangeA, 235 'relativating to position strictly inside line range should not change columns'); 236 const relativeTextRangeB = textRange.relativeTo(4, 2); 237 const expectedRangeB = {startLine: 0, startColumn: 1, endLine: 2, endColumn: 7}; 238 assertIsTextRangeAndEqualsRange( 239 relativeTextRangeB, expectedRangeB, 'relativating to position on start line should change start column'); 240 const relativeTextRangeC = textRange.relativeTo(6, 3); 241 const expectedRangeC = {startLine: -2, startColumn: 3, endLine: 0, endColumn: 4}; 242 assertIsTextRangeAndEqualsRange( 243 relativeTextRangeC, expectedRangeC, 'relativating to position on end line should change end column'); 244 const relativeTextRangeD = textRange.relativeTo(0, 0); 245 assert.notStrictEqual(relativeTextRangeD, textRange, 'relativeTo should clone range'); 246 }); 247 248 it('can be adjusted relative from a position', () => { 249 const textRange = TextRange.fromObject({startLine: 4, startColumn: 3, endLine: 6, endColumn: 7}); 250 const relativeTextRangeA = textRange.relativeFrom(2, 2); 251 const expectedRangeA = {startLine: 6, startColumn: 3, endLine: 8, endColumn: 7}; 252 assertIsTextRangeAndEqualsRange( 253 relativeTextRangeA, expectedRangeA, 254 'relativating from position strictly inside line range should not change columns'); 255 const relativeTextRangeB = textRange.relativeFrom(4, 2); 256 const expectedRangeB = {startLine: 8, startColumn: 3, endLine: 10, endColumn: 7}; 257 assertIsTextRangeAndEqualsRange( 258 relativeTextRangeB, expectedRangeB, 'relativating from position on start line should not change columns'); 259 const relativeTextRangeC = textRange.relativeFrom(6, 3); 260 const expectedRangeC = {startLine: 10, startColumn: 3, endLine: 12, endColumn: 7}; 261 assertIsTextRangeAndEqualsRange( 262 relativeTextRangeC, expectedRangeC, 'relativating from position on end line should not change columns'); 263 const relativeTextRangeD = textRange.relativeFrom(0, 0); 264 assert.notStrictEqual(relativeTextRangeD, textRange, 'relativeFrom should clone range'); 265 266 const textRange2 = TextRange.fromObject({startLine: 0, startColumn: 3, endLine: 6, endColumn: 7}); 267 const relativeTextRangeE = textRange2.relativeFrom(2, 2); 268 const expectedRangeE = {startLine: 2, startColumn: 5, endLine: 8, endColumn: 7}; 269 assertIsTextRangeAndEqualsRange( 270 relativeTextRangeE, expectedRangeE, 'relativating range with startLine 0 should change start column'); 271 272 const textRange3 = TextRange.fromObject({startLine: 1, startColumn: 3, endLine: 0, endColumn: 7}); 273 const relativeTextRangeF = textRange3.relativeFrom(2, 2); 274 const expectedRangeF = {startLine: 3, startColumn: 3, endLine: 2, endColumn: 9}; 275 assertIsTextRangeAndEqualsRange( 276 relativeTextRangeF, expectedRangeF, 'relativating range with endLine 0 should change end column'); 277 }); 278 279 it('can check if a position is contained', () => { 280 const textRangeA = TextRange.fromObject({startLine: 1, startColumn: 2, endLine: 3, endColumn: 4}); 281 assert.isFalse(textRangeA.containsLocation(0, 3), 'position before range should not be contained'); 282 assert.isFalse(textRangeA.containsLocation(1, 1), 'position before range should not be contained'); 283 assert.isTrue(textRangeA.containsLocation(1, 2), 'start position should be contained'); 284 assert.isTrue(textRangeA.containsLocation(1, 4), 'position in range should be contained'); 285 assert.isTrue(textRangeA.containsLocation(3, 4), 'end position should be contained'); 286 assert.isFalse(textRangeA.containsLocation(3, 5), 'position after range should compare greater'); 287 assert.isFalse(textRangeA.containsLocation(4, 4), 'position after range should compare greater'); 288 289 const textRangeB = TextRange.fromObject({startLine: 1, startColumn: 2, endLine: 1, endColumn: 4}); 290 assert.isFalse(textRangeB.containsLocation(1, 1), 'position before range should not be contained'); 291 assert.isTrue(textRangeB.containsLocation(1, 2), 'start position should be contained'); 292 assert.isTrue(textRangeB.containsLocation(1, 4), 'position in range should be contained'); 293 assert.isFalse(textRangeB.containsLocation(1, 5), 'end position should be contained'); 294 }); 295 296 describe('fromEdit()', () => { 297 it('can construct a range from an edit of a text ending with a newline', () => { 298 const textRange = TextRange.fromObject({startLine: 1, startColumn: 2, endLine: 3, endColumn: 4}); 299 const text = 'This is\nan example text\nwith newlines\nin it. It is for\n the test.\n'; 300 const textRangeEdited = TextRange.fromEdit(textRange, text); 301 const expectedRange = {startLine: 1, startColumn: 2, endLine: 6, endColumn: 0}; 302 assertIsTextRangeAndEqualsRange(textRangeEdited, expectedRange, 'range end should have been shifted back'); 303 }); 304 305 it('can construct a range from an edit of a text ending without a newline', () => { 306 const textRange = TextRange.fromObject({startLine: 1, startColumn: 2, endLine: 3, endColumn: 4}); 307 const text = 'This is\nan example text\nwith newlines\nin it. It is for\n the test.'; 308 const textRangeEdited = TextRange.fromEdit(textRange, text); 309 const expectedRange = {startLine: 1, startColumn: 2, endLine: 5, endColumn: 10}; 310 assertIsTextRangeAndEqualsRange(textRangeEdited, expectedRange, 'range end should have been shifted back'); 311 }); 312 313 it('can construct a range from an edit of a text without newlines', () => { 314 const textRange = TextRange.fromObject({startLine: 1, startColumn: 2, endLine: 3, endColumn: 4}); 315 const text = 'This is an example text without newlines in it. It is for the test.'; 316 const textRangeEdited = TextRange.fromEdit(textRange, text); 317 const expectedRange = {startLine: 1, startColumn: 2, endLine: 1, endColumn: 69}; 318 assertIsTextRangeAndEqualsRange(textRangeEdited, expectedRange, 'range end should have been shifted forward'); 319 }); 320 }); 321 322 describe('rebaseAfterTextEdit()', () => { 323 let originalRange: TextRange; 324 let editedRange: TextRange; 325 326 beforeEach(() => { 327 originalRange = TextRange.fromObject({startLine: 1, startColumn: 2, endLine: 3, endColumn: 4}); 328 editedRange = TextRange.fromObject({startLine: 1, startColumn: 2, endLine: 7, endColumn: 8}); 329 }); 330 331 it('can rebase a range that doesn\'t follow the original range', () => { 332 const range = {startLine: 2, startColumn: 4, endLine: 7, endColumn: 8}; 333 const textRange = TextRange.fromObject(range); 334 const rebasedTextrange = textRange.rebaseAfterTextEdit(originalRange, editedRange); 335 assertIsTextRangeAndEqualsRange(rebasedTextrange, range, 'range should not have been modified'); 336 }); 337 338 it('can rebase a range if its rebased range neither starts nor ends at end of the edited range', () => { 339 const textRange = TextRange.fromObject({startLine: 4, startColumn: 4, endLine: 6, endColumn: 8}); 340 const rebasedTextRange = textRange.rebaseAfterTextEdit(originalRange, editedRange); 341 const expectedRange = {startLine: 8, startColumn: 4, endLine: 10, endColumn: 8}; 342 assertIsTextRangeAndEqualsRange(rebasedTextRange, expectedRange, 'range’s lines should have been shifted back'); 343 }); 344 345 it('can rebase a range if its rebased range starts at the end of the edited range', () => { 346 const textRangeToRebase = TextRange.fromObject({startLine: 3, startColumn: 5, endLine: 6, endColumn: 8}); 347 const rebasedTextRange = textRangeToRebase.rebaseAfterTextEdit(originalRange, editedRange); 348 const expectedRange = {startLine: 7, startColumn: 9, endLine: 10, endColumn: 8}; 349 assertIsTextRangeAndEqualsRange( 350 rebasedTextRange, expectedRange, 'range’s lines and start column should have been shifted back'); 351 }); 352 353 it('can rebase a range if its rebased range starts and ends at the end of the edited range', () => { 354 const textRangeToRebase = TextRange.fromObject({startLine: 3, startColumn: 5, endLine: 3, endColumn: 8}); 355 const rebasedTextRange = textRangeToRebase.rebaseAfterTextEdit(originalRange, editedRange); 356 const expectedRange = {startLine: 7, startColumn: 9, endLine: 7, endColumn: 12}; 357 assertIsTextRangeAndEqualsRange( 358 rebasedTextRange, expectedRange, 'range’s lines and columns should have been shifted back'); 359 }); 360 }); 361 362 it('can be stringified', () => { 363 const textRange = TextRange.fromObject({startLine: 1, startColumn: 2, endLine: 3, endColumn: 4}); 364 assert.isTrue(typeof textRange.toString() === 'string', 'toString should return a string'); 365 }); 366}); 367