|  | <!DOCTYPE html> | 
|  | <!-- | 
|  | @license | 
|  | Copyright (C) 2016 The Android Open Source Project | 
|  |  | 
|  | Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | you may not use this file except in compliance with the License. | 
|  | You may obtain a copy of the License at | 
|  |  | 
|  | http://www.apache.org/licenses/LICENSE-2.0 | 
|  |  | 
|  | Unless required by applicable law or agreed to in writing, software | 
|  | distributed under the License is distributed on an "AS IS" BASIS, | 
|  | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | See the License for the specific language governing permissions and | 
|  | limitations under the License. | 
|  | --> | 
|  |  | 
|  | <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes"> | 
|  | <meta charset="utf-8"> | 
|  | <title>gr-diff-selection</title> | 
|  |  | 
|  | <script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script> | 
|  |  | 
|  | <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script> | 
|  | <script src="/components/wct-browser-legacy/browser.js"></script> | 
|  |  | 
|  | <test-fixture id="basic"> | 
|  | <template> | 
|  | <gr-diff-selection> | 
|  | <table id="diffTable" class="side-by-side"> | 
|  | <tr class="diff-row"> | 
|  | <td class="blame" data-line-number="1"></td> | 
|  | <td class="lineNum left" data-value="1">1</td> | 
|  | <td class="content"> | 
|  | <div class="contentText" data-side="left">ba ba</div> | 
|  | <div data-side="left"> | 
|  | <div class="comment-thread"> | 
|  | <div class="gr-formatted-text message"> | 
|  | <span id="output" class="gr-linked-text">This is a comment</span> | 
|  | </div> | 
|  | </div> | 
|  | </div> | 
|  | </td> | 
|  | <td class="lineNum right" data-value="1">1</td> | 
|  | <td class="content"> | 
|  | <div class="contentText" data-side="right">some other text</div> | 
|  | </td> | 
|  | </tr> | 
|  | <tr class="diff-row"> | 
|  | <td class="blame" data-line-number="2"></td> | 
|  | <td class="lineNum left" data-value="2">2</td> | 
|  | <td class="content"> | 
|  | <div class="contentText" data-side="left">zin</div> | 
|  | </td> | 
|  | <td class="lineNum right" data-value="2">2</td> | 
|  | <td class="content"> | 
|  | <div class="contentText" data-side="right">more more more</div> | 
|  | <div data-side="right"> | 
|  | <div class="comment-thread"> | 
|  | <div class="gr-formatted-text message"> | 
|  | <span id="output" class="gr-linked-text">This is a comment on the right</span> | 
|  | </div> | 
|  | </div> | 
|  | </div> | 
|  | </td> | 
|  | </tr> | 
|  | <tr class="diff-row"> | 
|  | <td class="blame" data-line-number="3"></td> | 
|  | <td class="lineNum left" data-value="3">3</td> | 
|  | <td class="content"> | 
|  | <div class="contentText" data-side="left">ga ga</div> | 
|  | <div data-side="left"> | 
|  | <div class="comment-thread"> | 
|  | <div class="gr-formatted-text message"> | 
|  | <span id="output" class="gr-linked-text">This is <a>a</a> different comment 💩 unicode is fun</span> | 
|  | </div> | 
|  | </div> | 
|  | </div> | 
|  | </td> | 
|  | <td class="lineNum right" data-value="3">3</td> | 
|  | </tr> | 
|  | <tr class="diff-row"> | 
|  | <td class="blame" data-line-number="4"></td> | 
|  | <td class="lineNum left" data-value="4">4</td> | 
|  | <td class="content"> | 
|  | <div class="contentText" data-side="left">ga ga</div> | 
|  | <div data-side="left"> | 
|  | <div class="comment-thread"> | 
|  | <textarea data-side="right">test for textarea copying</textarea> | 
|  | </div> | 
|  | </div> | 
|  | </td> | 
|  | <td class="lineNum right" data-value="4">4</td> | 
|  | </tr> | 
|  | <tr class="not-diff-row"> | 
|  | <td class="other"> | 
|  | <div class="contentText" data-side="right">some other text</div> | 
|  | </td> | 
|  | </tr> | 
|  | </table> | 
|  | </gr-diff-selection> | 
|  | </template> | 
|  | </test-fixture> | 
|  |  | 
|  | <script type="module"> | 
|  | import '../../../test/common-test-setup.js'; | 
|  | import './gr-diff-selection.js'; | 
|  | suite('gr-diff-selection', () => { | 
|  | let element; | 
|  | let sandbox; | 
|  |  | 
|  | const emulateCopyOn = function(target) { | 
|  | const fakeEvent = { | 
|  | target, | 
|  | preventDefault: sandbox.stub(), | 
|  | clipboardData: { | 
|  | setData: sandbox.stub(), | 
|  | }, | 
|  | }; | 
|  | element._getCopyEventTarget.returns(target); | 
|  | element._handleCopy(fakeEvent); | 
|  | return fakeEvent; | 
|  | }; | 
|  |  | 
|  | setup(() => { | 
|  | element = fixture('basic'); | 
|  | sandbox = sinon.sandbox.create(); | 
|  | sandbox.stub(element, '_getCopyEventTarget'); | 
|  | element._cachedDiffBuilder = { | 
|  | getLineElByChild: sandbox.stub().returns({}), | 
|  | getSideByLineEl: sandbox.stub(), | 
|  | diffElement: element.querySelector('#diffTable'), | 
|  | }; | 
|  | element.diff = { | 
|  | content: [ | 
|  | { | 
|  | a: ['ba ba'], | 
|  | b: ['some other text'], | 
|  | }, | 
|  | { | 
|  | a: ['zin'], | 
|  | b: ['more more more'], | 
|  | }, | 
|  | { | 
|  | a: ['ga ga'], | 
|  | b: ['some other text'], | 
|  | }, | 
|  | ], | 
|  | }; | 
|  | }); | 
|  |  | 
|  | teardown(() => { | 
|  | sandbox.restore(); | 
|  | }); | 
|  |  | 
|  | test('applies selected-left on left side click', () => { | 
|  | element.classList.add('selected-right'); | 
|  | element._cachedDiffBuilder.getSideByLineEl.returns('left'); | 
|  | MockInteractions.down(element); | 
|  | assert.isTrue( | 
|  | element.classList.contains('selected-left'), 'adds selected-left'); | 
|  | assert.isFalse( | 
|  | element.classList.contains('selected-right'), | 
|  | 'removes selected-right'); | 
|  | }); | 
|  |  | 
|  | test('applies selected-right on right side click', () => { | 
|  | element.classList.add('selected-left'); | 
|  | element._cachedDiffBuilder.getSideByLineEl.returns('right'); | 
|  | MockInteractions.down(element); | 
|  | assert.isTrue( | 
|  | element.classList.contains('selected-right'), 'adds selected-right'); | 
|  | assert.isFalse( | 
|  | element.classList.contains('selected-left'), 'removes selected-left'); | 
|  | }); | 
|  |  | 
|  | test('applies selected-blame on blame click', () => { | 
|  | element.classList.add('selected-left'); | 
|  | element.diffBuilder.getLineElByChild.returns(null); | 
|  | sandbox.stub(element, '_elementDescendedFromClass', | 
|  | (el, className) => className === 'blame'); | 
|  | MockInteractions.down(element); | 
|  | assert.isTrue( | 
|  | element.classList.contains('selected-blame'), 'adds selected-right'); | 
|  | assert.isFalse( | 
|  | element.classList.contains('selected-left'), 'removes selected-left'); | 
|  | }); | 
|  |  | 
|  | test('ignores copy for non-content Element', () => { | 
|  | sandbox.stub(element, '_getSelectedText'); | 
|  | emulateCopyOn(element.querySelector('.not-diff-row')); | 
|  | assert.isFalse(element._getSelectedText.called); | 
|  | }); | 
|  |  | 
|  | test('asks for text for left side Elements', () => { | 
|  | element._cachedDiffBuilder.getSideByLineEl.returns('left'); | 
|  | sandbox.stub(element, '_getSelectedText'); | 
|  | emulateCopyOn(element.querySelector('div.contentText')); | 
|  | assert.deepEqual(['left', false], element._getSelectedText.lastCall.args); | 
|  | }); | 
|  |  | 
|  | test('reacts to copy for content Elements', () => { | 
|  | sandbox.stub(element, '_getSelectedText'); | 
|  | emulateCopyOn(element.querySelector('div.contentText')); | 
|  | assert.isTrue(element._getSelectedText.called); | 
|  | }); | 
|  |  | 
|  | test('copy event is prevented for content Elements', () => { | 
|  | sandbox.stub(element, '_getSelectedText'); | 
|  | element._cachedDiffBuilder.getSideByLineEl.returns('left'); | 
|  | element._getSelectedText.returns('test'); | 
|  | const event = emulateCopyOn(element.querySelector('div.contentText')); | 
|  | assert.isTrue(event.preventDefault.called); | 
|  | }); | 
|  |  | 
|  | test('inserts text into clipboard on copy', () => { | 
|  | sandbox.stub(element, '_getSelectedText').returns('the text'); | 
|  | const event = emulateCopyOn(element.querySelector('div.contentText')); | 
|  | assert.deepEqual( | 
|  | ['Text', 'the text'], event.clipboardData.setData.lastCall.args); | 
|  | }); | 
|  |  | 
|  | test('_setClasses adds given SelectionClass values, removes others', () => { | 
|  | element.classList.add('selected-right'); | 
|  | element._setClasses(['selected-comment', 'selected-left']); | 
|  | assert.isTrue(element.classList.contains('selected-comment')); | 
|  | assert.isTrue(element.classList.contains('selected-left')); | 
|  | assert.isFalse(element.classList.contains('selected-right')); | 
|  | assert.isFalse(element.classList.contains('selected-blame')); | 
|  |  | 
|  | element._setClasses(['selected-blame']); | 
|  | assert.isFalse(element.classList.contains('selected-comment')); | 
|  | assert.isFalse(element.classList.contains('selected-left')); | 
|  | assert.isFalse(element.classList.contains('selected-right')); | 
|  | assert.isTrue(element.classList.contains('selected-blame')); | 
|  | }); | 
|  |  | 
|  | test('_setClasses removes before it ads', () => { | 
|  | element.classList.add('selected-right'); | 
|  | const addStub = sandbox.stub(element.classList, 'add'); | 
|  | const removeStub = sandbox.stub(element.classList, 'remove', () => { | 
|  | assert.isFalse(addStub.called); | 
|  | }); | 
|  | element._setClasses(['selected-comment', 'selected-left']); | 
|  | assert.isTrue(addStub.called); | 
|  | assert.isTrue(removeStub.called); | 
|  | }); | 
|  |  | 
|  | test('copies content correctly', () => { | 
|  | // Fetch the line number. | 
|  | element._cachedDiffBuilder.getLineElByChild = function(child) { | 
|  | while (!child.classList.contains('content') && child.parentElement) { | 
|  | child = child.parentElement; | 
|  | } | 
|  | return child.previousElementSibling; | 
|  | }; | 
|  |  | 
|  | element.classList.add('selected-left'); | 
|  | element.classList.remove('selected-right'); | 
|  |  | 
|  | const selection = window.getSelection(); | 
|  | selection.removeAllRanges(); | 
|  | const range = document.createRange(); | 
|  | range.setStart(element.querySelector('div.contentText').firstChild, 3); | 
|  | range.setEnd( | 
|  | element.querySelectorAll('div.contentText')[4].firstChild, 2); | 
|  | selection.addRange(range); | 
|  | assert.equal(element._getSelectedText('left'), 'ba\nzin\nga'); | 
|  | }); | 
|  |  | 
|  | test('copies comments', () => { | 
|  | element.classList.add('selected-left'); | 
|  | element.classList.add('selected-comment'); | 
|  | element.classList.remove('selected-right'); | 
|  | const selection = window.getSelection(); | 
|  | selection.removeAllRanges(); | 
|  | const range = document.createRange(); | 
|  | range.setStart( | 
|  | element.querySelector('.gr-formatted-text *').firstChild, 3); | 
|  | range.setEnd( | 
|  | element.querySelectorAll('.gr-formatted-text *')[2].childNodes[2], 7); | 
|  | selection.addRange(range); | 
|  | assert.equal('s is a comment\nThis is a differ', | 
|  | element._getSelectedText('left', true)); | 
|  | }); | 
|  |  | 
|  | test('respects astral chars in comments', () => { | 
|  | element.classList.add('selected-left'); | 
|  | element.classList.add('selected-comment'); | 
|  | element.classList.remove('selected-right'); | 
|  | const selection = window.getSelection(); | 
|  | selection.removeAllRanges(); | 
|  | const range = document.createRange(); | 
|  | const nodes = element.querySelectorAll('.gr-formatted-text *'); | 
|  | range.setStart(nodes[2].childNodes[2], 13); | 
|  | range.setEnd(nodes[2].childNodes[2], 23); | 
|  | selection.addRange(range); | 
|  | assert.equal('mment 💩 u', | 
|  | element._getSelectedText('left', true)); | 
|  | }); | 
|  |  | 
|  | test('defers to default behavior for textarea', () => { | 
|  | element.classList.add('selected-left'); | 
|  | element.classList.remove('selected-right'); | 
|  | const selectedTextSpy = sandbox.spy(element, '_getSelectedText'); | 
|  | emulateCopyOn(element.querySelector('textarea')); | 
|  | assert.isFalse(selectedTextSpy.called); | 
|  | }); | 
|  |  | 
|  | test('regression test for 4794', () => { | 
|  | element._cachedDiffBuilder.getLineElByChild = function(child) { | 
|  | while (!child.classList.contains('content') && child.parentElement) { | 
|  | child = child.parentElement; | 
|  | } | 
|  | return child.previousElementSibling; | 
|  | }; | 
|  |  | 
|  | element.classList.add('selected-right'); | 
|  | element.classList.remove('selected-left'); | 
|  |  | 
|  | const selection = window.getSelection(); | 
|  | selection.removeAllRanges(); | 
|  | const range = document.createRange(); | 
|  | range.setStart( | 
|  | element.querySelectorAll('div.contentText')[1].firstChild, 4); | 
|  | range.setEnd( | 
|  | element.querySelectorAll('div.contentText')[1].firstChild, 10); | 
|  | selection.addRange(range); | 
|  | assert.equal(element._getSelectedText('right'), ' other'); | 
|  | }); | 
|  |  | 
|  | test('copies to end of side (issue 7895)', () => { | 
|  | element._cachedDiffBuilder.getLineElByChild = function(child) { | 
|  | // Return null for the end container. | 
|  | if (child.textContent === 'ga ga') { return null; } | 
|  | while (!child.classList.contains('content') && child.parentElement) { | 
|  | child = child.parentElement; | 
|  | } | 
|  | return child.previousElementSibling; | 
|  | }; | 
|  | element.classList.add('selected-left'); | 
|  | element.classList.remove('selected-right'); | 
|  | const selection = window.getSelection(); | 
|  | selection.removeAllRanges(); | 
|  | const range = document.createRange(); | 
|  | range.setStart(element.querySelector('div.contentText').firstChild, 3); | 
|  | range.setEnd( | 
|  | element.querySelectorAll('div.contentText')[4].firstChild, 2); | 
|  | selection.addRange(range); | 
|  | assert.equal(element._getSelectedText('left'), 'ba\nzin\nga'); | 
|  | }); | 
|  |  | 
|  | suite('_getTextContentForRange', () => { | 
|  | let selection; | 
|  | let range; | 
|  | let nodes; | 
|  |  | 
|  | setup(() => { | 
|  | element.classList.add('selected-left'); | 
|  | element.classList.add('selected-comment'); | 
|  | element.classList.remove('selected-right'); | 
|  | selection = window.getSelection(); | 
|  | selection.removeAllRanges(); | 
|  | range = document.createRange(); | 
|  | nodes = element.querySelectorAll('.gr-formatted-text *'); | 
|  | }); | 
|  |  | 
|  | test('multi level element contained in range', () => { | 
|  | range.setStart(nodes[2].childNodes[0], 1); | 
|  | range.setEnd(nodes[2].childNodes[2], 7); | 
|  | selection.addRange(range); | 
|  | assert.equal(element._getTextContentForRange(element, selection, range), | 
|  | 'his is a differ'); | 
|  | }); | 
|  |  | 
|  | test('multi level element as startContainer of range', () => { | 
|  | range.setStart(nodes[2].childNodes[1], 0); | 
|  | range.setEnd(nodes[2].childNodes[2], 7); | 
|  | selection.addRange(range); | 
|  | assert.equal(element._getTextContentForRange(element, selection, range), | 
|  | 'a differ'); | 
|  | }); | 
|  |  | 
|  | test('startContainer === endContainer', () => { | 
|  | range.setStart(nodes[0].firstChild, 2); | 
|  | range.setEnd(nodes[0].firstChild, 12); | 
|  | selection.addRange(range); | 
|  | assert.equal(element._getTextContentForRange(element, selection, range), | 
|  | 'is is a co'); | 
|  | }); | 
|  | }); | 
|  |  | 
|  | test('cache is reset when diff changes', () => { | 
|  | element._linesCache = {left: 'test', right: 'test'}; | 
|  | element.diff = {}; | 
|  | flushAsynchronousOperations(); | 
|  | assert.deepEqual(element._linesCache, {left: null, right: null}); | 
|  | }); | 
|  | }); | 
|  | </script> |