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);
+ });
});
});