Commit 90aa16bf authored by Robert Speicher's avatar Robert Speicher

Merge branch 'ce-to-ee-2018-09-17' into 'master'

CE upstream - 2018-09-17 18:21 UTC

Closes gitlab-ce#51307 and gitlab-ce#51585

See merge request gitlab-org/gitlab-ee!7392
parents da20ea6a 981ba649
......@@ -36,7 +36,7 @@ _This notice should stay as the first item in the CONTRIBUTING.md file._
- [Release Scoping labels](#release-scoping-labels)
- [Priority labels](#priority-labels)
- [Severity labels](#severity-labels)
- [Severity impact guidance](#severity-impact-guidance)
- [Severity impact guidance](#severity-impact-guidance)
- [Label for community contributors](#label-for-community-contributors)
- [Implement design & UI elements](#implement-design--ui-elements)
- [Issue tracker](#issue-tracker)
......@@ -70,7 +70,7 @@ to contribute to GitLab in a way that is easy for everyone.
For a first-time step-by-step guide to the contribution process, please see
["Contributing to GitLab"](https://about.gitlab.com/contributing/).
Looking for something to work on? Look for issues with the label [Accepting Merge Requests](#i-want-to-contribute).
Looking for something to work on? Look for issues in the [Backlog (Accepting merge requests) milestone](#i-want-to-contribute).
GitLab comes into two flavors, GitLab Community Edition (CE) our free and open
source edition, and GitLab Enterprise Edition (EE) which is our commercial
......@@ -151,8 +151,8 @@ the remaining issues on the GitHub issue tracker.
## I want to contribute!
If you want to contribute to GitLab [issues with the label `Accepting Merge Requests` and small weight][accepting-mrs-weight]
is a great place to start. Issues with a lower weight (1 or 2) are deemed
If you want to contribute to GitLab, [issues in the `Backlog (Accepting merge requests)` milestone with small weight][https://gitlab.com/gitlab-org/gitlab-ce/issues?scope=all&utf8=✓&state=opened&assignee_id=0&milestone_title=Backlog%20(Accepting%20merge%20requests)]
are a great place to start. Issues with a lower weight (1 or 2) are deemed
suitable for beginners. These issues will be of reasonable size and challenge,
for anyone to start contributing to GitLab. If you have any questions or need help visit [Getting Help](https://about.gitlab.com/getting-help/#discussion) to
learn how to communicate with GitLab. If you're looking for a Gitter or Slack channel
......
<script>
import { mapGetters } from 'vuex';
import { n__, __, sprintf } from '~/locale';
import tooltip from '~/vue_shared/directives/tooltip';
import Icon from '~/vue_shared/components/icon.vue';
import NewDropdown from './new_dropdown/index.vue';
import ChangedFileIcon from './changed_file_icon.vue';
import MrFileIcon from './mr_file_icon.vue';
export default {
name: 'FileRowExtra',
directives: {
tooltip,
},
components: {
Icon,
NewDropdown,
ChangedFileIcon,
MrFileIcon,
},
props: {
file: {
type: Object,
required: true,
},
mouseOver: {
type: Boolean,
required: true,
},
},
computed: {
...mapGetters([
'getChangesInFolder',
'getUnstagedFilesCountForPath',
'getStagedFilesCountForPath',
]),
folderUnstagedCount() {
return this.getUnstagedFilesCountForPath(this.file.path);
},
folderStagedCount() {
return this.getStagedFilesCountForPath(this.file.path);
},
changesCount() {
return this.getChangesInFolder(this.file.path);
},
folderChangesTooltip() {
if (this.changesCount === 0) return undefined;
if (this.folderUnstagedCount > 0 && this.folderStagedCount === 0) {
return n__('%d unstaged change', '%d unstaged changes', this.folderUnstagedCount);
} else if (this.folderUnstagedCount === 0 && this.folderStagedCount > 0) {
return n__('%d staged change', '%d staged changes', this.folderStagedCount);
}
return sprintf(__('%{unstaged} unstaged and %{staged} staged changes'), {
unstaged: this.folderUnstagedCount,
staged: this.folderStagedCount,
});
},
showTreeChangesCount() {
return this.file.type === 'tree' && this.changesCount > 0 && !this.file.opened;
},
showChangedFileIcon() {
return this.file.changed || this.file.tempFile || this.file.staged;
},
},
};
</script>
<template>
<div class="float-right ide-file-icon-holder">
<mr-file-icon
v-if="file.mrChange"
/>
<span
v-if="showTreeChangesCount"
class="ide-tree-changes"
>
{{ changesCount }}
<icon
v-tooltip
:title="folderChangesTooltip"
:size="12"
data-container="body"
data-placement="right"
name="file-modified"
css-classes="prepend-left-5 ide-file-modified"
/>
</span>
<changed-file-icon
v-else-if="showChangedFileIcon"
:file="file"
:show-tooltip="true"
:show-staged-icon="true"
:force-modified-icon="true"
/>
<new-dropdown
:type="file.type"
:path="file.path"
:mouse-over="mouseOver"
class="prepend-left-8"
/>
</div>
</template>
......@@ -2,15 +2,16 @@
import { mapActions, mapGetters, mapState } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
import RepoFile from './repo_file.vue';
import FileRow from '~/vue_shared/components/file_row.vue';
import NavDropdown from './nav_dropdown.vue';
import FileRowExtra from './file_row_extra.vue';
export default {
components: {
Icon,
RepoFile,
SkeletonLoadingContainer,
NavDropdown,
FileRow,
},
props: {
viewerType: {
......@@ -34,8 +35,9 @@ export default {
this.updateViewer(this.viewerType);
},
methods: {
...mapActions(['updateViewer']),
...mapActions(['updateViewer', 'toggleTreeOpen']),
},
FileRowExtra,
};
</script>
......@@ -63,11 +65,13 @@ export default {
<div
class="ide-tree-body h-100"
>
<repo-file
<file-row
v-for="file in currentTree.tree"
:key="file.key"
:file="file"
:level="0"
:extra-component="$options.FileRowExtra"
@toggleTreeOpen="toggleTreeOpen"
/>
</div>
</template>
......
<script>
import { mapActions, mapGetters } from 'vuex';
import { n__, __, sprintf } from '~/locale';
import tooltip from '~/vue_shared/directives/tooltip';
import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
import Icon from '~/vue_shared/components/icon.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import router from '../ide_router';
import NewDropdown from './new_dropdown/index.vue';
import FileStatusIcon from './repo_file_status_icon.vue';
import ChangedFileIcon from './changed_file_icon.vue';
import MrFileIcon from './mr_file_icon.vue';
export default {
name: 'RepoFile',
directives: {
tooltip,
},
name: 'FileRow',
components: {
SkeletonLoadingContainer,
NewDropdown,
FileStatusIcon,
FileIcon,
ChangedFileIcon,
MrFileIcon,
Icon,
},
props: {
......@@ -34,6 +17,11 @@ export default {
type: Number,
required: true,
},
extraComponent: {
type: Object,
required: false,
default: null,
},
},
data() {
return {
......@@ -41,34 +29,6 @@ export default {
};
},
computed: {
...mapGetters([
'getChangesInFolder',
'getUnstagedFilesCountForPath',
'getStagedFilesCountForPath',
]),
folderUnstagedCount() {
return this.getUnstagedFilesCountForPath(this.file.path);
},
folderStagedCount() {
return this.getStagedFilesCountForPath(this.file.path);
},
changesCount() {
return this.getChangesInFolder(this.file.path);
},
folderChangesTooltip() {
if (this.changesCount === 0) return undefined;
if (this.folderUnstagedCount > 0 && this.folderStagedCount === 0) {
return n__('%d unstaged change', '%d unstaged changes', this.folderUnstagedCount);
} else if (this.folderUnstagedCount === 0 && this.folderStagedCount > 0) {
return n__('%d staged change', '%d staged changes', this.folderStagedCount);
}
return sprintf(__('%{unstaged} unstaged and %{staged} staged changes'), {
unstaged: this.folderUnstagedCount,
staged: this.folderStagedCount,
});
},
isTree() {
return this.file.type === 'tree';
},
......@@ -83,17 +43,11 @@ export default {
fileClass() {
return {
'file-open': this.isBlob && this.file.opened,
'file-active': this.isBlob && this.file.active,
'is-active': this.isBlob && this.file.active,
folder: this.isTree,
'is-open': this.file.opened,
};
},
showTreeChangesCount() {
return this.isTree && this.changesCount > 0 && !this.file.opened;
},
showChangedFileIcon() {
return this.file.changed || this.file.tempFile || this.file.staged;
},
},
watch: {
'file.active': function fileActiveWatch(active) {
......@@ -108,14 +62,16 @@ export default {
}
},
methods: {
...mapActions(['toggleTreeOpen']),
toggleTreeOpen(path) {
this.$emit('toggleTreeOpen', path);
},
clickFile() {
// Manual Action if a tree is selected/opened
if (this.isTree && this.hasUrlAtCurrentRoute()) {
this.toggleTreeOpen(this.file.path);
}
router.push(`/project${this.file.url}`);
if (this.$router) this.$router.push(`/project${this.file.url}`);
},
scrollIntoView(isInit = false) {
const block = isInit && this.isTree ? 'center' : 'nearest';
......@@ -141,6 +97,8 @@ export default {
return filePath === routePath;
},
hasUrlAtCurrentRoute() {
if (!this.$router || !this.$router.currentRoute) return true;
return this.$router.currentRoute.path === `/project${this.file.url}`;
},
toggleHover(over) {
......@@ -154,18 +112,18 @@ export default {
<div>
<div
:class="fileClass"
class="file"
class="file-row"
role="button"
@click="clickFile"
@mouseover="toggleHover(true)"
@mouseout="toggleHover(false)"
>
<div
class="file-name"
class="file-row-name-container"
>
<span
:style="levelIndentation"
class="ide-file-name str-truncated"
class="file-row-name str-truncated"
>
<file-icon
:file-name="file.name"
......@@ -175,53 +133,78 @@ export default {
:size="16"
/>
{{ file.name }}
<file-status-icon
:file="file"
/>
</span>
<span class="float-right ide-file-icon-holder">
<mr-file-icon
v-if="file.mrChange"
/>
<span
v-if="showTreeChangesCount"
class="ide-tree-changes"
>
{{ changesCount }}
<icon
v-tooltip
:title="folderChangesTooltip"
:size="12"
data-container="body"
data-placement="right"
name="file-modified"
css-classes="prepend-left-5 ide-file-modified"
/>
</span>
<changed-file-icon
v-else-if="showChangedFileIcon"
:file="file"
:show-tooltip="true"
:show-staged-icon="true"
:force-modified-icon="true"
class="float-right"
/>
</span>
<new-dropdown
:type="file.type"
:path="file.path"
<component
v-if="extraComponent"
:is="extraComponent"
:file="file"
:mouse-over="mouseOver"
class="float-right prepend-left-8"
/>
</div>
</div>
<template v-if="file.opened">
<repo-file
<file-row
v-for="childFile in file.tree"
:key="childFile.key"
:file="childFile"
:level="level + 1"
:extra-component="extraComponent"
@toggleTreeOpen="toggleTreeOpen"
/>
</template>
</div>
</template>
<style>
.file-row {
display: flex;
align-items: center;
height: 32px;
padding: 4px 8px;
margin-left: -8px;
margin-right: -8px;
border-radius: 3px;
text-align: left;
cursor: pointer;
}
.file-row:hover,
.file-row:focus {
background: #f2f2f2;
}
.file-row:active {
background: #dfdfdf;
}
.file-row.is-active {
background: #f2f2f2;
}
.file-row-name-container {
display: flex;
width: 100%;
align-items: center;
overflow: visible;
}
.file-row-name {
display: inline-block;
flex: 1;
max-width: inherit;
height: 18px;
line-height: 16px;
text-overflow: ellipsis;
white-space: nowrap;
}
.file-row-name svg {
margin-right: 2px;
vertical-align: middle;
}
.file-row-name .loading-container {
display: inline-block;
margin-right: 4px;
}
</style>
......@@ -53,83 +53,9 @@ $ide-commit-header-height: 48px;
flex: 1;
min-height: 0; // firefox fix
.file {
height: 32px;
cursor: pointer;
&.file-active {
background: $theme-gray-100;
}
.ide-file-name {
flex: 1;
white-space: nowrap;
text-overflow: ellipsis;
max-width: inherit;
line-height: 16px;
display: inline-block;
height: 18px;
svg {
vertical-align: middle;
margin-right: 2px;
}
.loading-container {
margin-right: 4px;
display: inline-block;
}
}
.ide-file-icon-holder {
display: flex;
align-items: center;
color: $theme-gray-700;
}
.ide-file-changed-icon {
margin-left: auto;
> svg {
display: block;
}
}
.ide-new-btn {
display: none;
.btn {
padding: 2px 5px;
}
}
&:hover,
&:focus {
.ide-new-btn {
display: block;
}
}
.folder-icon {
fill: $gl-text-color-secondary;
}
}
a {
color: $gl-text-color;
}
th {
position: sticky;
top: 0;
}
}
.file-name {
display: flex;
overflow: visible;
align-items: center;
width: 100%;
}
.multi-file-loading-container {
......@@ -625,8 +551,7 @@ $ide-commit-header-height: 48px;
}
}
.multi-file-commit-list-path,
.ide-file-list .file {
.multi-file-commit-list-path {
display: flex;
align-items: center;
margin-left: -$grid-size;
......@@ -634,28 +559,14 @@ $ide-commit-header-height: 48px;
padding: $grid-size / 2 $grid-size;
border-radius: $border-radius-default;
text-align: left;
&:hover,
&:focus {
background: $theme-gray-100;
}
&:active {
background: $theme-gray-200;
}
}
.multi-file-commit-list-path {
cursor: pointer;
height: $ide-commit-row-height;
padding-right: 0;
&.is-active {
background-color: $white-normal;
}
&:hover,
&:focus {
background: $theme-gray-100;
outline: 0;
.multi-file-discard-btn {
......@@ -665,6 +576,14 @@ $ide-commit-header-height: 48px;
}
}
&:active {
background: $theme-gray-200;
}
&.is-active {
background-color: $white-normal;
}
svg {
min-width: 16px;
vertical-align: middle;
......@@ -1398,9 +1317,17 @@ $ide-commit-header-height: 48px;
}
}
.ide-new-btn .dropdown.show .ide-entry-dropdown-toggle {
color: $white-normal;
background-color: $blue-500;
.ide-new-btn {
display: none;
.btn {
padding: 2px 5px;
}
.dropdown.show .ide-entry-dropdown-toggle {
color: $white-normal;
background-color: $blue-500;
}
}
.ide-preview-header {
......@@ -1465,3 +1392,28 @@ $ide-commit-header-height: 48px;
width: $ide-commit-row-height;
height: $ide-commit-row-height;
}
.ide-file-icon-holder {
display: flex;
align-items: center;
color: $theme-gray-700;
}
.ide-file-changed-icon {
margin-left: auto;
> svg {
display: block;
}
}
.file-row:hover,
.file-row:focus {
.ide-new-btn {
display: block;
}
.folder-icon {
fill: $gl-text-color-secondary;
}
}
......@@ -279,6 +279,10 @@ table.u2f-registrations {
}
}
.codes {
padding-top: 14px;
}
.oauth-application-show {
.scope-name {
font-weight: $gl-font-weight-bold;
......
......@@ -10,4 +10,6 @@
%li
%span.monospace= code
= link_to 'Proceed', profile_account_path, class: 'btn btn-success'
.d-flex
= link_to 'Proceed', profile_account_path, class: 'btn btn-success append-right-10'
= link_to 'Download codes', "data:text/plain;charset=utf-8,#{CGI.escape(@codes.join("\n"))}", download: "gitlab-recovery-codes.txt", class: 'btn btn-default'
---
title: Add button to download 2FA codes
merge_request:
author: Luke Picciau
type: added
---
title: Add Gitaly diff stats RPC client
merge_request: 21732
author:
type: changed
......@@ -1231,13 +1231,16 @@ rspec:
```
The collected JUnit reports will be uploaded to GitLab as an artifact and will
be automatically [shown in merge requests](../junit_test_reports.md).
be automatically shown in merge requests.
For more examples, see [JUnit test reports](../junit_test_reports.md).
NOTE: **Note:**
In case the JUnit tool you use exports to multiple XML files, you can specify
multiple test report paths within a single job
(`junit: [rspec-1.xml, rspec-2.xml, rspec-3.xml]`) and they will be automatically
concatenated into a single file.
multiple test report paths within a single job and they will be automatically
concatenated into a single file. Use a filename pattern (`junit: rspec-*.xml`),
an array of filenames (`junit: [rspec-1.xml, rspec-2.xml, rspec-3.xml]`), or a
combination thereof (`junit: [rspec.xml, test-results/TEST-*.xml]`).
## `dependencies`
......
# frozen_string_literal: true
module Gitlab
module Git
class DiffStatsCollection
include Enumerable
def initialize(diff_stats)
@collection = diff_stats
end
def each(&block)
@collection.each(&block)
end
end
end
end
......@@ -438,6 +438,16 @@ module Gitlab
Gitlab::Git::DiffCollection.new(iterator, options)
end
def diff_stats(left_id, right_id)
stats = wrapped_gitaly_errors do
gitaly_commit_client.diff_stats(left_id, right_id)
end
Gitlab::Git::DiffStatsCollection.new(stats)
rescue CommandError
Gitlab::Git::DiffStatsCollection.new([])
end
# Returns a RefName for a given SHA
def ref_name_for_sha(ref_path, sha)
raise ArgumentError, "sha can't be empty" unless sha.present?
......
......@@ -172,6 +172,17 @@ module Gitlab
consume_commits_response(response)
end
def diff_stats(left_commit_sha, right_commit_sha)
request = Gitaly::DiffStatsRequest.new(
repository: @gitaly_repo,
left_commit_id: left_commit_sha,
right_commit_id: right_commit_sha
)
response = GitalyClient.call(@repository.storage, :diff_service, :diff_stats, request, timeout: GitalyClient.medium_timeout)
response.flat_map(&:stats)
end
def find_all_commits(opts = {})
request = Gitaly::FindAllCommitsRequest.new(
repository: @gitaly_repo,
......
import Vue from 'vue';
import { createStore } from '~/ide/stores';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import FileRowExtra from '~/ide/components/file_row_extra.vue';
import { file, resetStore } from '../helpers';
describe('IDE extra file row component', () => {
let Component;
let vm;
let unstagedFilesCount = 0;
let stagedFilesCount = 0;
let changesCount = 0;
beforeAll(() => {
Component = Vue.extend(FileRowExtra);
});
beforeEach(() => {
vm = createComponentWithStore(Component, createStore(), {
file: {
...file('test'),
},
mouseOver: false,
});
spyOnProperty(vm, 'getUnstagedFilesCountForPath').and.returnValue(() => unstagedFilesCount);
spyOnProperty(vm, 'getStagedFilesCountForPath').and.returnValue(() => stagedFilesCount);
spyOnProperty(vm, 'getChangesInFolder').and.returnValue(() => changesCount);
vm.$mount();
});
afterEach(() => {
vm.$destroy();
resetStore(vm.$store);
stagedFilesCount = 0;
unstagedFilesCount = 0;
changesCount = 0;
});
describe('folderChangesTooltip', () => {
it('returns undefined when changes count is 0', () => {
expect(vm.folderChangesTooltip).toBe(undefined);
});
it('returns unstaged changes text', () => {
changesCount = 1;
unstagedFilesCount = 1;
expect(vm.folderChangesTooltip).toBe('1 unstaged change');
});
it('returns staged changes text', () => {
changesCount = 1;
stagedFilesCount = 1;
expect(vm.folderChangesTooltip).toBe('1 staged change');
});
it('returns staged and unstaged changes text', () => {
changesCount = 1;
stagedFilesCount = 1;
unstagedFilesCount = 1;
expect(vm.folderChangesTooltip).toBe('1 unstaged and 1 staged changes');
});
});
describe('show tree changes count', () => {
it('does not show for blobs', () => {
vm.file.type = 'blob';
expect(vm.$el.querySelector('.ide-tree-changes')).toBe(null);
});
it('does not show when changes count is 0', () => {
vm.file.type = 'tree';
expect(vm.$el.querySelector('.ide-tree-changes')).toBe(null);
});
it('does not show when tree is open', done => {
vm.file.type = 'tree';
vm.file.opened = true;
changesCount = 1;
vm.$nextTick(() => {
expect(vm.$el.querySelector('.ide-tree-changes')).toBe(null);
done();
});
});
it('shows for trees with changes', done => {
vm.file.type = 'tree';
vm.file.opened = false;
changesCount = 1;
vm.$nextTick(() => {
expect(vm.$el.querySelector('.ide-tree-changes')).not.toBe(null);
done();
});
});
});
describe('changes file icon', () => {
it('hides when file is not changed', () => {
expect(vm.$el.querySelector('.ide-file-changed-icon')).toBe(null);
});
it('shows when file is changed', done => {
vm.file.changed = true;
vm.$nextTick(() => {
expect(vm.$el.querySelector('.ide-file-changed-icon')).not.toBe(null);
done();
});
});
it('shows when file is staged', done => {
vm.file.staged = true;
vm.$nextTick(() => {
expect(vm.$el.querySelector('.ide-file-changed-icon')).not.toBe(null);
done();
});
});
it('shows when file is a tempFile', done => {
vm.file.tempFile = true;
vm.$nextTick(() => {
expect(vm.$el.querySelector('.ide-file-changed-icon')).not.toBe(null);
done();
});
});
});
describe('merge request icon', () => {
it('hides when not a merge request change', () => {
expect(vm.$el.querySelector('.ic-git-merge')).toBe(null);
});
it('shows when a merge request change', done => {
vm.file.mrChange = true;
vm.$nextTick(() => {
expect(vm.$el.querySelector('.ic-git-merge')).not.toBe(null);
done();
});
});
});
});
import Vue from 'vue';
import store from '~/ide/stores';
import repoFile from '~/ide/components/repo_file.vue';
import router from '~/ide/ide_router';
import { createComponentWithStore } from '../../helpers/vue_mount_component_helper';
import { file } from '../helpers';
describe('RepoFile', () => {
let vm;
function createComponent(propsData) {
const RepoFile = Vue.extend(repoFile);
vm = createComponentWithStore(RepoFile, store, propsData);
vm.$mount();
}
afterEach(() => {
vm.$destroy();
});
it('renders link, icon and name', () => {
createComponent({
file: file('t4'),
level: 0,
});
const name = vm.$el.querySelector('.ide-file-name');
expect(name.href).toMatch('');
expect(name.textContent.trim()).toEqual(vm.file.name);
});
it('fires clickFile when the link is clicked', done => {
spyOn(router, 'push');
createComponent({
file: file('t3'),
level: 0,
});
vm.$el.querySelector('.file-name').click();
setTimeout(() => {
expect(router.push).toHaveBeenCalledWith(`/project${vm.file.url}`);
done();
});
});
describe('folder', () => {
it('renders changes count inside folder', () => {
const f = {
...file('folder'),
path: 'testing',
type: 'tree',
branchId: 'master',
projectId: 'project',
};
store.state.changedFiles.push({
...file('fileName'),
path: 'testing/fileName',
});
createComponent({
file: f,
level: 0,
});
const treeChangesEl = vm.$el.querySelector('.ide-tree-changes');
expect(treeChangesEl).not.toBeNull();
expect(treeChangesEl.textContent).toContain('1');
});
it('renders action dropdown', done => {
createComponent({
file: {
...file('t4'),
type: 'tree',
branchId: 'master',
projectId: 'project',
},
level: 0,
});
setTimeout(() => {
expect(vm.$el.querySelector('.ide-new-btn')).not.toBeNull();
done();
});
});
});
describe('locked file', () => {
let f;
beforeEach(() => {
f = file('locked file');
f.file_lock = {
user: {
name: 'testuser',
updated_at: new Date(),
},
};
createComponent({
file: f,
level: 0,
});
});
it('renders lock icon', () => {
expect(vm.$el.querySelector('.file-status-icon')).not.toBeNull();
});
it('renders a tooltip', () => {
expect(
vm.$el.querySelector('.ide-file-name span:nth-child(2)').dataset.originalTitle,
).toContain('Locked by testuser');
});
});
it('calls scrollIntoView if made active', done => {
createComponent({
file: {
...file(),
type: 'blob',
active: false,
},
level: 0,
});
spyOn(vm, 'scrollIntoView');
vm.file.active = true;
vm.$nextTick(() => {
expect(vm.scrollIntoView).toHaveBeenCalled();
done();
});
});
});
import Vue from 'vue';
import FileRow from '~/vue_shared/components/file_row.vue';
import { file } from 'spec/ide/helpers';
import mountComponent from '../../helpers/vue_mount_component_helper';
describe('RepoFile', () => {
let vm;
function createComponent(propsData) {
const FileRowComponent = Vue.extend(FileRow);
vm = mountComponent(FileRowComponent, propsData);
}
afterEach(() => {
vm.$destroy();
});
it('renders name', () => {
createComponent({
file: file('t4'),
level: 0,
});
const name = vm.$el.querySelector('.file-row-name');
expect(name.textContent.trim()).toEqual(vm.file.name);
});
it('emits toggleTreeOpen on click', () => {
createComponent({
file: {
...file('t3'),
type: 'tree',
},
level: 0,
});
spyOn(vm, '$emit').and.stub();
vm.$el.querySelector('.file-row').click();
expect(vm.$emit).toHaveBeenCalledWith('toggleTreeOpen', vm.file.path);
});
it('calls scrollIntoView if made active', done => {
createComponent({
file: {
...file(),
type: 'blob',
active: false,
},
level: 0,
});
spyOn(vm, 'scrollIntoView').and.stub();
vm.file.active = true;
vm.$nextTick(() => {
expect(vm.scrollIntoView).toHaveBeenCalled();
done();
});
});
it('indents row based on level', () => {
createComponent({
file: file('t4'),
level: 2,
});
expect(vm.$el.querySelector('.file-row-name').style.marginLeft).toBe('32px');
});
});
......@@ -1112,6 +1112,32 @@ describe Gitlab::Git::Repository, :seed_helper do
end
end
describe '#diff_stats' do
let(:left_commit_id) { 'feature' }
let(:right_commit_id) { 'master' }
it 'returns a DiffStatsCollection' do
collection = repository.diff_stats(left_commit_id, right_commit_id)
expect(collection).to be_a(Gitlab::Git::DiffStatsCollection)
expect(collection).to be_a(Enumerable)
end
it 'yields Gitaly::DiffStats objects' do
collection = repository.diff_stats(left_commit_id, right_commit_id)
expect(collection.to_a).to all(be_a(Gitaly::DiffStats))
end
it 'returns no Gitaly::DiffStats when SHAs are invalid' do
collection = repository.diff_stats('foo', 'bar')
expect(collection).to be_a(Gitlab::Git::DiffStatsCollection)
expect(collection).to be_a(Enumerable)
expect(collection.to_a).to be_empty
end
end
describe "#ls_files" do
let(:master_file_paths) { repository.ls_files("master") }
let(:utf8_file_paths) { repository.ls_files("ls-files-utf8") }
......
......@@ -118,6 +118,22 @@ describe Gitlab::GitalyClient::CommitService do
end
end
describe '#diff_stats' do
let(:left_commit_id) { 'master' }
let(:right_commit_id) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' }
it 'sends an RPC request' do
request = Gitaly::DiffStatsRequest.new(repository: repository_message,
left_commit_id: left_commit_id,
right_commit_id: right_commit_id)
expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:diff_stats)
.with(request, kind_of(Hash)).and_return([])
described_class.new(repository).diff_stats(left_commit_id, right_commit_id)
end
end
describe '#tree_entries' do
let(:path) { '/' }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment