| /** |
| * @license |
| * Copyright 2022 Google LLC |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| import {assert} from '@open-wc/testing'; |
| import '../../../test/common-test-setup'; |
| import { |
| createElementDiff, |
| formatText, |
| createTabWrapper, |
| getRange, |
| computeKeyLocations, |
| GrDiffCommentThread, |
| getDataFromCommentThreadEl, |
| compareComments, |
| GrDiffThreadElement, |
| computeContext, |
| FULL_CONTEXT, |
| FullContext, |
| computeLineLength, |
| } from './gr-diff-utils'; |
| import {FILE, LOST, Side} from '../../../api/diff'; |
| import {createDefaultDiffPrefs} from '../../../constants/constants'; |
| |
| const LINE_BREAK_HTML = '<span class="gr-diff br"></span>'; |
| |
| suite('gr-diff-utils tests', () => { |
| test('createElementDiff classStr applies all classes', () => { |
| const node = createElementDiff('div', 'test classes'); |
| assert.isTrue(node.classList.contains('gr-diff')); |
| assert.isTrue(node.classList.contains('test')); |
| assert.isTrue(node.classList.contains('classes')); |
| }); |
| |
| test('formatText newlines 1', () => { |
| let text = 'abcdef'; |
| |
| assert.equal( |
| formatText(text, 'NONE', 4, 10, '').firstElementChild?.innerHTML, |
| text |
| ); |
| text = 'a'.repeat(20); |
| assert.equal( |
| formatText(text, 'NONE', 4, 10, '').firstElementChild?.innerHTML, |
| 'a'.repeat(10) + LINE_BREAK_HTML + 'a'.repeat(10) |
| ); |
| }); |
| |
| test('formatText newlines 2', () => { |
| const text = '<span class="thumbsup">👍</span>'; |
| assert.equal( |
| formatText(text, 'NONE', 4, 10, '').firstElementChild?.innerHTML, |
| '<span clas' + |
| LINE_BREAK_HTML + |
| 's="thumbsu' + |
| LINE_BREAK_HTML + |
| 'p">👍</span' + |
| LINE_BREAK_HTML + |
| '>' |
| ); |
| }); |
| |
| test('formatText newlines 3', () => { |
| const text = '01234\t56789'; |
| assert.equal( |
| formatText(text, 'NONE', 4, 10, '').firstElementChild?.innerHTML, |
| '01234' + createTabWrapper(3).outerHTML + '56' + LINE_BREAK_HTML + '789' |
| ); |
| }); |
| |
| test('formatText newlines 4', () => { |
| const text = '👍'.repeat(58); |
| assert.equal( |
| formatText(text, 'NONE', 4, 20, '').firstElementChild?.innerHTML, |
| '👍'.repeat(20) + |
| LINE_BREAK_HTML + |
| '👍'.repeat(20) + |
| LINE_BREAK_HTML + |
| '👍'.repeat(18) |
| ); |
| }); |
| |
| test('tab wrapper style', () => { |
| const pattern = new RegExp( |
| '^<span class="gr-diff tab" ' + |
| 'style="((?:-moz-)?tab-size: (\\d+);.?)+">\\t<\\/span>$' |
| ); |
| |
| for (const size of [1, 3, 8, 55]) { |
| const html = createTabWrapper(size).outerHTML; |
| assert.match(html, pattern); |
| assert.equal(html.match(pattern)?.[2], size.toString()); |
| } |
| }); |
| |
| test('tab wrapper insertion', () => { |
| const html = 'abc\tdef'; |
| const tabSize = 8; |
| const wrapper = createTabWrapper(tabSize - 3); |
| assert.ok(wrapper); |
| assert.equal(wrapper.innerText, '\t'); |
| assert.equal( |
| formatText(html, 'NONE', tabSize, Infinity, '').firstElementChild |
| ?.innerHTML, |
| 'abc' + wrapper.outerHTML + 'def' |
| ); |
| }); |
| |
| test('escaping HTML', () => { |
| let input = '<script>alert("XSS");<' + '/script>'; |
| let expected = '<script>alert("XSS");</script>'; |
| |
| let result = formatText(input, 'NONE', 1, Number.POSITIVE_INFINITY, '') |
| .firstElementChild?.innerHTML; |
| assert.equal(result, expected); |
| |
| input = '& < > " \' / `'; |
| expected = '& < > " \' / `'; |
| result = formatText(input, 'NONE', 1, Number.POSITIVE_INFINITY, '') |
| .firstElementChild?.innerHTML; |
| assert.equal(result, expected); |
| }); |
| |
| test('text length with tabs and unicode', () => { |
| function expectTextLength(text: string, tabSize: number, expected: number) { |
| // Formatting to |expected| columns should not introduce line breaks. |
| const result = formatText(text, 'NONE', tabSize, expected, '') |
| .firstElementChild!; |
| assert.isNotOk( |
| result.querySelector('.contentText > .br'), |
| ' Expected the result of: \n' + |
| ` _formatText(${text}', 'NONE', ${tabSize}, ${expected})\n` + |
| ' to not contain a br. But the actual result HTML was:\n' + |
| ` '${result.innerHTML}'\nwhereupon` |
| ); |
| |
| // Increasing the line limit should produce the same markup. |
| assert.equal( |
| formatText(text, 'NONE', tabSize, Infinity, '').firstElementChild |
| ?.innerHTML, |
| result.innerHTML |
| ); |
| assert.equal( |
| formatText(text, 'NONE', tabSize, expected + 1, '').firstElementChild |
| ?.innerHTML, |
| result.innerHTML |
| ); |
| |
| // Decreasing the line limit should introduce line breaks. |
| if (expected > 0) { |
| const tooSmall = formatText(text, 'NONE', tabSize, expected - 1, '') |
| .firstElementChild!; |
| assert.isOk( |
| tooSmall.querySelector('.contentText .br'), |
| ' Expected the result of: \n' + |
| ` _formatText(${text}', ${tabSize}, ${expected - 1})\n` + |
| ' to contain a br. But the actual result HTML was:\n' + |
| ` '${tooSmall.innerHTML}'\nwhereupon` |
| ); |
| } |
| } |
| expectTextLength('12345', 4, 5); |
| expectTextLength('\t\t12', 4, 10); |
| expectTextLength('abc💢123', 4, 7); |
| expectTextLength('abc\t', 8, 8); |
| expectTextLength('abc\t\t', 10, 20); |
| expectTextLength('', 10, 0); |
| // 17 Thai combining chars. |
| expectTextLength('ก้้้้้้้้้้้้้้้้', 4, 17); |
| expectTextLength('abc\tde', 10, 12); |
| expectTextLength('abc\tde\t', 10, 20); |
| expectTextLength('\t\t\t\t\t', 20, 100); |
| }); |
| |
| test('getRange returns undefined with start_line = 0', () => { |
| const range = { |
| start_line: 0, |
| end_line: 12, |
| start_character: 0, |
| end_character: 0, |
| }; |
| const threadEl = document.createElement('div'); |
| threadEl.className = 'comment-thread'; |
| threadEl.setAttribute('diff-side', 'right'); |
| threadEl.setAttribute('line-num', '1'); |
| threadEl.setAttribute('range', JSON.stringify(range)); |
| threadEl.setAttribute('slot', 'right-1'); |
| assert.isUndefined(getRange(threadEl)); |
| }); |
| |
| suite('computeContext', () => { |
| test('computeContext 1', () => { |
| assert.equal(computeContext(1, FullContext.YES, 2), FULL_CONTEXT); |
| assert.equal(computeContext(1, FullContext.NO, 2), 1); |
| assert.equal(computeContext(1, FullContext.UNDECIDED, 2), 1); |
| }); |
| |
| test('computeContext 0', () => { |
| assert.equal(computeContext(0, FullContext.YES, 2), FULL_CONTEXT); |
| assert.equal(computeContext(0, FullContext.NO, 2), 0); |
| assert.equal(computeContext(0, FullContext.UNDECIDED, 2), 0); |
| }); |
| |
| test('computeContext FULL_CONTEXT', () => { |
| assert.equal( |
| computeContext(FULL_CONTEXT, FullContext.YES, 2), |
| FULL_CONTEXT |
| ); |
| assert.equal(computeContext(FULL_CONTEXT, FullContext.NO, 2), 2); |
| assert.equal( |
| computeContext(FULL_CONTEXT, FullContext.UNDECIDED, 2), |
| FULL_CONTEXT |
| ); |
| }); |
| }); |
| |
| suite('computeLineLength', () => { |
| test('computeLineLength(1, ...)', () => { |
| assert.equal( |
| computeLineLength( |
| {...createDefaultDiffPrefs(), line_length: 1}, |
| 'a.txt' |
| ), |
| 1 |
| ); |
| assert.equal( |
| computeLineLength( |
| {...createDefaultDiffPrefs(), line_length: 1}, |
| undefined |
| ), |
| 1 |
| ); |
| }); |
| |
| test('computeLineLength(1, "/COMMIT_MSG")', () => { |
| assert.equal( |
| computeLineLength( |
| {...createDefaultDiffPrefs(), line_length: 1}, |
| '/COMMIT_MSG' |
| ), |
| 72 |
| ); |
| }); |
| }); |
| |
| suite('key locations', () => { |
| test('lineOfInterest is a key location', () => { |
| const lineOfInterest = {lineNum: 789, side: Side.LEFT}; |
| assert.deepEqual(computeKeyLocations(lineOfInterest, []), { |
| left: {789: true}, |
| right: {}, |
| }); |
| }); |
| |
| test('line comments are key locations', async () => { |
| const comments: GrDiffCommentThread[] = [{side: Side.RIGHT, line: 3}]; |
| assert.deepEqual(computeKeyLocations(undefined, comments), { |
| left: {}, |
| right: {3: true}, |
| }); |
| }); |
| |
| test('file comments are key locations', async () => { |
| const comments: GrDiffCommentThread[] = [{side: Side.LEFT, line: FILE}]; |
| assert.deepEqual(computeKeyLocations(undefined, comments), { |
| left: {FILE: true}, |
| right: {}, |
| }); |
| }); |
| |
| test('lots of key locations', () => { |
| const lineOfInterest = {lineNum: 789, side: Side.LEFT}; |
| const comments: GrDiffCommentThread[] = [ |
| {side: Side.LEFT, line: FILE}, |
| {side: Side.LEFT, line: 2}, |
| {side: Side.LEFT, line: 111}, |
| {side: Side.RIGHT, line: LOST}, |
| {side: Side.RIGHT, line: 13}, |
| {side: Side.RIGHT, line: 19}, |
| ]; |
| assert.deepEqual(computeKeyLocations(lineOfInterest, comments), { |
| left: {FILE: true, 2: true, 111: true, 789: true}, |
| right: {LOST: true, 13: true, 19: true}, |
| }); |
| }); |
| }); |
| |
| suite('toCommentThreadModel', () => { |
| test('simple example', () => { |
| const el = document.createElement( |
| 'div' |
| ) as unknown as GrDiffThreadElement; |
| el.className = 'comment-thread'; |
| el.setAttribute('diff-side', 'left'); |
| el.setAttribute('line-num', '3'); |
| el.rootId = 'ab12'; |
| |
| assert.deepEqual(getDataFromCommentThreadEl(el), { |
| line: 3, |
| side: Side.LEFT, |
| range: undefined, |
| rootId: 'ab12', |
| }); |
| }); |
| |
| test('FILE default', () => { |
| const el = document.createElement( |
| 'div' |
| ) as unknown as GrDiffThreadElement; |
| el.className = 'comment-thread'; |
| el.setAttribute('diff-side', 'left'); |
| el.rootId = 'ab12'; |
| |
| assert.deepEqual(getDataFromCommentThreadEl(el), { |
| line: FILE, |
| side: Side.LEFT, |
| range: undefined, |
| rootId: 'ab12', |
| }); |
| }); |
| |
| test('undefined', () => { |
| const el = document.createElement( |
| 'div' |
| ) as unknown as GrDiffThreadElement; |
| assert.isUndefined(getDataFromCommentThreadEl(el)); |
| el.className = 'comment-thread'; |
| assert.isUndefined(getDataFromCommentThreadEl(el)); |
| el.setAttribute('line-num', '3'); |
| assert.isUndefined(getDataFromCommentThreadEl(el)); |
| }); |
| }); |
| |
| suite('compare comments', () => { |
| test('sort array of comments', () => { |
| const comments: GrDiffCommentThread[] = [ |
| {side: Side.RIGHT, line: 3}, |
| {side: Side.RIGHT, line: 2}, |
| {side: Side.RIGHT, line: 1}, |
| {side: Side.RIGHT, line: LOST}, |
| {side: Side.RIGHT, line: FILE}, |
| {side: Side.LEFT, line: 3}, |
| {side: Side.LEFT, line: 2}, |
| { |
| side: Side.LEFT, |
| line: 1, |
| rootId: 'b', |
| range: { |
| start_line: 1, |
| start_character: 0, |
| end_line: 5, |
| end_character: 14, |
| }, |
| }, |
| { |
| side: Side.LEFT, |
| line: 1, |
| rootId: 'b', |
| range: { |
| start_line: 1, |
| start_character: 0, |
| end_line: 2, |
| end_character: 4, |
| }, |
| }, |
| {side: Side.LEFT, line: 1, rootId: 'b'}, |
| {side: Side.LEFT, line: 1, rootId: 'a'}, |
| {side: Side.LEFT, line: 1}, |
| {side: Side.LEFT, line: LOST}, |
| ]; |
| const commentsOrdered: GrDiffCommentThread[] = [ |
| comments[12], |
| comments[11], |
| comments[10], |
| comments[9], |
| comments[8], |
| comments[7], |
| comments[6], |
| comments[5], |
| comments[4], |
| comments[3], |
| comments[2], |
| comments[1], |
| comments[0], |
| ]; |
| assert.sameOrderedMembers( |
| comments.sort(compareComments), |
| commentsOrdered |
| ); |
| }); |
| }); |
| }); |