diff --git a/app/assets/javascripts/diffs/components/diff_row.vue b/app/assets/javascripts/diffs/components/diff_row.vue index e5695c4390f165e4c080dc63f5f6e8d3c6fb9930..dfca6d61270805f1eb75fc7ba1b38a8d452e3362 100644 --- a/app/assets/javascripts/diffs/components/diff_row.vue +++ b/app/assets/javascripts/diffs/components/diff_row.vue @@ -60,6 +60,16 @@ export default { type: Boolean, required: true, }, + isFirstHighlightedLine: { + type: Boolean, + required: false, + default: false, + }, + isLastHighlightedLine: { + type: Boolean, + required: false, + default: false, + }, fileLineCoverage: { type: Function, required: true, @@ -81,12 +91,23 @@ export default { ), parallelViewLeftLineType: memoize( (props) => { - return utils.parallelViewLeftLineType(props.line, props.isHighlighted || props.isCommented); + return utils.parallelViewLeftLineType({ + line: props.line, + highlighted: props.isHighlighted, + commented: props.isCommented, + selectionStart: props.isFirstHighlightedLine, + selectionEnd: props.isLastHighlightedLine, + }); }, (props) => - [props.line.left?.type, props.line.right?.type, props.isHighlighted, props.isCommented].join( - ':', - ), + [ + props.line.left?.type, + props.line.right?.type, + props.isHighlighted, + props.isCommented, + props.isFirstHighlightedLine, + props.isLastHighlightedLine, + ].join(':'), ), coverageStateLeft: memoize( (props) => { @@ -118,20 +139,40 @@ export default { classNameMapCellLeft: memoize( (props) => { return utils.classNameMapCell({ - line: props.line.left, - hll: props.isHighlighted || props.isCommented, + line: props.line?.left, + highlighted: props.isHighlighted, + commented: props.isCommented, + selectionStart: props.isFirstHighlightedLine, + selectionEnd: props.isLastHighlightedLine, }); }, - (props) => [props.line.left.type, props.isHighlighted, props.isCommented].join(':'), + (props) => + [ + props.line?.left?.type, + props.isHighlighted, + props.isCommented, + props.isFirstHighlightedLine, + props.isLastHighlightedLine, + ].join(':'), ), classNameMapCellRight: memoize( (props) => { return utils.classNameMapCell({ - line: props.line.right, - hll: props.isHighlighted || props.isCommented, + line: props.line?.right, + highlighted: props.isHighlighted, + commented: props.isCommented, + selectionStart: props.isFirstHighlightedLine, + selectionEnd: props.isLastHighlightedLine, }); }, - (props) => [props.line.right.type, props.isHighlighted, props.isCommented].join(':'), + (props) => + [ + props.line?.right?.type, + props.isHighlighted, + props.isCommented, + props.isFirstHighlightedLine, + props.isLastHighlightedLine, + ].join(':'), ), shouldRenderCommentButton: memoize( (props) => { @@ -303,15 +344,24 @@ export default { !props.inline || (props.line.left && props.line.left.type === $options.CONFLICT_MARKER) " > -
+
 
-
-
-
+
+
@@ -390,13 +440,13 @@ export default { :class="[ props.line.right.type, $options.coverageStateRight(props).class, - { hll: props.isHighlighted, hll: props.isCommented }, + ...$options.classNameMapCellRight(props), ]" class="diff-td line-coverage right-side has-tooltip" >
-
-
-
+
+
+
+
diff --git a/app/assets/javascripts/diffs/components/diff_row_utils.js b/app/assets/javascripts/diffs/components/diff_row_utils.js index 479853caae3d62763968e4fc6fa2e7102f943d43..a489c96b0c9574f18f6e34a5f4972d19bdba08c2 100644 --- a/app/assets/javascripts/diffs/components/diff_row_utils.js +++ b/app/assets/javascripts/diffs/components/diff_row_utils.js @@ -40,19 +40,33 @@ export const lineCode = (line) => { return line.line_code || line.left?.line_code || line.right?.line_code; }; -export const classNameMapCell = ({ line, hll, isLoggedIn, isHover }) => { - if (!line) return []; - const { type } = line; +export const classNameMapCell = ({ + line, + highlighted, + commented, + selectionStart, + selectionEnd, + isLoggedIn, + isHover, +}) => { + const classes = { + 'highlight-top': highlighted || selectionStart, + 'highlight-bottom': highlighted || selectionEnd, + hll: highlighted, + commented, + }; - return [ - type, - { - hll, + if (line) { + const { type } = line; + Object.assign(classes, { + [type]: true, [LINE_HOVER_CLASS_NAME]: isLoggedIn && isHover && !isContextLine(type) && !isMetaLine(type), - old_line: line.type === 'old', - new_line: line.type === 'new', - }, - ]; + old_line: type === 'old', + new_line: type === 'new', + }); + } + + return [classes]; }; export const addCommentTooltip = (line) => { @@ -88,14 +102,28 @@ export const addCommentTooltip = (line) => { return tooltip; }; -export const parallelViewLeftLineType = (line, hll) => { +export const parallelViewLeftLineType = ({ + line, + highlighted, + commented, + selectionStart, + selectionEnd, +}) => { if (line?.right?.type === NEW_NO_NEW_LINE_TYPE) { return OLD_NO_NEW_LINE_TYPE; } const lineTypeClass = line?.left ? line.left.type : EMPTY_CELL_TYPE; - return [lineTypeClass, { hll }]; + return [ + lineTypeClass, + { + hll: highlighted, + commented, + 'highlight-top': highlighted || selectionStart, + 'highlight-bottom': highlighted || selectionEnd, + }, + ]; }; export const shouldShowCommentButton = (hover, context, meta, discussions) => { diff --git a/app/assets/javascripts/diffs/components/diff_view.vue b/app/assets/javascripts/diffs/components/diff_view.vue index aa9a17d18e3c998077120f11197bfa4a2169d97f..a2e052e0f939dd7777d5f9de63d7bd36cb698099 100644 --- a/app/assets/javascripts/diffs/components/diff_view.vue +++ b/app/assets/javascripts/diffs/components/diff_view.vue @@ -59,7 +59,12 @@ export default { }, computed: { ...mapGetters('diffs', ['commitId', 'fileLineCoverage']), - ...mapState('diffs', ['codequalityDiff', 'highlightedRow', 'coverageLoaded']), + ...mapState('diffs', [ + 'codequalityDiff', + 'highlightedRow', + 'coverageLoaded', + 'selectedCommentPosition', + ]), ...mapState({ selectedCommentPosition: ({ notes }) => notes.selectedCommentPosition, selectedCommentPositionHover: ({ notes }) => notes.selectedCommentPositionHover, @@ -144,6 +149,14 @@ export default { false, ); }, + isFirstHighlightedLine(line) { + const lineCode = line.left?.line_code || line.right?.line_code; + return lineCode && lineCode === this.selectedCommentPosition?.start.line_code; + }, + isLastHighlightedLine(line) { + const lineCode = line.left?.line_code || line.right?.line_code; + return lineCode && lineCode === this.selectedCommentPosition?.end.line_code; + }, handleParallelLineMouseDown(e) { const line = e.target.closest('.diff-td'); if (line) { @@ -230,10 +243,14 @@ export default { :line="line" :is-bottom="index + 1 === diffLinesLength" :is-commented="index >= commentedLines.startLine && index <= commentedLines.endLine" + :is-highlighted="isHighlighted(line)" + :is-first-highlighted-line=" + isFirstHighlightedLine(line) || index === commentedLines.startLine + " + :is-last-highlighted-line="isLastHighlightedLine(line) || index === commentedLines.endLine" :inline="inline" :index="index" :code-quality-expanded="codeQualityExpandedLines.includes(getCodeQualityLine(line))" - :is-highlighted="isHighlighted(line)" :file-line-coverage="fileLineCoverage" :coverage-loaded="coverageLoaded" @showCommentForm="(code) => singleLineComment(code, line)" diff --git a/app/assets/stylesheets/framework/diffs.scss b/app/assets/stylesheets/framework/diffs.scss index f7cd5d7e183792f01cb71e11ead3c88def915e8f..4eb26d533c2cb098c32ddd6807a78c744209c556 100644 --- a/app/assets/stylesheets/framework/diffs.scss +++ b/app/assets/stylesheets/framework/diffs.scss @@ -450,6 +450,16 @@ } } +.code .diff-grid-row.line_holder.diff-tr .diff-td.commented:not(.hll) { + --highlight-border-color: #{$blue-300}; + background-color: $blue-50; + + .gl-dark & { + --highlight-border-color: #{$blue-600}; + background-color: $blue-900; + } +} + .diff-table.code, table.code { width: 100%; @@ -461,6 +471,21 @@ table.code { table-layout: fixed; border-radius: 0 0 $border-radius-default $border-radius-default; + .diff-td.highlight-top { + box-shadow: 0 -1px var(--highlight-border-color, $blue-300); + z-index: 1; + } + + .diff-td.highlight-bottom { + box-shadow: 0 1px var(--highlight-border-color, $blue-300); + z-index: 1; + } + + .diff-td.highlight-top.highlight-bottom { + box-shadow: 0 -1px var(--highlight-border-color, $blue-300), 0 1px var(--highlight-border-color, $blue-300); + z-index: 2; + } + .diff-tr.line_holder .diff-td, tr.line_holder td { line-height: $code-line-height; @@ -485,13 +510,16 @@ table.code { user-select: none; margin: 0; padding: 0 10px 0 5px; - border-right-width: 1px; - border-right-style: solid; text-align: right; width: 50px; position: relative; white-space: nowrap; + &:nth-of-type(2) { + border-right-width: 1px; + border-right-style: solid; + } + a { transition: none; float: left; diff --git a/app/assets/stylesheets/highlight/common.scss b/app/assets/stylesheets/highlight/common.scss index 96df8487c0efd417555251d4560abfb9ac66b825..085e25a0cdc9e3708bd068e2a25a828aa736e90c 100644 --- a/app/assets/stylesheets/highlight/common.scss +++ b/app/assets/stylesheets/highlight/common.scss @@ -57,6 +57,15 @@ } } +@mixin line-number-hover-dark { + background-color: $purple-800; + border-color: $purple-300; + + a { + color: $purple-50; + } +} + @mixin conflict-colors($theme) { .diff-line-num { &.conflict_marker_our, @@ -75,6 +84,8 @@ .line_holder { .line_content, .line-coverage { + position: relative; + &.conflict_marker_our { background-color: map-get($conflict-colors, #{$theme}-header-head-neutral); border-color: map-get($conflict-colors, #{$theme}-header-head-neutral); diff --git a/app/assets/stylesheets/highlight/themes/dark.scss b/app/assets/stylesheets/highlight/themes/dark.scss index 3438a73eff67bf68507d1f26b5970b1b585c0ab9..02469cf5165d377c1351cd957d9106bf55a3c875 100644 --- a/app/assets/stylesheets/highlight/themes/dark.scss +++ b/app/assets/stylesheets/highlight/themes/dark.scss @@ -131,7 +131,7 @@ $dark-il: #de935f; @include hljs-override('title\\.class\\.inherited', $dark-no); @include hljs-override('variable\\.constant', $dark-no); @include hljs-override('title\\.function', $dark-nf); - + // Line numbers .file-line-num { @@ -174,6 +174,11 @@ $dark-il: #de935f; @include diff-expansion($gray-600, $gray-200, $gray-300, $white); } + .diff-grid-row.line_holder.diff-tr .diff-td.commented:not(.hll) { + --highlight-border-color: #{$blue-600}; + background-color: $blue-900; + } + // Diff line .line_holder { &.match .line_content, @@ -188,15 +193,15 @@ $dark-il: #de935f; @include dark-diff-expansion-line; } - .diff-td.diff-line-num.hll:not(.empty-cell), - .diff-td.line-coverage.hll:not(.empty-cell), - .diff-td.line-codequality.hll:not(.empty-cell), - .diff-td.line_content.hll:not(.empty-cell), - td.diff-line-num.hll:not(.empty-cell), - td.line-coverage.hll:not(.empty-cell), - td.line_content.hll:not(.empty-cell) { - background-color: $dark-diff-not-empty-bg; - border-color: darken($dark-diff-not-empty-bg, 15%); + .diff-td.diff-line-num.hll, + .diff-td.line-coverage.hll, + .diff-td.line-codequality.hll, + .diff-td.line_content.hll, + td.diff-line-num.hll, + td.line-coverage.hll, + td.line_content.hll { + --highlight-border-color: #{$orange-500}; + background-color: $orange-800; } .line-coverage { @@ -239,14 +244,14 @@ $dark-il: #de935f; &:not(.match) .diff-grid-right:hover, &.code-search-line:hover { .diff-line-num:not(.empty-cell) { - @include line-number-hover; + @include line-number-hover-dark; } } .diff-line-num { &.is-over, &.hll:not(.empty-cell).is-over { - @include line-number-hover; + @include line-number-hover-dark; } } diff --git a/app/assets/stylesheets/highlight/themes/monokai.scss b/app/assets/stylesheets/highlight/themes/monokai.scss index 02aa9ed6b16ab34de35dcc3417e030aec2c4ab96..30d04b4002eff59bf74ce7032142da85e14c0dd6 100644 --- a/app/assets/stylesheets/highlight/themes/monokai.scss +++ b/app/assets/stylesheets/highlight/themes/monokai.scss @@ -148,6 +148,11 @@ $monokai-gh: #75715e; color: $monokai-line-num-color; } + .diff-grid-row.line_holder.diff-tr .diff-td.commented:not(.hll) { + --highlight-border-color: #{$blue-600}; + background-color: $blue-900; + } + // Code itself pre.code, .diff-line-num { @@ -179,15 +184,15 @@ $monokai-gh: #75715e; @include dark-diff-expansion-line; } - .diff-td.diff-line-num.hll:not(.empty-cell), - .diff-td.line-coverage.hll:not(.empty-cell), - .diff-td.line-codequality.hll:not(.empty-cell), - .diff-td.line_content.hll:not(.empty-cell), - td.diff-line-num.hll:not(.empty-cell), - td.line-coverage.hll:not(.empty-cell), - td.line_content.hll:not(.empty-cell) { - background-color: $monokai-line-empty-bg; - border-color: $monokai-line-empty-border; + .diff-td.diff-line-num.hll, + .diff-td.line-coverage.hll, + .diff-td.line-codequality.hll, + .diff-td.line_content.hll, + td.diff-line-num.hll, + td.line-coverage.hll, + td.line_content.hll { + --highlight-border-color: #{$orange-500}; + background-color: $orange-800; } .line-coverage { @@ -230,14 +235,14 @@ $monokai-gh: #75715e; &:not(.match) .diff-grid-right:hover, &.code-search-line:hover { .diff-line-num:not(.empty-cell) { - @include line-number-hover; + @include line-number-hover-dark; } } .diff-line-num { &.is-over, &.hll:not(.empty-cell).is-over { - @include line-number-hover; + @include line-number-hover-dark; } } diff --git a/app/assets/stylesheets/highlight/themes/none.scss b/app/assets/stylesheets/highlight/themes/none.scss index fa1f7211b3ea2ffad5c08db964c3323185fb0ee0..8339d7eff80a822c95ad3b91c8d083047d18cbbf 100644 --- a/app/assets/stylesheets/highlight/themes/none.scss +++ b/app/assets/stylesheets/highlight/themes/none.scss @@ -55,7 +55,7 @@ &, pre.code, - .line_holder .line_content { + .line_holder .line_content:not(.hll) { background-color: $white; color: $gl-text-color; } @@ -84,8 +84,8 @@ @include line-coverage-border-color($green-500, $orange-500); } - .line-coverage, - .line-codequality { + .line-coverage:not(.hll), + .line-codequality:not(.hll) { &.old, &.new, &.new-nomappinginraw, @@ -119,11 +119,6 @@ &.hll:not(.empty-cell).is-over { @include line-number-hover; } - - &.hll:not(.empty-cell) { - background-color: $white; - border-color: $white-normal; - } } &:not(.diff-expanded) + .diff-expanded, @@ -158,7 +153,7 @@ } } - &.new, &.new-nomappinginraw { + &.new:not(.hll), &.new-nomappinginraw:not(.hll) { background-color: $white-normal; &::before { @@ -174,18 +169,9 @@ &.match { @include match-line; } - - &.hll:not(.empty-cell) { - background-color: $white-normal; - } } } - // highlight line via anchor - pre .hll { - background-color: $white-normal; - } - // Search result highlight span.highlight_word { background-color: $white-normal; @@ -197,7 +183,10 @@ text-decoration: underline; } - .hll { background-color: $white; } + .hll { + --highlight-border-color: #{$orange-200}; + background-color: $orange-50; + } .gd { color: $gl-text-color; diff --git a/app/assets/stylesheets/highlight/themes/solarized-dark.scss b/app/assets/stylesheets/highlight/themes/solarized-dark.scss index 18e18619c096860edc1347be493637c7b40dcb0f..075510e6e5f0f21c5b2832adab65d89417da71af 100644 --- a/app/assets/stylesheets/highlight/themes/solarized-dark.scss +++ b/app/assets/stylesheets/highlight/themes/solarized-dark.scss @@ -151,6 +151,11 @@ $solarized-dark-il: #2aa198; color: $solarized-dark-line-color; } + .diff-grid-row.line_holder.diff-tr .diff-td.commented:not(.hll) { + --highlight-border-color: #{$blue-600}; + background-color: $blue-900; + } + // Code itself pre.code, .diff-line-num { @@ -182,15 +187,15 @@ $solarized-dark-il: #2aa198; @include dark-diff-expansion-line; } - .diff-td.diff-line-num.hll:not(.empty-cell), - .diff-td.line-coverage.hll:not(.empty-cell), - .diff-td.line-codequality.hll:not(.empty-cell), - .diff-td.line_content.hll:not(.empty-cell), - td.diff-line-num.hll:not(.empty-cell), - td.line-coverage.hll:not(.empty-cell), - td.line_content.hll:not(.empty-cell) { - background-color: $solarized-dark-hll-bg; - border-color: darken($solarized-dark-hll-bg, 15%); + .diff-td.diff-line-num.hll, + .diff-td.line-coverage.hll, + .diff-td.line-codequality.hll, + .diff-td.line_content.hll, + td.diff-line-num.hll, + td.line-coverage.hll, + td.line_content.hll { + --highlight-border-color: #{$orange-500}; + background-color: $orange-800; } .line-coverage { @@ -201,7 +206,7 @@ $solarized-dark-il: #2aa198; &:not(.match) .diff-grid-right:hover, &.code-search-line:hover { .diff-line-num:not(.empty-cell) { - @include line-number-hover; + @include line-number-hover-dark; } } @@ -240,7 +245,7 @@ $solarized-dark-il: #2aa198; .diff-line-num { &.is-over, &.hll:not(.empty-cell).is-over { - @include line-number-hover; + @include line-number-hover-dark; } } diff --git a/app/assets/stylesheets/highlight/themes/solarized-light.scss b/app/assets/stylesheets/highlight/themes/solarized-light.scss index d2dea02440801df8b5375804cfaba29f04a99186..4e244ed7420b3e8c5a87dda82d09c9e86dc2c149 100644 --- a/app/assets/stylesheets/highlight/themes/solarized-light.scss +++ b/app/assets/stylesheets/highlight/themes/solarized-light.scss @@ -116,7 +116,7 @@ $solarized-light-il: #2aa198; @include hljs-override('variable\\.constant', $solarized-light-no); @include hljs-override('variable\\.language', $solarized-light-nb); @include hljs-override('params', $solarized-light-nb); - + // Line numbers .file-line-num { @include line-link($black, 'link'); @@ -174,13 +174,13 @@ $solarized-light-il: #2aa198; background-color: $solarized-light-matchline-bg; } - .diff-td.diff-line-num.hll:not(.empty-cell), - .diff-td.line-coverage.hll:not(.empty-cell), - .diff-td.line-codequality.hll:not(.empty-cell), - .diff-td.line_content.hll:not(.empty-cell), - td.diff-line-num.hll:not(.empty-cell), - td.line-coverage.hll:not(.empty-cell), - td.line_content.hll:not(.empty-cell) { + .diff-td.diff-line-num.hll, + .diff-td.line-coverage.hll, + .diff-td.line-codequality.hll, + .diff-td.line_content.hll, + td.diff-line-num.hll, + td.line-coverage.hll, + td.line_content.hll { background-color: $solarized-light-hll-bg; border-color: darken($solarized-light-hll-bg, 15%); } diff --git a/app/assets/stylesheets/highlight/themes/white.scss b/app/assets/stylesheets/highlight/themes/white.scss index f6cce25671fdc25db6669275427b4304b942c493..0a283254a4c0b17a26d801c632934120ffb000ab 100644 --- a/app/assets/stylesheets/highlight/themes/white.scss +++ b/app/assets/stylesheets/highlight/themes/white.scss @@ -19,4 +19,4 @@ :root { --default-diff-color-deletion: #eb919b; --default-diff-color-addition: #a0f5b4; -} \ No newline at end of file +} diff --git a/app/assets/stylesheets/highlight/white_base.scss b/app/assets/stylesheets/highlight/white_base.scss index 816aa88cfdedd8347bb67d86da75efd4de5d9364..ccb5d96e966cebb3fcc9839081074ff06c3afb49 100644 --- a/app/assets/stylesheets/highlight/white_base.scss +++ b/app/assets/stylesheets/highlight/white_base.scss @@ -125,13 +125,13 @@ $white-gc-bg: #eaf2f5; .diff-line-num, .diff-line-num a { - color: $black-transparent; + color: $gray-400; } // Code itself pre.code, .diff-line-num { - border-color: $white-normal; + border-color: rgba(0, 0, 0, 0.1); } &, @@ -173,7 +173,7 @@ pre.code, background-color: $line-number-old; a { - color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%); + color: scale-color($gray-300, $red: -30%, $green: -30%, $blue: -30%); } } @@ -182,7 +182,7 @@ pre.code, background-color: $line-number-new; a { - color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%); + color: scale-color($gray-200, $red: -30%, $green: -30%, $blue: -30%); } } @@ -191,9 +191,9 @@ pre.code, @include line-number-hover; } - &.hll:not(.empty-cell) { - background-color: $line-number-select; - border-color: $line-select-yellow-dark; + &.hll { + --highlight-border-color: #{$orange-200}; + background-color: $orange-50; } } @@ -246,8 +246,9 @@ pre.code, @include match-line; } - &.hll:not(.empty-cell) { - background-color: $line-select-yellow; + &.hll { + --highlight-border-color: #{$orange-200}; + background-color: $orange-50; } } @@ -267,8 +268,9 @@ pre.code, background-color: $line-added; } - &.hll:not(.empty-cell) { - background-color: $line-select-yellow; + &.hll { + --highlight-border-color: #{$orange-200}; + background-color: $orange-50; } } } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index c6fa1d50997ad9101f780548a75d85a70966615b..5d03281a30a7e5d667248568f8f05340160aed2b 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -671,6 +671,7 @@ $system-note-svg-size: 1rem; } .discussion-reply-holder { + border-top: 0; border-radius: 0 0 $border-radius-default $border-radius-default; position: relative; diff --git a/spec/frontend/diffs/components/diff_row_utils_spec.js b/spec/frontend/diffs/components/diff_row_utils_spec.js index a6f508c73ebb115bf241d8791d8b3d617cf2dd16..6e9eb433924a661d842437d7e40794e120d6c038 100644 --- a/spec/frontend/diffs/components/diff_row_utils_spec.js +++ b/spec/frontend/diffs/components/diff_row_utils_spec.js @@ -21,262 +21,287 @@ function problemsClone({ }; } -describe('isHighlighted', () => { - it('should return true if line is highlighted', () => { - const line = { line_code: LINE_CODE }; - const isCommented = false; - expect(utils.isHighlighted(LINE_CODE, line, isCommented)).toBe(true); - }); - - it('should return false if line is not highlighted', () => { - const line = { line_code: LINE_CODE }; - const isCommented = false; - expect(utils.isHighlighted('xxx', line, isCommented)).toBe(false); - }); - - it('should return true if isCommented is true', () => { - const line = { line_code: LINE_CODE }; - const isCommented = true; - expect(utils.isHighlighted('xxx', line, isCommented)).toBe(true); - }); -}); - -describe('isContextLine', () => { - it('return true if line type is context', () => { - expect(utils.isContextLine(CONTEXT_LINE_TYPE)).toBe(true); - }); - - it('return false if line type is not context', () => { - expect(utils.isContextLine('xxx')).toBe(false); - }); -}); - -describe('isMatchLine', () => { - it('return true if line type is match', () => { - expect(utils.isMatchLine(MATCH_LINE_TYPE)).toBe(true); - }); - - it('return false if line type is not match', () => { - expect(utils.isMatchLine('xxx')).toBe(false); - }); -}); - -describe('isMetaLine', () => { - it.each` - type | expectation - ${OLD_NO_NEW_LINE_TYPE} | ${true} - ${NEW_NO_NEW_LINE_TYPE} | ${true} - ${EMPTY_CELL_TYPE} | ${true} - ${'xxx'} | ${false} - `('should return $expectation if type is $type', ({ type, expectation }) => { - expect(utils.isMetaLine(type)).toBe(expectation); - }); -}); - -describe('shouldRenderCommentButton', () => { - it('should return false if comment button is not rendered', () => { - expect(utils.shouldRenderCommentButton(true, false)).toBe(false); - }); - - it('should return false if not logged in', () => { - expect(utils.shouldRenderCommentButton(false, true)).toBe(false); - }); - - it('should return true logged in and rendered', () => { - expect(utils.shouldRenderCommentButton(true, true)).toBe(true); - }); -}); - -describe('hasDiscussions', () => { - it('should return false if line is undefined', () => { - expect(utils.hasDiscussions()).toBe(false); - }); - - it('should return false if discussions is undefined', () => { - expect(utils.hasDiscussions({})).toBe(false); - }); - - it('should return false if discussions has legnth of 0', () => { - expect(utils.hasDiscussions({ discussions: [] })).toBe(false); - }); +describe('diff_row_utils', () => { + describe('isHighlighted', () => { + it('should return true if line is highlighted', () => { + const line = { line_code: LINE_CODE }; + const isCommented = false; + expect(utils.isHighlighted(LINE_CODE, line, isCommented)).toBe(true); + }); + + it('should return false if line is not highlighted', () => { + const line = { line_code: LINE_CODE }; + const isCommented = false; + expect(utils.isHighlighted('xxx', line, isCommented)).toBe(false); + }); - it('should return true if discussions has legnth > 0', () => { - expect(utils.hasDiscussions({ discussions: [1] })).toBe(true); - }); -}); - -describe('lineHref', () => { - it(`should return #${LINE_CODE}`, () => { - expect(utils.lineHref({ line_code: LINE_CODE })).toEqual(`#${LINE_CODE}`); - }); - - it(`should return '#' if line is undefined`, () => { - expect(utils.lineHref()).toEqual('#'); - }); - - it(`should return '#' if line_code is undefined`, () => { - expect(utils.lineHref({})).toEqual('#'); - }); -}); - -describe('lineCode', () => { - it(`should return undefined if line_code is undefined`, () => { - expect(utils.lineCode()).toEqual(undefined); - expect(utils.lineCode({ left: {} })).toEqual(undefined); - expect(utils.lineCode({ right: {} })).toEqual(undefined); - }); - - it(`should return ${LINE_CODE}`, () => { - expect(utils.lineCode({ line_code: LINE_CODE })).toEqual(LINE_CODE); - expect(utils.lineCode({ left: { line_code: LINE_CODE } })).toEqual(LINE_CODE); - expect(utils.lineCode({ right: { line_code: LINE_CODE } })).toEqual(LINE_CODE); - }); -}); - -describe('classNameMapCell', () => { - it.each` - line | hll | isLoggedIn | isHover | expectation - ${undefined} | ${true} | ${true} | ${true} | ${[]} - ${{ type: 'new' }} | ${false} | ${false} | ${false} | ${['new', { hll: false, 'is-over': false, new_line: true, old_line: false }]} - ${{ type: 'new' }} | ${true} | ${true} | ${false} | ${['new', { hll: true, 'is-over': false, new_line: true, old_line: false }]} - ${{ type: 'new' }} | ${true} | ${false} | ${true} | ${['new', { hll: true, 'is-over': false, new_line: true, old_line: false }]} - ${{ type: 'new' }} | ${true} | ${true} | ${true} | ${['new', { hll: true, 'is-over': true, new_line: true, old_line: false }]} - `('should return $expectation', ({ line, hll, isLoggedIn, isHover, expectation }) => { - const classes = utils.classNameMapCell({ line, hll, isLoggedIn, isHover }); - expect(classes).toEqual(expectation); - }); -}); - -describe('addCommentTooltip', () => { - const brokenSymLinkTooltip = - 'Commenting on symbolic links that replace or are replaced by files is not supported'; - const brokenRealTooltip = - 'Commenting on files that replace or are replaced by symbolic links is not supported'; - const lineMovedOrRenamedFileTooltip = - 'Commenting on files that are only moved or renamed is not supported'; - const lineWithNoLineCodeTooltip = 'Commenting on this line is not supported'; - const dragTooltip = 'Add a comment to this line or drag for multiple lines'; - - it('should return default tooltip', () => { - expect(utils.addCommentTooltip()).toBeUndefined(); - }); - - it('should return drag comment tooltip when dragging is enabled', () => { - expect(utils.addCommentTooltip({ problems: problemsClone() })).toEqual(dragTooltip); - }); - - it('should return broken symlink tooltip', () => { - expect( - utils.addCommentTooltip({ - problems: problemsClone({ brokenSymlink: { wasSymbolic: true } }), - }), - ).toEqual(brokenSymLinkTooltip); - expect( - utils.addCommentTooltip({ problems: problemsClone({ brokenSymlink: { isSymbolic: true } }) }), - ).toEqual(brokenSymLinkTooltip); - }); - - it('should return broken real tooltip', () => { - expect( - utils.addCommentTooltip({ problems: problemsClone({ brokenSymlink: { wasReal: true } }) }), - ).toEqual(brokenRealTooltip); - expect( - utils.addCommentTooltip({ problems: problemsClone({ brokenSymlink: { isReal: true } }) }), - ).toEqual(brokenRealTooltip); - }); - - it('reports a tooltip when the line is in a file that has only been moved or renamed', () => { - expect(utils.addCommentTooltip({ problems: problemsClone({ fileOnlyMoved: true }) })).toEqual( - lineMovedOrRenamedFileTooltip, + it('should return true if isCommented is true', () => { + const line = { line_code: LINE_CODE }; + const isCommented = true; + expect(utils.isHighlighted('xxx', line, isCommented)).toBe(true); + }); + }); + + describe('isContextLine', () => { + it('return true if line type is context', () => { + expect(utils.isContextLine(CONTEXT_LINE_TYPE)).toBe(true); + }); + + it('return false if line type is not context', () => { + expect(utils.isContextLine('xxx')).toBe(false); + }); + }); + + describe('isMatchLine', () => { + it('return true if line type is match', () => { + expect(utils.isMatchLine(MATCH_LINE_TYPE)).toBe(true); + }); + + it('return false if line type is not match', () => { + expect(utils.isMatchLine('xxx')).toBe(false); + }); + }); + + describe('isMetaLine', () => { + it.each` + type | expectation + ${OLD_NO_NEW_LINE_TYPE} | ${true} + ${NEW_NO_NEW_LINE_TYPE} | ${true} + ${EMPTY_CELL_TYPE} | ${true} + ${'xxx'} | ${false} + `('should return $expectation if type is $type', ({ type, expectation }) => { + expect(utils.isMetaLine(type)).toBe(expectation); + }); + }); + + describe('shouldRenderCommentButton', () => { + it('should return false if comment button is not rendered', () => { + expect(utils.shouldRenderCommentButton(true, false)).toBe(false); + }); + + it('should return false if not logged in', () => { + expect(utils.shouldRenderCommentButton(false, true)).toBe(false); + }); + + it('should return true logged in and rendered', () => { + expect(utils.shouldRenderCommentButton(true, true)).toBe(true); + }); + }); + + describe('hasDiscussions', () => { + it('should return false if line is undefined', () => { + expect(utils.hasDiscussions()).toBe(false); + }); + + it('should return false if discussions is undefined', () => { + expect(utils.hasDiscussions({})).toBe(false); + }); + + it('should return false if discussions has legnth of 0', () => { + expect(utils.hasDiscussions({ discussions: [] })).toBe(false); + }); + + it('should return true if discussions has legnth > 0', () => { + expect(utils.hasDiscussions({ discussions: [1] })).toBe(true); + }); + }); + + describe('lineHref', () => { + it(`should return #${LINE_CODE}`, () => { + expect(utils.lineHref({ line_code: LINE_CODE })).toEqual(`#${LINE_CODE}`); + }); + + it(`should return '#' if line is undefined`, () => { + expect(utils.lineHref()).toEqual('#'); + }); + + it(`should return '#' if line_code is undefined`, () => { + expect(utils.lineHref({})).toEqual('#'); + }); + }); + + describe('lineCode', () => { + it(`should return undefined if line_code is undefined`, () => { + expect(utils.lineCode()).toEqual(undefined); + expect(utils.lineCode({ left: {} })).toEqual(undefined); + expect(utils.lineCode({ right: {} })).toEqual(undefined); + }); + + it(`should return ${LINE_CODE}`, () => { + expect(utils.lineCode({ line_code: LINE_CODE })).toEqual(LINE_CODE); + expect(utils.lineCode({ left: { line_code: LINE_CODE } })).toEqual(LINE_CODE); + expect(utils.lineCode({ right: { line_code: LINE_CODE } })).toEqual(LINE_CODE); + }); + }); + + describe('classNameMapCell', () => { + it.each` + line | highlighted | commented | selectionStart | selectionEnd | isLoggedIn | isHover | expectation + ${undefined} | ${true} | ${false} | ${false} | ${false} | ${true} | ${true} | ${[{ 'highlight-top': true, 'highlight-bottom': true, hll: true, commented: false }]} + ${undefined} | ${false} | ${true} | ${false} | ${false} | ${true} | ${true} | ${[{ 'highlight-top': false, 'highlight-bottom': false, hll: false, commented: true }]} + ${{ type: 'new' }} | ${false} | ${false} | ${false} | ${false} | ${false} | ${false} | ${[{ new: true, 'highlight-top': false, 'highlight-bottom': false, hll: false, commented: false, 'is-over': false, new_line: true, old_line: false }]} + ${{ type: 'new' }} | ${true} | ${false} | ${false} | ${false} | ${true} | ${false} | ${[{ new: true, 'highlight-top': true, 'highlight-bottom': true, hll: true, commented: false, 'is-over': false, new_line: true, old_line: false }]} + ${{ type: 'new' }} | ${true} | ${false} | ${false} | ${false} | ${false} | ${true} | ${[{ new: true, 'highlight-top': true, 'highlight-bottom': true, hll: true, commented: false, 'is-over': false, new_line: true, old_line: false }]} + ${{ type: 'new' }} | ${true} | ${false} | ${false} | ${false} | ${true} | ${true} | ${[{ new: true, 'highlight-top': true, 'highlight-bottom': true, hll: true, commented: false, 'is-over': true, new_line: true, old_line: false }]} + `( + 'should return $expectation', + ({ + line, + highlighted, + commented, + selectionStart, + selectionEnd, + isLoggedIn, + isHover, + expectation, + }) => { + const classes = utils.classNameMapCell({ + line, + highlighted, + commented, + selectionStart, + selectionEnd, + isLoggedIn, + isHover, + }); + expect(classes).toEqual(expectation); + }, ); }); - it("reports a tooltip when the line doesn't have a line code to leave a comment on", () => { - expect(utils.addCommentTooltip({ problems: problemsClone({ brokenLineCode: true }) })).toEqual( - lineWithNoLineCodeTooltip, + describe('addCommentTooltip', () => { + const brokenSymLinkTooltip = + 'Commenting on symbolic links that replace or are replaced by files is not supported'; + const brokenRealTooltip = + 'Commenting on files that replace or are replaced by symbolic links is not supported'; + const lineMovedOrRenamedFileTooltip = + 'Commenting on files that are only moved or renamed is not supported'; + const lineWithNoLineCodeTooltip = 'Commenting on this line is not supported'; + const dragTooltip = 'Add a comment to this line or drag for multiple lines'; + + it('should return default tooltip', () => { + expect(utils.addCommentTooltip()).toBeUndefined(); + }); + + it('should return drag comment tooltip when dragging is enabled', () => { + expect(utils.addCommentTooltip({ problems: problemsClone() })).toEqual(dragTooltip); + }); + + it('should return broken symlink tooltip', () => { + expect( + utils.addCommentTooltip({ + problems: problemsClone({ brokenSymlink: { wasSymbolic: true } }), + }), + ).toEqual(brokenSymLinkTooltip); + expect( + utils.addCommentTooltip({ + problems: problemsClone({ brokenSymlink: { isSymbolic: true } }), + }), + ).toEqual(brokenSymLinkTooltip); + }); + + it('should return broken real tooltip', () => { + expect( + utils.addCommentTooltip({ problems: problemsClone({ brokenSymlink: { wasReal: true } }) }), + ).toEqual(brokenRealTooltip); + expect( + utils.addCommentTooltip({ problems: problemsClone({ brokenSymlink: { isReal: true } }) }), + ).toEqual(brokenRealTooltip); + }); + + it('reports a tooltip when the line is in a file that has only been moved or renamed', () => { + expect(utils.addCommentTooltip({ problems: problemsClone({ fileOnlyMoved: true }) })).toEqual( + lineMovedOrRenamedFileTooltip, + ); + }); + + it("reports a tooltip when the line doesn't have a line code to leave a comment on", () => { + expect( + utils.addCommentTooltip({ problems: problemsClone({ brokenLineCode: true }) }), + ).toEqual(lineWithNoLineCodeTooltip); + }); + }); + + describe('parallelViewLeftLineType', () => { + it(`should return ${OLD_NO_NEW_LINE_TYPE}`, () => { + expect( + utils.parallelViewLeftLineType({ line: { right: { type: NEW_NO_NEW_LINE_TYPE } } }), + ).toEqual(OLD_NO_NEW_LINE_TYPE); + }); + + it(`should return 'new'`, () => { + expect(utils.parallelViewLeftLineType({ line: { left: { type: 'new' } } })[0]).toBe('new'); + }); + + it(`should return ${EMPTY_CELL_TYPE}`, () => { + expect(utils.parallelViewLeftLineType({})).toContain(EMPTY_CELL_TYPE); + }); + + it(`should return hll:true`, () => { + expect(utils.parallelViewLeftLineType({ highlighted: true })[1].hll).toBe(true); + }); + }); + + describe('shouldShowCommentButton', () => { + it.each` + hover | context | meta | discussions | expectation + ${true} | ${false} | ${false} | ${false} | ${true} + ${false} | ${false} | ${false} | ${false} | ${false} + ${true} | ${true} | ${false} | ${false} | ${false} + ${true} | ${true} | ${true} | ${false} | ${false} + ${true} | ${true} | ${true} | ${true} | ${false} + `( + 'should return $expectation when hover is $hover', + ({ hover, context, meta, discussions, expectation }) => { + expect(utils.shouldShowCommentButton(hover, context, meta, discussions)).toBe(expectation); + }, ); }); -}); - -describe('parallelViewLeftLineType', () => { - it(`should return ${OLD_NO_NEW_LINE_TYPE}`, () => { - expect(utils.parallelViewLeftLineType({ right: { type: NEW_NO_NEW_LINE_TYPE } })).toEqual( - OLD_NO_NEW_LINE_TYPE, - ); - }); - - it(`should return 'new'`, () => { - expect(utils.parallelViewLeftLineType({ left: { type: 'new' } })).toContain('new'); - }); - - it(`should return ${EMPTY_CELL_TYPE}`, () => { - expect(utils.parallelViewLeftLineType({})).toContain(EMPTY_CELL_TYPE); - }); - - it(`should return hll:true`, () => { - expect(utils.parallelViewLeftLineType({}, true)[1]).toEqual({ hll: true }); - }); -}); - -describe('shouldShowCommentButton', () => { - it.each` - hover | context | meta | discussions | expectation - ${true} | ${false} | ${false} | ${false} | ${true} - ${false} | ${false} | ${false} | ${false} | ${false} - ${true} | ${true} | ${false} | ${false} | ${false} - ${true} | ${true} | ${true} | ${false} | ${false} - ${true} | ${true} | ${true} | ${true} | ${false} - `( - 'should return $expectation when hover is $hover', - ({ hover, context, meta, discussions, expectation }) => { - expect(utils.shouldShowCommentButton(hover, context, meta, discussions)).toBe(expectation); - }, - ); -}); -describe('mapParallel', () => { - it('should assign computed properties to the line object', () => { - const side = { - discussions: [{}], - discussionsExpanded: true, - hasForm: true, - problems: problemsClone(), - }; - const content = { - diffFile: {}, - hasParallelDraftLeft: () => false, - hasParallelDraftRight: () => false, - draftsForLine: () => [], - }; - const line = { left: side, right: side }; - const expectation = { - commentRowClasses: '', - draftRowClasses: 'js-temp-notes-holder', - hasDiscussionsLeft: true, - hasDiscussionsRight: true, - isContextLineLeft: false, - isContextLineRight: false, - isMatchLineLeft: false, - isMatchLineRight: false, - isMetaLineLeft: false, - isMetaLineRight: false, - }; - const leftExpectation = { - renderDiscussion: true, - hasDraft: false, - lineDrafts: [], - hasCommentForm: true, - }; - const rightExpectation = { - renderDiscussion: false, - hasDraft: false, - lineDrafts: [], - hasCommentForm: false, - }; - const mapped = utils.mapParallel(content)(line); - - expect(mapped).toMatchObject(expectation); - expect(mapped.left).toMatchObject(leftExpectation); - expect(mapped.right).toMatchObject(rightExpectation); + describe('mapParallel', () => { + it('should assign computed properties to the line object', () => { + const side = { + discussions: [{}], + discussionsExpanded: true, + hasForm: true, + problems: problemsClone(), + }; + const content = { + diffFile: {}, + hasParallelDraftLeft: () => false, + hasParallelDraftRight: () => false, + draftsForLine: () => [], + }; + const line = { left: side, right: side }; + const expectation = { + commentRowClasses: '', + draftRowClasses: 'js-temp-notes-holder', + hasDiscussionsLeft: true, + hasDiscussionsRight: true, + isContextLineLeft: false, + isContextLineRight: false, + isMatchLineLeft: false, + isMatchLineRight: false, + isMetaLineLeft: false, + isMetaLineRight: false, + }; + const leftExpectation = { + renderDiscussion: true, + hasDraft: false, + lineDrafts: [], + hasCommentForm: true, + }; + const rightExpectation = { + renderDiscussion: false, + hasDraft: false, + lineDrafts: [], + hasCommentForm: false, + }; + const mapped = utils.mapParallel(content)(line); + + expect(mapped).toMatchObject(expectation); + expect(mapped.left).toMatchObject(leftExpectation); + expect(mapped.right).toMatchObject(rightExpectation); + }); }); });