| import * as generationUtils from '../src/fragment-generation-utils.js'; |
| import * as fragmentUtils from '../src/text-fragment-utils.js'; |
| |
| describe('FragmentGenerationUtils', function() { |
| beforeEach(function() { |
| generationUtils.setTimeout(500); |
| generationUtils.forTesting.recordStartTime(Date.now()); |
| }); |
| |
| it('can generate a fragment for an exact match', function() { |
| document.body.innerHTML = __html__['basic_test.html']; |
| const range = document.createRange(); |
| // firstChild of body is a <p>; firstChild of <p> is a text node. |
| range.selectNodeContents(document.body.firstChild.firstChild); |
| |
| const selection = window.getSelection(); |
| selection.removeAllRanges(); |
| selection.addRange(range); |
| |
| const result = generationUtils.generateFragment(selection); |
| expect(result.status) |
| .toEqual(generationUtils.GenerateFragmentStatus.SUCCESS); |
| expect(result.fragment.textStart).not.toBeUndefined(); |
| expect(result.fragment.textStart) |
| .toEqual('this is a trivial test of the marking logic.'); |
| expect(result.fragment.textEnd).toBeUndefined(); |
| expect(result.fragment.prefix).toBeUndefined(); |
| expect(result.fragment.suffix).toBeUndefined(); |
| }); |
| |
| it('can generate a fragment for a match across block boundaries', function() { |
| document.body.innerHTML = __html__['marks_test.html']; |
| const range = document.createRange(); |
| |
| range.setStart(document.getElementById('c'), 0); |
| range.setEnd(document.getElementById('f'), 1); |
| |
| expect(fragmentUtils.forTesting.normalizeString(range.toString())) |
| .toEqual('elaborate fancy div with lots of different stuff'); |
| |
| const selection = window.getSelection(); |
| selection.removeAllRanges(); |
| selection.addRange(range); |
| |
| let result = generationUtils.generateFragment(selection); |
| expect(result.status) |
| .toEqual(generationUtils.GenerateFragmentStatus.SUCCESS); |
| expect(result.fragment.textStart).toEqual('elaborate'); |
| expect(result.fragment.textEnd).toEqual('stuff'); |
| expect(result.fragment.prefix).toBeUndefined(); |
| expect(result.fragment.suffix).toBeUndefined(); |
| |
| range.selectNodeContents(document.getElementById('a')); |
| |
| expect(fragmentUtils.forTesting.normalizeString(range.toString().trim())) |
| .toEqual( |
| 'this is a really elaborate fancy div with lots of different stuff in it.'); |
| |
| selection.removeAllRanges(); |
| selection.addRange(range); |
| result = generationUtils.generateFragment(selection); |
| expect(result.status) |
| .toEqual(generationUtils.GenerateFragmentStatus.SUCCESS); |
| expect(result.fragment.textStart).toEqual('This'); |
| expect(result.fragment.textEnd).toEqual('it'); |
| expect(result.fragment.prefix).toBeUndefined(); |
| expect(result.fragment.suffix).toBeUndefined(); |
| }); |
| |
| it('can generate a fragment for a really long range in a text node.', |
| function() { |
| document.body.innerHTML = __html__['very-long-text.html']; |
| const range = document.createRange(); |
| range.selectNodeContents(document.getElementById('root')); |
| |
| const selection = window.getSelection(); |
| selection.removeAllRanges(); |
| selection.addRange(range); |
| |
| const result = generationUtils.generateFragment(selection); |
| expect(result.fragment.textStart).toEqual('words words words'); |
| expect(result.fragment.textEnd).toEqual('words words words'); |
| }); |
| |
| it('can detect if a range contains a block boundary', function() { |
| document.body.innerHTML = __html__['marks_test.html']; |
| const range = document.createRange(); |
| const root = document.getElementById('a'); |
| |
| // Starts/ends inside text nodes that are children of the same block element |
| // and have block elements in between them. |
| range.setStart(root.firstChild, 3); |
| range.setEnd(root.lastChild, 5); |
| expect(generationUtils.forTesting.containsBlockBoundary(range)) |
| .toEqual(true); |
| |
| // Starts/ends inside a single text node. |
| range.setStart(root.firstChild, 3); |
| range.setEnd(root.firstChild, 7); |
| expect(generationUtils.forTesting.containsBlockBoundary(range)) |
| .toEqual(false); |
| |
| // Contains other nodes, but none of them are block nodes. |
| range.setStart(root.childNodes[4], 3); // "div with" |
| range.setEnd(root.lastChild, 5); |
| expect(generationUtils.forTesting.containsBlockBoundary(range)) |
| .toEqual(false); |
| |
| // Detects boundaries that are only the start of a block node. |
| range.setStart(root.firstChild, 3); |
| range.setEnd(document.getElementById('b').firstChild, 5); // "a really" |
| expect(generationUtils.forTesting.containsBlockBoundary(range)) |
| .toEqual(true); |
| |
| // Detects boundaries that are only the end of a block node. |
| range.setStart(document.getElementById('e').firstChild, 1); // "fancy" |
| range.setEnd(root.childNodes[4], 7); // "div with" |
| expect(generationUtils.forTesting.containsBlockBoundary(range)) |
| .toEqual(true); |
| }); |
| |
| it('can find a word start inside a text node', function() { |
| document.body.innerHTML = __html__['word_bounds_test.html']; |
| const elt = document.getElementById('block'); |
| |
| // elt is an HTML element, not a text node, so we should find -1 |
| let result = generationUtils.forTesting.findWordStartBoundInTextNode(elt); |
| expect(result).toEqual(-1); |
| |
| const node = elt.firstChild; |
| // With no second arg, we find the first from the end |
| result = generationUtils.forTesting.findWordStartBoundInTextNode(node); |
| expect(result).toEqual(7); // Between " " and "b" |
| |
| // Second arg in the middle of a word |
| result = generationUtils.forTesting.findWordStartBoundInTextNode(node, 10); |
| expect(result).toEqual(7); // Between " " and "b" |
| |
| // Second arg immediately *before* a space should give the same output |
| result = generationUtils.forTesting.findWordStartBoundInTextNode(node, 6); |
| expect(result).toEqual(6) |
| |
| // No more spaces to the left of second arg, -1 |
| result = generationUtils.forTesting.findWordStartBoundInTextNode(node, 3); |
| expect(result).toEqual(-1); |
| }); |
| |
| it('can find a word end inside a text node', function() { |
| document.body.innerHTML = __html__['word_bounds_test.html']; |
| const elt = document.getElementById('block'); |
| |
| // elt is an HTML element, not a text node, so we should find -1 |
| let result = generationUtils.forTesting.findWordEndBoundInTextNode(elt); |
| expect(result).toEqual(-1); |
| |
| const node = elt.firstChild; |
| // With no second arg, we find the first |
| result = generationUtils.forTesting.findWordEndBoundInTextNode(node); |
| expect(result).toEqual(6); // Between "e" and " " |
| |
| // Second arg in the middle of a word |
| result = generationUtils.forTesting.findWordEndBoundInTextNode(node, 2); |
| expect(result).toEqual(6); // Between "e" and " " |
| |
| // Second arg immediately *after* a space should give the same output |
| result = generationUtils.forTesting.findWordEndBoundInTextNode(node, 7); |
| expect(result).toEqual(7) |
| |
| // No more spaces to the right of second arg, -1 |
| result = generationUtils.forTesting.findWordEndBoundInTextNode(node, 10); |
| expect(result).toEqual(-1); |
| }); |
| |
| it('can expand a range start to a word bound within a node', function() { |
| document.body.innerHTML = __html__['word_bounds_test.html']; |
| const range = document.createRange(); |
| const textNodeInBlock = document.getElementById('block').firstChild; |
| |
| range.setStart(textNodeInBlock, 10); |
| range.setEnd(textNodeInBlock, 12); |
| expect(range.toString()).toEqual('ck'); |
| |
| generationUtils.forTesting.expandRangeStartToWordBound(range); |
| expect(range.toString()).toEqual('block'); |
| }); |
| |
| it('can expand a range end to a word bound within a node', function() { |
| document.body.innerHTML = __html__['word_bounds_test.html']; |
| const range = document.createRange(); |
| const textNodeInBlock = document.getElementById('block').firstChild; |
| |
| range.setStart(textNodeInBlock, 0); |
| range.setEnd(textNodeInBlock, 3); |
| expect(range.toString()).toEqual('Ins'); |
| |
| generationUtils.forTesting.expandRangeEndToWordBound(range); |
| expect(range.toString()).toEqual('Inside'); |
| }); |
| |
| it('can expand a range start to an inner block boundary', function() { |
| document.body.innerHTML = __html__['word_bounds_test.html']; |
| const range = document.createRange(); |
| const textNodeInBlock = document.getElementById('block').firstChild; |
| |
| range.setStart(textNodeInBlock, 3); |
| range.setEnd(textNodeInBlock, 12); |
| expect(range.toString()).toEqual('ide block'); |
| |
| generationUtils.forTesting.expandRangeStartToWordBound(range); |
| expect(range.toString()).toEqual('Inside block'); |
| |
| expect(generationUtils.forTesting.containsBlockBoundary(range)) |
| .toEqual(false); |
| }); |
| |
| it('can expand a range end to an inner block boundary', function() { |
| document.body.innerHTML = __html__['word_bounds_test.html']; |
| const range = document.createRange(); |
| const textNodeInBlock = document.getElementById('block').firstChild; |
| |
| range.setStart(textNodeInBlock, 0); |
| range.setEnd(textNodeInBlock, 10); |
| expect(range.toString()).toEqual('Inside blo'); |
| |
| generationUtils.forTesting.expandRangeEndToWordBound(range); |
| expect(range.toString()).toEqual('Inside block'); |
| |
| expect(generationUtils.forTesting.containsBlockBoundary(range)) |
| .toEqual(false); |
| }); |
| |
| it('can expand a range end across inline elements', function() { |
| document.body.innerHTML = __html__['word_bounds_test.html']; |
| const range = document.createRange(); |
| const inlineTextNode = document.getElementById('inline').firstChild; |
| // Get the text node between the <p> and <i> nodes: |
| const middleTextNode = document.getElementById('root').childNodes[2]; |
| |
| range.setStart(middleTextNode, 3); |
| range.setEnd(inlineTextNode, 2); |
| expect(range.toString()).toEqual('Inli'); |
| |
| generationUtils.forTesting.expandRangeEndToWordBound(range); |
| expect(range.toString()).toEqual('Inline'); |
| |
| range.setStart(middleTextNode, 3); |
| range.setEnd(middleTextNode, 5); |
| expect(range.toString()).toEqual('In'); |
| |
| generationUtils.forTesting.expandRangeEndToWordBound(range); |
| expect(range.toString()).toEqual('Inline'); |
| }); |
| |
| it('can traverse in order for finding block boundaries', function() { |
| document.body.innerHTML = __html__['postorder-tree.html']; |
| const walker = document.createTreeWalker(document.getElementById('l')); |
| walker.currentNode = document.getElementById('b').firstChild; |
| const visited = generationUtils.forTesting.createForwardOverrideMap(walker); |
| const traversalOrder = []; |
| while (generationUtils.forTesting.forwardTraverse(walker, visited) != |
| null) { |
| if (walker.currentNode.id != null) { |
| traversalOrder.push(walker.currentNode.id); |
| } |
| } |
| expect(traversalOrder).toEqual([ |
| 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l' |
| ]); |
| }); |
| |
| it('can traverse in reverse order for finding block boundaries', function() { |
| document.body.innerHTML = __html__['postorder-tree.html']; |
| const walker = document.createTreeWalker(document.getElementById('l')); |
| const origin = document.getElementById('k').firstChild; |
| walker.currentNode = origin; |
| const visited = new Set(); |
| const traversalOrder = []; |
| while (generationUtils.forTesting.backwardTraverse( |
| walker, visited, origin) != null) { |
| if (walker.currentNode.id != null) { |
| traversalOrder.push(walker.currentNode.id); |
| } |
| } |
| expect(traversalOrder).toEqual([ |
| 'k', 'j', 'h', 'i', 'g', 'f', 'c', 'e', 'd', 'b', 'l' |
| ]); |
| }); |
| |
| it('can trim leading/trailing boundary characters from a string', function() { |
| expect(generationUtils.forTesting.trimBoundary('foo')).toEqual('foo'); |
| expect(generationUtils.forTesting.trimBoundary(' foo')).toEqual('foo'); |
| expect(generationUtils.forTesting.trimBoundary('foo ')).toEqual('foo'); |
| expect(generationUtils.forTesting.trimBoundary(' foo ')).toEqual('foo'); |
| expect(generationUtils.forTesting.trimBoundary('\n\'[]!foö...')) |
| .toEqual('foö'); |
| expect(generationUtils.forTesting.trimBoundary('...f...oo...')) |
| .toEqual('f...oo'); |
| }) |
| |
| it('can find the search space for range-based fragments', function() { |
| document.body.innerHTML = __html__['marks_test.html']; |
| const range = document.createRange(); |
| |
| // Simplest case: a whole element with a bunch of block boundaries inside. |
| range.selectNodeContents(document.getElementById('a')); |
| expect(fragmentUtils.forTesting.normalizeString( |
| generationUtils.forTesting.getSearchSpaceForStart(range))) |
| .toEqual('this is'); |
| expect(fragmentUtils.forTesting.normalizeString( |
| generationUtils.forTesting.getSearchSpaceForEnd(range))) |
| .toEqual('div with lots of different stuff in it'); |
| |
| // Starts and ends inside a text node. No block boundaries, so we should get |
| // an undefined return. |
| range.selectNodeContents(document.getElementById('e').firstChild); |
| expect(generationUtils.forTesting.getSearchSpaceForStart(range)) |
| .toBeUndefined(); |
| expect(generationUtils.forTesting.getSearchSpaceForEnd(range)) |
| .toBeUndefined(); |
| |
| |
| // Starts inside one block, ends outside that block |
| range.selectNodeContents(document.getElementById('a')); |
| range.setStart(document.getElementById('c').firstChild, 0); |
| expect(fragmentUtils.forTesting.normalizeString( |
| generationUtils.forTesting.getSearchSpaceForStart(range))) |
| .toEqual('elaborate'); |
| expect(fragmentUtils.forTesting.normalizeString( |
| generationUtils.forTesting.getSearchSpaceForEnd(range))) |
| .toEqual('div with lots of different stuff in it'); |
| |
| // Ends inside one block, started outside that block |
| range.selectNodeContents(document.getElementById('a')); |
| range.setEnd(document.getElementById('b').lastChild, 3); |
| expect(fragmentUtils.forTesting.normalizeString( |
| generationUtils.forTesting.getSearchSpaceForStart(range))) |
| .toEqual('this is'); |
| expect(fragmentUtils.forTesting.normalizeString( |
| generationUtils.forTesting.getSearchSpaceForEnd(range))) |
| .toEqual('a really elaborate'); |
| |
| // Starts and ends in different, non-overlapping divs |
| range.setStart(document.getElementById('c').firstChild, 0); |
| range.setEnd(document.getElementById('e').firstChild, 5); |
| expect(fragmentUtils.forTesting.normalizeString( |
| generationUtils.forTesting.getSearchSpaceForStart(range))) |
| .toEqual('elaborate'); |
| expect(fragmentUtils.forTesting.normalizeString( |
| generationUtils.forTesting.getSearchSpaceForEnd(range))) |
| .toEqual('fancy'); |
| |
| // Boundaries that aren't text nodes |
| range.setStart(document.getElementById('a'), 1); |
| range.setEnd(document.getElementById('a'), 6); |
| expect(fragmentUtils.forTesting.normalizeString( |
| generationUtils.forTesting.getSearchSpaceForStart(range))) |
| .toEqual('a really elaborate'); |
| expect(fragmentUtils.forTesting.normalizeString( |
| generationUtils.forTesting.getSearchSpaceForEnd(range))) |
| .toEqual('div with lots of different stuff'); |
| }); |
| |
| it('can generate progressively larger fragments across blocks', function() { |
| document.body.innerHTML = __html__['range-fragment-test.html']; |
| const range = document.createRange(); |
| range.selectNodeContents(document.getElementById('root')); |
| |
| const startSpace = generationUtils.forTesting.getSearchSpaceForStart(range); |
| const endSpace = generationUtils.forTesting.getSearchSpaceForEnd(range); |
| |
| const factory = new generationUtils.forTesting.FragmentFactory() |
| .setStartAndEndSearchSpace(startSpace, endSpace); |
| |
| expect(factory.embiggen()).toEqual(true); |
| expect(startSpace.substring(0, factory.startOffset)).toEqual('repeat'); |
| expect(endSpace.substring(factory.endOffset)).toEqual('repeat'); |
| |
| expect(factory.tryToMakeUniqueFragment()).toBeUndefined(); |
| |
| expect(factory.embiggen()).toEqual(true); |
| expect(startSpace.substring(0, factory.startOffset)) |
| .toEqual('repeat repeat'); |
| expect(endSpace.substring(factory.endOffset)).toEqual('repeat repeat'); |
| |
| expect(factory.tryToMakeUniqueFragment()).toBeUndefined(); |
| |
| expect(factory.embiggen()).toEqual(true); |
| expect(startSpace.substring(0, factory.startOffset)) |
| .toEqual('repeat repeat repeat'); |
| expect(endSpace.substring(factory.endOffset)) |
| .toEqual('repeat repeat repeat'); |
| |
| expect(factory.tryToMakeUniqueFragment()).toBeUndefined(); |
| |
| expect(factory.embiggen()).toEqual(true); |
| expect(startSpace.substring(0, factory.startOffset)) |
| .toEqual('repeat repeat repeat unique'); |
| expect(endSpace.substring(factory.endOffset)) |
| .toEqual('unique repeat repeat repeat'); |
| |
| const fragment = factory.tryToMakeUniqueFragment(); |
| expect(fragment).not.toBeUndefined(); |
| expect(fragment.textStart).toEqual('repeat repeat repeat unique'); |
| expect(fragment.textEnd).toEqual('unique repeat repeat repeat'); |
| |
| expect(factory.embiggen()).toEqual(true); |
| expect(startSpace.substring(0, factory.startOffset)) |
| .toEqual('repeat repeat repeat unique repeat'); |
| expect(endSpace.substring(factory.endOffset)) |
| .toEqual('repeat unique repeat repeat repeat'); |
| |
| expect(factory.embiggen()).toEqual(true); |
| expect(factory.embiggen()).toEqual(true); |
| |
| expect(factory.embiggen()).toEqual(false); |
| }); |
| |
| it('can generate progressively larger fragments within a block', function() { |
| const sharedSpace = 'text1 text2 text3 text4 text5 text6 text7'; |
| |
| const factory = |
| new generationUtils.forTesting.FragmentFactory().setSharedSearchSpace( |
| sharedSpace); |
| |
| expect(factory.embiggen()).toEqual(true); |
| expect(sharedSpace.substring(0, factory.startOffset)).toEqual('text1'); |
| expect(sharedSpace.substring(factory.endOffset)).toEqual('text7'); |
| |
| expect(factory.embiggen()).toEqual(true); |
| expect(sharedSpace.substring(0, factory.startOffset)) |
| .toEqual('text1 text2'); |
| expect(sharedSpace.substring(factory.endOffset)).toEqual('text6 text7'); |
| |
| expect(factory.embiggen()).toEqual(true); |
| expect(sharedSpace.substring(0, factory.startOffset)) |
| .toEqual('text1 text2 text3'); |
| expect(sharedSpace.substring(factory.endOffset)) |
| .toEqual('text5 text6 text7'); |
| |
| expect(factory.embiggen()).toEqual(true); |
| expect(sharedSpace.substring(0, factory.startOffset)) |
| .toEqual('text1 text2 text3 text4'); |
| expect(sharedSpace.substring(factory.endOffset)) |
| .toEqual(' text5 text6 text7'); |
| |
| expect(factory.embiggen()).toEqual(false); |
| }); |
| |
| it('can add context to a single-block range match', function() { |
| const sharedSpace = 'text1 text2 text3 text4 text5 text6 text7'; |
| const prefixSpace = 'prefix3 prefix2 prefix1'; |
| const suffixSpace = 'suffix1 suffix2 suffix3'; |
| |
| const factory = |
| new generationUtils.forTesting.FragmentFactory() |
| .setSharedSearchSpace(sharedSpace) |
| .setPrefixAndSuffixSearchSpace(prefixSpace, suffixSpace); |
| |
| expect(factory.embiggen()).toEqual(true); |
| expect(sharedSpace.substring(0, factory.startOffset)).toEqual('text1'); |
| expect(sharedSpace.substring(factory.endOffset)).toEqual('text7'); |
| |
| expect(factory.embiggen()).toEqual(true); |
| expect(sharedSpace.substring(0, factory.startOffset)) |
| .toEqual('text1 text2'); |
| expect(sharedSpace.substring(factory.endOffset)).toEqual('text6 text7'); |
| |
| expect(factory.embiggen()).toEqual(true); |
| expect(sharedSpace.substring(0, factory.startOffset)) |
| .toEqual('text1 text2 text3'); |
| expect(sharedSpace.substring(factory.endOffset)) |
| .toEqual('text5 text6 text7'); |
| |
| expect(factory.embiggen()).toEqual(true); |
| expect(sharedSpace.substring(0, factory.startOffset)) |
| .toEqual('text1 text2 text3 text4'); |
| expect(sharedSpace.substring(factory.endOffset)) |
| .toEqual(' text5 text6 text7'); |
| expect(prefixSpace.substring(factory.prefixOffset)).toEqual('prefix1'); |
| expect(suffixSpace.substring(0, factory.suffixOffset)).toEqual('suffix1'); |
| |
| expect(factory.embiggen()).toEqual(true); |
| expect(sharedSpace.substring(0, factory.startOffset)) |
| .toEqual('text1 text2 text3 text4'); |
| expect(sharedSpace.substring(factory.endOffset)) |
| .toEqual(' text5 text6 text7'); |
| expect(prefixSpace.substring(factory.prefixOffset)) |
| .toEqual('prefix2 prefix1'); |
| expect(suffixSpace.substring(0, factory.suffixOffset)) |
| .toEqual('suffix1 suffix2'); |
| |
| expect(factory.embiggen()).toEqual(true); |
| expect(sharedSpace.substring(0, factory.startOffset)) |
| .toEqual('text1 text2 text3 text4'); |
| expect(sharedSpace.substring(factory.endOffset)) |
| .toEqual(' text5 text6 text7'); |
| expect(prefixSpace.substring(factory.prefixOffset)) |
| .toEqual('prefix3 prefix2 prefix1'); |
| expect(suffixSpace.substring(0, factory.suffixOffset)) |
| .toEqual('suffix1 suffix2 suffix3'); |
| |
| expect(factory.embiggen()).toEqual(false); |
| }); |
| |
| it('can add context to an exact text match', function() { |
| const exactText = 'text1 text2 text3 text4 text5 text6 text7'; |
| const prefixSpace = 'prefix3 prefix2 prefix1'; |
| const suffixSpace = 'suffix1 suffix2 suffix3'; |
| |
| const factory = |
| new generationUtils.forTesting.FragmentFactory() |
| .setExactTextMatch(exactText) |
| .setPrefixAndSuffixSearchSpace(prefixSpace, suffixSpace); |
| |
| expect(factory.embiggen()).toEqual(true); |
| expect(factory.exactTextMatch).toEqual(exactText); |
| expect(prefixSpace.substring(factory.prefixOffset)).toEqual('prefix1'); |
| expect(suffixSpace.substring(0, factory.suffixOffset)).toEqual('suffix1'); |
| |
| expect(factory.embiggen()).toEqual(true); |
| expect(factory.exactTextMatch).toEqual(exactText); |
| expect(prefixSpace.substring(factory.prefixOffset)) |
| .toEqual('prefix2 prefix1'); |
| expect(suffixSpace.substring(0, factory.suffixOffset)) |
| .toEqual('suffix1 suffix2'); |
| |
| expect(factory.embiggen()).toEqual(true); |
| expect(factory.exactTextMatch).toEqual(exactText); |
| expect(prefixSpace.substring(factory.prefixOffset)) |
| .toEqual('prefix3 prefix2 prefix1'); |
| expect(suffixSpace.substring(0, factory.suffixOffset)) |
| .toEqual('suffix1 suffix2 suffix3'); |
| |
| expect(factory.embiggen()).toEqual(false); |
| }); |
| |
| it('can generate prefixes/suffixes to distinguish short matches', function() { |
| // This is the most common case for prefix/suffix matches: a user selects a |
| // word or a small portion of a word. |
| document.body.innerHTML = __html__['ambiguous-match.html']; |
| |
| const target = document.createRange(); |
| const selection = window.getSelection(); |
| |
| target.selectNodeContents(document.getElementById('target1')); |
| selection.removeAllRanges(); |
| selection.addRange(target); |
| |
| let result = generationUtils.generateFragment(selection); |
| expect(result.fragment.textStart).toEqual('target'); |
| expect(result.fragment.textEnd).toBeUndefined(); |
| expect(result.fragment.prefix).toEqual('prefix1'); |
| expect(result.fragment.suffix).toEqual('suffix1'); |
| |
| target.selectNodeContents(document.getElementById('target3')); |
| selection.removeAllRanges(); |
| selection.addRange(target); |
| |
| result = generationUtils.generateFragment(selection); |
| expect(result.fragment.textStart).toEqual('target'); |
| expect(result.fragment.textEnd).toBeUndefined(); |
| expect(result.fragment.prefix).toEqual('prefix1'); |
| expect(result.fragment.suffix).toEqual('suffix2'); |
| }); |
| |
| it('can generate prefixes/suffixes to distinguish long matches', function() { |
| // A passage which appears multiple times on a page. |
| document.body.innerHTML = __html__['long-ambiguous-match.html']; |
| |
| const target = document.createRange(); |
| const selection = window.getSelection(); |
| |
| const node = document.getElementById('target').firstChild; |
| target.setStart(node, 5); |
| target.setEnd(node, node.textContent.length - 8); |
| selection.removeAllRanges(); |
| selection.addRange(target); |
| |
| const result = generationUtils.generateFragment(selection); |
| expect(result.fragment.textStart).not.toBeFalsy(); |
| expect(result.fragment.textEnd).not.toBeFalsy(); |
| expect(fragmentUtils.forTesting.normalizeString(result.fragment.prefix)) |
| .toEqual('prefix. lorem ipsum dolor'); |
| expect(fragmentUtils.forTesting.normalizeString(result.fragment.suffix)) |
| .toEqual('recteque qui ei. suffix'); |
| }); |
| |
| it('can generate URLs spanning table elements', function() { |
| document.body.innerHTML = __html__['table.html']; |
| |
| const target = document.createRange(); |
| const selection = window.getSelection(); |
| |
| const nodeA = document.getElementById('a').firstChild; |
| target.setStart(nodeA, 6); |
| target.setEnd(nodeA, 11); |
| selection.removeAllRanges(); |
| selection.addRange(target); |
| |
| let result = generationUtils.generateFragment(selection); |
| expect(fragmentUtils.forTesting.normalizeString(result.fragment.prefix)) |
| .toEqual('first'); |
| expect(fragmentUtils.forTesting.normalizeString(result.fragment.textStart)) |
| .toEqual('named'); |
| |
| const nodeB = document.getElementById('b').firstChild; |
| target.setStart(nodeA, 0); |
| target.setEnd(nodeB, nodeB.textContent.length); |
| selection.removeAllRanges(); |
| selection.addRange(target); |
| |
| result = generationUtils.generateFragment(selection); |
| expect(fragmentUtils.forTesting.normalizeString(result.fragment.textStart)) |
| .toEqual('first named'); |
| expect(fragmentUtils.forTesting.normalizeString(result.fragment.textEnd)) |
| .toEqual('2014'); |
| }); |
| |
| it('will halt generation after a certain time period', function() { |
| document.body.innerHTML = __html__['basic_test.html']; |
| const range = document.createRange(); |
| range.selectNodeContents(document.body.firstChild.firstChild); |
| |
| const selection = window.getSelection(); |
| selection.removeAllRanges(); |
| selection.addRange(range); |
| |
| expect(function() { |
| generationUtils.forTesting.doGenerateFragment( |
| selection, Date.now() - 1000); |
| }).toThrowMatching(function(thrown) { |
| return thrown.isTimeout |
| }); |
| |
| generationUtils.setTimeout(2000); |
| expect(function() { |
| generationUtils.forTesting.doGenerateFragment( |
| selection, Date.now() - 1000); |
| }).not.toThrowMatching(function(thrown) { |
| return thrown.isTimeout |
| }); |
| }); |
| |
| |
| it('will halt search space creation after a certain time period', function() { |
| document.body.innerHTML = __html__['complicated-layout.html']; |
| const range = document.createRange(); |
| range.selectNodeContents(document.getElementById('root')); |
| |
| generationUtils.forTesting.recordStartTime(Date.now() - 1000); |
| expect(function() { |
| generationUtils.forTesting.getSearchSpaceForStart(range) |
| }).toThrowMatching(function(thrown) { |
| return thrown.isTimeout |
| }); |
| }); |
| |
| it('identifies bad ranges as ineligible', async function() { |
| document.body.innerHTML = __html__['range-eligibility.html']; |
| const range = document.createRange(); |
| |
| range.selectNodeContents(document.getElementById('regular-string')); |
| expect(generationUtils.isValidRangeForFragmentGeneration(range)).toBeTrue(); |
| |
| range.selectNodeContents(document.getElementById('inline-image')); |
| expect(generationUtils.isValidRangeForFragmentGeneration(range)).toBeTrue(); |
| |
| range.selectNodeContents(document.getElementById('punctuation-only')); |
| expect(generationUtils.isValidRangeForFragmentGeneration(range)) |
| .toBeFalse(); |
| |
| range.selectNodeContents(document.getElementById('inside-an-editable')); |
| expect(generationUtils.isValidRangeForFragmentGeneration(range)) |
| .toBeFalse(); |
| |
| range.selectNodeContents(document.getElementById('textarea')); |
| expect(generationUtils.isValidRangeForFragmentGeneration(range)) |
| .toBeFalse(); |
| |
| range.selectNodeContents(document.getElementById('input')); |
| expect(generationUtils.isValidRangeForFragmentGeneration(range)) |
| .toBeFalse(); |
| |
| const iframe = document.createElement('iframe'); |
| const setupIframe = new Promise((resolve, reject) => { |
| iframe.srcdoc = '<p>test words</p>'; |
| iframe.onload = () => resolve(); |
| iframe.onerror = () => reject(new Error()); |
| document.body.appendChild(iframe); |
| }); |
| await setupIframe; |
| range.selectNodeContents( |
| iframe.contentDocument.getElementsByTagName('p')[0]); |
| expect(generationUtils.isValidRangeForFragmentGeneration(range)) |
| .toBeFalse(); |
| |
| // Should work for a long (1000) string of punctuation with a regular |
| // character at the end, but not a *really really* long (100k) one. |
| const longString = document.createElement('p'); |
| let longStringContents = ' '.repeat(1000) + 'a'; |
| longString.textContent = longStringContents; |
| range.selectNodeContents(longString); |
| expect(generationUtils.isValidRangeForFragmentGeneration(range)).toBeTrue(); |
| longStringContents = ' '.repeat(100000) + 'a'; |
| longString.textContent = longStringContents; |
| range.selectNodeContents(longString); |
| expect(generationUtils.isValidRangeForFragmentGeneration(range)) |
| .toBeFalse(); |
| |
| // Set up a really deep hierarchy and make sure we don't traverse the whole |
| // thing |
| let node = document.createElement('div'); |
| document.body.appendChild(node); |
| for (let i = 0; i < 1000; i++) { |
| const newNode = document.createElement('div'); |
| node.appendChild(newNode); |
| node = newNode; |
| } |
| node.textContent = 'hello'; |
| range.selectNodeContents(node); |
| expect(generationUtils.isValidRangeForFragmentGeneration(range)) |
| .toBeFalse(); |
| }); |
| }); |