summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWinnie Hellmann <winnie@gitlab.com>2019-05-14 10:23:42 +0200
committerWinnie Hellmann <winnie@gitlab.com>2019-05-14 10:39:25 +0200
commit36c63d1758906ca6ece1bc033d7cfbac871b511a (patch)
tree9245032e0dbcdfb9337064071772b235304e91f1
parent9f3203475c8c2825df23364cac5cdece746a5d4f (diff)
downloadgitlab-ce-winh-update-vue-test-utils.tar.gz
-rw-r--r--spec/javascripts/monitoring/charts/area_spec.js231
-rw-r--r--spec/javascripts/notes/components/noteable_discussion_spec.js269
-rw-r--r--spec/javascripts/pipelines/pipeline_triggerer_spec.js54
-rw-r--r--spec/javascripts/vue_shared/components/user_avatar/user_avatar_list_spec.js156
4 files changed, 710 insertions, 0 deletions
diff --git a/spec/javascripts/monitoring/charts/area_spec.js b/spec/javascripts/monitoring/charts/area_spec.js
new file mode 100644
index 00000000000..41a6c04efb9
--- /dev/null
+++ b/spec/javascripts/monitoring/charts/area_spec.js
@@ -0,0 +1,231 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlAreaChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
+import { shallowWrapperContainsSlotText } from 'spec/helpers/vue_test_utils_helper';
+import Area from '~/monitoring/components/charts/area.vue';
+import MonitoringStore from '~/monitoring/stores/monitoring_store';
+import MonitoringMock, { deploymentData } from '../mock_data';
+
+describe('Area component', () => {
+ const mockWidgets = 'mockWidgets';
+ const mockSvgPathContent = 'mockSvgPathContent';
+ let mockGraphData;
+ let areaChart;
+ let spriteSpy;
+
+ beforeEach(() => {
+ const store = new MonitoringStore();
+ store.storeMetrics(MonitoringMock.data);
+ store.storeDeploymentData(deploymentData);
+
+ [mockGraphData] = store.groups[0].metrics;
+
+ areaChart = shallowMount(Area, {
+ propsData: {
+ graphData: mockGraphData,
+ containerWidth: 0,
+ deploymentData: store.deploymentData,
+ },
+ slots: {
+ default: mockWidgets,
+ },
+ });
+
+ spriteSpy = spyOnDependency(Area, 'getSvgIconPathContent').and.callFake(
+ () => new Promise(resolve => resolve(mockSvgPathContent)),
+ );
+ });
+
+ afterEach(() => {
+ areaChart.destroy();
+ });
+
+ it('renders chart title', () => {
+ expect(areaChart.find({ ref: 'graphTitle' }).text()).toBe(mockGraphData.title);
+ });
+
+ it('contains graph widgets from slot', () => {
+ expect(areaChart.find({ ref: 'graphWidgets' }).text()).toBe(mockWidgets);
+ });
+
+ describe('wrapped components', () => {
+ describe('GitLab UI area chart', () => {
+ let glAreaChart;
+
+ beforeEach(() => {
+ glAreaChart = areaChart.find(GlAreaChart);
+ });
+
+ it('is a Vue instance', () => {
+ expect(glAreaChart.isVueInstance()).toBe(true);
+ });
+
+ it('receives data properties needed for proper chart render', () => {
+ const props = glAreaChart.props();
+
+ expect(props.data).toBe(areaChart.vm.chartData);
+ expect(props.option).toBe(areaChart.vm.chartOptions);
+ expect(props.formatTooltipText).toBe(areaChart.vm.formatTooltipText);
+ expect(props.thresholds).toBe(areaChart.vm.thresholds);
+ });
+
+ it('recieves a tooltip title', () => {
+ const mockTitle = 'mockTitle';
+ areaChart.vm.tooltip.title = mockTitle;
+
+ expect(shallowWrapperContainsSlotText(glAreaChart, 'tooltipTitle', mockTitle)).toBe(true);
+ });
+
+ describe('when tooltip is showing deployment data', () => {
+ beforeEach(() => {
+ areaChart.vm.tooltip.isDeployment = true;
+ });
+
+ it('uses deployment title', () => {
+ expect(shallowWrapperContainsSlotText(glAreaChart, 'tooltipTitle', 'Deployed')).toBe(
+ true,
+ );
+ });
+
+ it('renders commit sha in tooltip content', () => {
+ const mockSha = 'mockSha';
+ areaChart.vm.tooltip.sha = mockSha;
+
+ expect(shallowWrapperContainsSlotText(glAreaChart, 'tooltipContent', mockSha)).toBe(true);
+ });
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('formatTooltipText', () => {
+ const mockDate = deploymentData[0].created_at;
+ const generateSeriesData = type => ({
+ seriesData: [
+ {
+ seriesName: areaChart.vm.chartData[0].name,
+ componentSubType: type,
+ value: [mockDate, 5.55555],
+ seriesIndex: 0,
+ },
+ ],
+ value: mockDate,
+ });
+
+ describe('when series is of line type', () => {
+ beforeEach(() => {
+ areaChart.vm.formatTooltipText(generateSeriesData('line'));
+ });
+
+ it('formats tooltip title', () => {
+ expect(areaChart.vm.tooltip.title).toBe('31 May 2017, 9:23PM');
+ });
+
+ it('formats tooltip content', () => {
+ const name = 'Core Usage';
+ const value = '5.556';
+ const seriesLabel = areaChart.find(GlChartSeriesLabel);
+
+ expect(seriesLabel.vm.color).toBe('');
+ expect(shallowWrapperContainsSlotText(seriesLabel, 'default', name)).toBe(true);
+ expect(areaChart.vm.tooltip.content).toEqual([{ name, value, color: undefined }]);
+ expect(
+ shallowWrapperContainsSlotText(areaChart.find(GlAreaChart), 'tooltipContent', value),
+ ).toBe(true);
+ });
+ });
+
+ describe('when series is of scatter type', () => {
+ beforeEach(() => {
+ areaChart.vm.formatTooltipText(generateSeriesData('scatter'));
+ });
+
+ it('formats tooltip title', () => {
+ expect(areaChart.vm.tooltip.title).toBe('31 May 2017, 9:23PM');
+ });
+
+ it('formats tooltip sha', () => {
+ expect(areaChart.vm.tooltip.sha).toBe('f5bcd1d9');
+ });
+ });
+ });
+
+ describe('setSvg', () => {
+ const mockSvgName = 'mockSvgName';
+
+ beforeEach(() => {
+ areaChart.vm.setSvg(mockSvgName);
+ });
+
+ it('gets svg path content', () => {
+ expect(spriteSpy).toHaveBeenCalledWith(mockSvgName);
+ });
+
+ it('sets svg path content', done => {
+ areaChart.vm.$nextTick(() => {
+ expect(areaChart.vm.svgs[mockSvgName]).toBe(`path://${mockSvgPathContent}`);
+ done();
+ });
+ });
+ });
+
+ describe('onResize', () => {
+ const mockWidth = 233;
+
+ beforeEach(() => {
+ spyOn(Element.prototype, 'getBoundingClientRect').and.callFake(() => ({
+ width: mockWidth,
+ }));
+ areaChart.vm.onResize();
+ });
+
+ it('sets area chart width', () => {
+ expect(areaChart.vm.width).toBe(mockWidth);
+ });
+ });
+ });
+
+ describe('computed', () => {
+ describe('chartData', () => {
+ let chartData;
+ const seriesData = () => chartData[0];
+
+ beforeEach(() => {
+ ({ chartData } = areaChart.vm);
+ });
+
+ it('utilizes all data points', () => {
+ expect(chartData.length).toBe(1);
+ expect(seriesData().data.length).toBe(297);
+ });
+
+ it('creates valid data', () => {
+ const { data } = seriesData();
+
+ expect(
+ data.filter(([time, value]) => new Date(time).getTime() > 0 && typeof value === 'number')
+ .length,
+ ).toBe(data.length);
+ });
+
+ it('formats line width correctly', () => {
+ expect(chartData[0].lineStyle.width).toBe(2);
+ });
+ });
+
+ describe('scatterSeries', () => {
+ it('utilizes deployment data', () => {
+ expect(areaChart.vm.scatterSeries.data).toEqual([
+ ['2017-05-31T21:23:37.881Z', 0],
+ ['2017-05-30T20:08:04.629Z', 0],
+ ['2017-05-30T17:42:38.409Z', 0],
+ ]);
+ });
+ });
+
+ describe('yAxisLabel', () => {
+ it('constructs a label for the chart y-axis', () => {
+ expect(areaChart.vm.yAxisLabel).toBe('CPU');
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/notes/components/noteable_discussion_spec.js b/spec/javascripts/notes/components/noteable_discussion_spec.js
new file mode 100644
index 00000000000..efa864e7d00
--- /dev/null
+++ b/spec/javascripts/notes/components/noteable_discussion_spec.js
@@ -0,0 +1,269 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import createStore from '~/notes/stores';
+import noteableDiscussion from '~/notes/components/noteable_discussion.vue';
+import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
+import ResolveWithIssueButton from '~/notes/components/discussion_resolve_with_issue_button.vue';
+import NoteForm from '~/notes/components/note_form.vue';
+import '~/behaviors/markdown/render_gfm';
+import { noteableDataMock, discussionMock, notesDataMock } from '../mock_data';
+import mockDiffFile from '../../diffs/mock_data/diff_file';
+
+const discussionWithTwoUnresolvedNotes = 'merge_requests/resolved_diff_discussion.json';
+
+describe('noteable_discussion component', () => {
+ let store;
+ let wrapper;
+
+ preloadFixtures(discussionWithTwoUnresolvedNotes);
+
+ beforeEach(() => {
+ window.mrTabs = {};
+ store = createStore();
+ store.dispatch('setNoteableData', noteableDataMock);
+ store.dispatch('setNotesData', notesDataMock);
+
+ const localVue = createLocalVue();
+ wrapper = shallowMount(noteableDiscussion, {
+ store,
+ propsData: { discussion: discussionMock },
+ localVue,
+ sync: false,
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('should render user avatar', () => {
+ expect(wrapper.find('.user-avatar-link').exists()).toBe(true);
+ });
+
+ it('should not render discussion header for non diff discussions', () => {
+ expect(wrapper.find('.discussion-header').exists()).toBe(false);
+ });
+
+ it('should render discussion header', done => {
+ const discussion = { ...discussionMock };
+ discussion.diff_file = mockDiffFile;
+ discussion.diff_discussion = true;
+
+ wrapper.setProps({ discussion });
+
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ expect(wrapper.find('.discussion-header').exists()).toBe(true);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ describe('actions', () => {
+ it('should toggle reply form', done => {
+ const replyPlaceholder = wrapper.find(ReplyPlaceholder);
+
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ expect(wrapper.vm.isReplying).toEqual(false);
+
+ replyPlaceholder.vm.$emit('onClick');
+ })
+ .then(() => wrapper.vm.$nextTick())
+ .then(() => {
+ expect(wrapper.vm.isReplying).toEqual(true);
+
+ const noteForm = wrapper.find(NoteForm);
+
+ expect(noteForm.exists()).toBe(true);
+
+ const noteFormProps = noteForm.props();
+
+ expect(noteFormProps.discussion).toBe(discussionMock);
+ expect(noteFormProps.isEditing).toBe(false);
+ expect(noteFormProps.line).toBe(null);
+ expect(noteFormProps.saveButtonTitle).toBe('Comment');
+ expect(noteFormProps.autosaveKey).toBe(`Note/Issue/${discussionMock.id}/Reply`);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not render jump to discussion button', () => {
+ expect(
+ wrapper.find('*[data-original-title="Jump to next unresolved discussion"]').exists(),
+ ).toBe(false);
+ });
+ });
+
+ describe('methods', () => {
+ describe('jumpToNextDiscussion', () => {
+ it('expands next unresolved discussion', done => {
+ const discussion2 = getJSONFixture(discussionWithTwoUnresolvedNotes)[0];
+ discussion2.resolved = false;
+ discussion2.active = true;
+ discussion2.id = 'next'; // prepare this for being identified as next one (to be jumped to)
+ store.dispatch('setInitialNotes', [discussionMock, discussion2]);
+ window.mrTabs.currentAction = 'show';
+
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ spyOn(wrapper.vm, 'expandDiscussion').and.stub();
+
+ const nextDiscussionId = discussion2.id;
+
+ setFixtures(`
+ <div class="discussion" data-discussion-id="${nextDiscussionId}"></div>
+ `);
+
+ wrapper.vm.jumpToNextDiscussion();
+
+ expect(wrapper.vm.expandDiscussion).toHaveBeenCalledWith({
+ discussionId: nextDiscussionId,
+ });
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+ });
+
+ describe('action text', () => {
+ const commitId = 'razupaltuff';
+ const truncatedCommitId = commitId.substr(0, 8);
+ let commitElement;
+
+ beforeEach(done => {
+ store.state.diffs = {
+ projectPath: 'something',
+ };
+
+ wrapper.setProps({
+ discussion: {
+ ...discussionMock,
+ for_commit: true,
+ commit_id: commitId,
+ diff_discussion: true,
+ diff_file: {
+ ...mockDiffFile,
+ },
+ },
+ renderDiffFile: true,
+ });
+
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ commitElement = wrapper.find('.commit-sha');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ describe('for commit discussions', () => {
+ it('should display a monospace started a discussion on commit', () => {
+ expect(wrapper.text()).toContain(`started a discussion on commit ${truncatedCommitId}`);
+ expect(commitElement.exists()).toBe(true);
+ expect(commitElement.text()).toContain(truncatedCommitId);
+ });
+ });
+
+ describe('for diff discussion with a commit id', () => {
+ it('should display started discussion on commit header', done => {
+ wrapper.vm.discussion.for_commit = false;
+
+ wrapper.vm.$nextTick(() => {
+ expect(wrapper.text()).toContain(`started a discussion on commit ${truncatedCommitId}`);
+
+ expect(commitElement).not.toBe(null);
+
+ done();
+ });
+ });
+
+ it('should display outdated change on commit header', done => {
+ wrapper.vm.discussion.for_commit = false;
+ wrapper.vm.discussion.active = false;
+
+ wrapper.vm.$nextTick(() => {
+ expect(wrapper.text()).toContain(
+ `started a discussion on an outdated change in commit ${truncatedCommitId}`,
+ );
+
+ expect(commitElement).not.toBe(null);
+
+ done();
+ });
+ });
+ });
+
+ describe('for diff discussions without a commit id', () => {
+ it('should show started a discussion on the diff text', done => {
+ Object.assign(wrapper.vm.discussion, {
+ for_commit: false,
+ commit_id: null,
+ });
+
+ wrapper.vm.$nextTick(() => {
+ expect(wrapper.text()).toContain('started a discussion on the diff');
+
+ done();
+ });
+ });
+
+ it('should show discussion on older version text', done => {
+ Object.assign(wrapper.vm.discussion, {
+ for_commit: false,
+ commit_id: null,
+ active: false,
+ });
+
+ wrapper.vm.$nextTick(() => {
+ expect(wrapper.text()).toContain('started a discussion on an old version of the diff');
+
+ done();
+ });
+ });
+ });
+ });
+
+ describe('for resolved discussion', () => {
+ beforeEach(() => {
+ const discussion = getJSONFixture(discussionWithTwoUnresolvedNotes)[0];
+ wrapper.setProps({ discussion });
+ });
+
+ it('does not display a button to resolve with issue', () => {
+ const button = wrapper.find(ResolveWithIssueButton);
+
+ expect(button.exists()).toBe(false);
+ });
+ });
+
+ describe('for unresolved discussion', () => {
+ beforeEach(done => {
+ const discussion = {
+ ...getJSONFixture(discussionWithTwoUnresolvedNotes)[0],
+ expanded: true,
+ };
+ discussion.notes = discussion.notes.map(note => ({
+ ...note,
+ resolved: false,
+ }));
+
+ wrapper.setProps({ discussion });
+ wrapper.vm
+ .$nextTick()
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('displays a button to resolve with issue', () => {
+ const button = wrapper.find(ResolveWithIssueButton);
+
+ expect(button.exists()).toBe(true);
+ });
+ });
+});
diff --git a/spec/javascripts/pipelines/pipeline_triggerer_spec.js b/spec/javascripts/pipelines/pipeline_triggerer_spec.js
new file mode 100644
index 00000000000..8cf290f2663
--- /dev/null
+++ b/spec/javascripts/pipelines/pipeline_triggerer_spec.js
@@ -0,0 +1,54 @@
+import { mount } from '@vue/test-utils';
+import pipelineTriggerer from '~/pipelines/components/pipeline_triggerer.vue';
+
+describe('Pipelines Triggerer', () => {
+ let wrapper;
+
+ const mockData = {
+ pipeline: {
+ user: {
+ name: 'foo',
+ avatar_url: '/avatar',
+ path: '/path',
+ },
+ },
+ };
+
+ const createComponent = () => {
+ wrapper = mount(pipelineTriggerer, {
+ propsData: mockData,
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('should render a table cell', () => {
+ expect(wrapper.contains('.table-section')).toBe(true);
+ });
+
+ it('should render triggerer information when triggerer is provided', () => {
+ const link = wrapper.find('.js-pipeline-url-user');
+
+ expect(link.attributes('href')).toEqual(mockData.pipeline.user.path);
+ expect(link.find('.js-user-avatar-image-toolip').text()).toEqual(mockData.pipeline.user.name);
+ expect(link.find('img.avatar').attributes('src')).toEqual(
+ `${mockData.pipeline.user.avatar_url}?width=26`,
+ );
+ });
+
+ it('should render "API" when no triggerer is provided', () => {
+ wrapper.setProps({
+ pipeline: {
+ user: null,
+ },
+ });
+
+ expect(wrapper.find('.js-pipeline-url-api').text()).toEqual('API');
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/user_avatar/user_avatar_list_spec.js b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_list_spec.js
new file mode 100644
index 00000000000..96bc3b0cc17
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_list_spec.js
@@ -0,0 +1,156 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { GlButton } from '@gitlab/ui';
+import { TEST_HOST } from 'spec/test_constants';
+import UserAvatarList from '~/vue_shared/components/user_avatar/user_avatar_list.vue';
+import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
+
+const TEST_IMAGE_SIZE = 7;
+const TEST_BREAKPOINT = 5;
+const TEST_EMPTY_MESSAGE = 'Lorem ipsum empty';
+const DEFAULT_EMPTY_MESSAGE = 'None';
+
+const createUser = id => ({
+ id,
+ name: 'Lorem',
+ web_url: `${TEST_HOST}/${id}`,
+ avatar_url: `${TEST_HOST}/${id}/avatar`,
+});
+const createList = n =>
+ Array(n)
+ .fill(1)
+ .map((x, id) => createUser(id));
+
+const localVue = createLocalVue();
+
+describe('UserAvatarList', () => {
+ let props;
+ let wrapper;
+
+ const factory = (options = {}) => {
+ const propsData = {
+ ...props,
+ ...options.propsData,
+ };
+
+ wrapper = shallowMount(localVue.extend(UserAvatarList), {
+ ...options,
+ localVue,
+ propsData,
+ });
+ };
+
+ const clickButton = () => {
+ const button = wrapper.find(GlButton);
+ button.vm.$emit('click');
+ };
+
+ beforeEach(() => {
+ props = { imgSize: TEST_IMAGE_SIZE };
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('empty text', () => {
+ it('shows when items are empty', () => {
+ factory({ propsData: { items: [] } });
+
+ expect(wrapper.text()).toContain(DEFAULT_EMPTY_MESSAGE);
+ });
+
+ it('does not show when items are not empty', () => {
+ factory({ propsData: { items: createList(1) } });
+
+ expect(wrapper.text()).not.toContain(DEFAULT_EMPTY_MESSAGE);
+ });
+
+ it('can be set in props', () => {
+ factory({ propsData: { items: [], emptyText: TEST_EMPTY_MESSAGE } });
+
+ expect(wrapper.text()).toContain(TEST_EMPTY_MESSAGE);
+ });
+ });
+
+ describe('with no breakpoint', () => {
+ beforeEach(() => {
+ props.breakpoint = 0;
+ });
+
+ it('renders avatars', () => {
+ const items = createList(20);
+ factory({ propsData: { items } });
+
+ const links = wrapper.findAll(UserAvatarLink);
+ const linkProps = links.wrappers.map(x => x.props());
+
+ expect(linkProps).toEqual(
+ items.map(x =>
+ jasmine.objectContaining({
+ linkHref: x.web_url,
+ imgSrc: x.avatar_url,
+ imgAlt: x.name,
+ tooltipText: x.name,
+ imgSize: TEST_IMAGE_SIZE,
+ }),
+ ),
+ );
+ });
+ });
+
+ describe('with breakpoint and length equal to breakpoint', () => {
+ beforeEach(() => {
+ props.breakpoint = TEST_BREAKPOINT;
+ props.items = createList(TEST_BREAKPOINT);
+ });
+
+ it('renders all avatars if length is <= breakpoint', () => {
+ factory();
+
+ const links = wrapper.findAll(UserAvatarLink);
+
+ expect(links.length).toEqual(props.items.length);
+ });
+
+ it('does not show button', () => {
+ factory();
+
+ expect(wrapper.find(GlButton).exists()).toBe(false);
+ });
+ });
+
+ describe('with breakpoint and length greater than breakpoint', () => {
+ beforeEach(() => {
+ props.breakpoint = TEST_BREAKPOINT;
+ props.items = createList(TEST_BREAKPOINT + 1);
+ });
+
+ it('renders avatars up to breakpoint', () => {
+ factory();
+
+ const links = wrapper.findAll(UserAvatarLink);
+
+ expect(links.length).toEqual(TEST_BREAKPOINT);
+ });
+
+ describe('with expand clicked', () => {
+ beforeEach(() => {
+ factory();
+ clickButton();
+ });
+
+ it('renders all avatars', () => {
+ const links = wrapper.findAll(UserAvatarLink);
+
+ expect(links.length).toEqual(props.items.length);
+ });
+
+ it('with collapse clicked, it renders avatars up to breakpoint', () => {
+ clickButton();
+ const links = wrapper.findAll(UserAvatarLink);
+
+ expect(links.length).toEqual(TEST_BREAKPOINT);
+ });
+ });
+ });
+});