From b4ded0ba7b4d2cdbed5b1f331cf2083a25ee4d7c Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Mon, 10 Feb 2020 09:08:56 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- spec/features/projects/pipelines/pipeline_spec.rb | 12 ++ spec/finders/keys_finder_spec.rb | 200 ++++++++++----------- .../components/blob_header_viewer_switcher_spec.js | 109 +++++++++++ .../issue_card_inner_scoped_label_spec.js | 43 +++++ .../boards/components/issue_due_date_spec.js | 65 +++++++ .../components/error_tracking_list_spec.js | 29 ++- .../error_tracking/store/list/mutation_spec.js | 25 +++ .../issue_card_inner_scoped_label_spec.js | 43 ----- .../boards/components/issue_due_date_spec.js | 65 ------- spec/lib/banzai/filter/gollum_tags_filter_spec.rb | 15 -- .../filter/table_of_contents_tag_filter_spec.rb | 23 +++ spec/lib/banzai/pipeline/full_pipeline_spec.rb | 31 ++++ spec/requests/api/internal/base_spec.rb | 6 +- spec/services/users/destroy_service_spec.rb | 9 +- spec/support/helpers/filter_spec_helper.rb | 4 +- 15 files changed, 441 insertions(+), 238 deletions(-) create mode 100644 spec/frontend/blob/components/blob_header_viewer_switcher_spec.js create mode 100644 spec/frontend/boards/components/issue_card_inner_scoped_label_spec.js create mode 100644 spec/frontend/boards/components/issue_due_date_spec.js delete mode 100644 spec/javascripts/boards/components/issue_card_inner_scoped_label_spec.js delete mode 100644 spec/javascripts/boards/components/issue_due_date_spec.js create mode 100644 spec/lib/banzai/filter/table_of_contents_tag_filter_spec.rb (limited to 'spec') diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 5fe99c0ad1d..8d9a397de9a 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -356,6 +356,18 @@ describe 'Pipeline', :js do end end + context 'test tabs' do + let(:pipeline) { create(:ci_pipeline, :with_test_reports, project: project) } + + it 'shows badge counter in Tests tab' do + visit_pipeline + wait_for_requests + + expect(pipeline.test_reports.total_count).to eq(4) + expect(page.find('.js-test-report-badge-counter').text).to eq(pipeline.test_reports.total_count.to_s) + end + end + context 'retrying jobs' do before do visit_pipeline diff --git a/spec/finders/keys_finder_spec.rb b/spec/finders/keys_finder_spec.rb index 7605d066ddf..bae4a542484 100644 --- a/spec/finders/keys_finder_spec.rb +++ b/spec/finders/keys_finder_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe KeysFinder do - subject { described_class.new(user, params).execute } + subject { described_class.new(params).execute } let(:user) { create(:user) } let(:params) { {} } @@ -20,159 +20,149 @@ describe KeysFinder do let!(:key_2) { create(:personal_key, last_used_at: nil, user: user) } let!(:key_3) { create(:personal_key, last_used_at: 2.days.ago) } - context 'with a regular user' do - it 'raises GitLabAccessDeniedError' do - expect { subject }.to raise_error(KeysFinder::GitLabAccessDeniedError) - end - end + context 'key_type' do + let!(:deploy_key) { create(:deploy_key) } - context 'with an admin user' do - let(:user) {create(:admin)} + context 'when `key_type` is `ssh`' do + before do + params[:key_type] = 'ssh' + end + + it 'returns only SSH keys' do + expect(subject).to contain_exactly(key_1, key_2, key_3) + end + end - context 'key_type' do - let!(:deploy_key) { create(:deploy_key) } + context 'when `key_type` is not specified' do + it 'returns all types of keys' do + expect(subject).to contain_exactly(key_1, key_2, key_3, deploy_key) + end + end + end - context 'when `key_type` is `ssh`' do + context 'fingerprint' do + context 'with invalid fingerprint' do + context 'with invalid MD5 fingerprint' do before do - params[:key_type] = 'ssh' + params[:fingerprint] = '11:11:11:11' end - it 'returns only SSH keys' do - expect(subject).to contain_exactly(key_1, key_2, key_3) + it 'raises InvalidFingerprint' do + expect { subject }.to raise_error(KeysFinder::InvalidFingerprint) end end - context 'when `key_type` is not specified' do - it 'returns all types of keys' do - expect(subject).to contain_exactly(key_1, key_2, key_3, deploy_key) + context 'with invalid SHA fingerprint' do + before do + params[:fingerprint] = 'nUhzNyftwAAKs7HufskYTte2g' + end + + it 'raises InvalidFingerprint' do + expect { subject }.to raise_error(KeysFinder::InvalidFingerprint) end end end - context 'fingerprint' do - context 'with invalid fingerprint' do - context 'with invalid MD5 fingerprint' do + context 'with valid fingerprints' do + let!(:deploy_key) do + create(:deploy_key, + user: user, + key: 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt1017k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=', + fingerprint: '8a:4a:12:92:0b:50:47:02:d4:5a:8e:a9:44:4e:08:b4', + fingerprint_sha256: '4DPHOVNh53i9dHb5PpY2vjfyf5qniTx1/pBFPoZLDdk') + end + + context 'personal key with valid MD5 params' do + context 'with an existent fingerprint' do before do - params[:fingerprint] = '11:11:11:11' + params[:fingerprint] = 'ba:81:59:68:d7:6c:cd:02:02:bf:6a:9b:55:4e:af:d1' end - it 'raises InvalidFingerprint' do - expect { subject }.to raise_error(KeysFinder::InvalidFingerprint) + it 'returns the key' do + expect(subject).to eq(key_1) + expect(subject.user).to eq(user) end end - context 'with invalid SHA fingerprint' do + context 'deploy key with an existent fingerprint' do before do - params[:fingerprint] = 'nUhzNyftwAAKs7HufskYTte2g' + params[:fingerprint] = '8a:4a:12:92:0b:50:47:02:d4:5a:8e:a9:44:4e:08:b4' end - it 'raises InvalidFingerprint' do - expect { subject }.to raise_error(KeysFinder::InvalidFingerprint) + it 'returns the key' do + expect(subject).to eq(deploy_key) + expect(subject.user).to eq(user) end end - end - - context 'with valid fingerprints' do - let!(:deploy_key) do - create(:deploy_key, - user: user, - key: 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt1017k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=', - fingerprint: '8a:4a:12:92:0b:50:47:02:d4:5a:8e:a9:44:4e:08:b4', - fingerprint_sha256: '4DPHOVNh53i9dHb5PpY2vjfyf5qniTx1/pBFPoZLDdk') - end - - context 'personal key with valid MD5 params' do - context 'with an existent fingerprint' do - before do - params[:fingerprint] = 'ba:81:59:68:d7:6c:cd:02:02:bf:6a:9b:55:4e:af:d1' - end - it 'returns the key' do - expect(subject).to eq(key_1) - expect(subject.user).to eq(user) - end + context 'with a non-existent fingerprint' do + before do + params[:fingerprint] = 'bb:81:59:68:d7:6c:cd:02:02:bf:6a:9b:55:4e:af:d2' end - context 'deploy key with an existent fingerprint' do - before do - params[:fingerprint] = '8a:4a:12:92:0b:50:47:02:d4:5a:8e:a9:44:4e:08:b4' - end - - it 'returns the key' do - expect(subject).to eq(deploy_key) - expect(subject.user).to eq(user) - end + it 'returns nil' do + expect(subject).to be_nil end + end + end - context 'with a non-existent fingerprint' do - before do - params[:fingerprint] = 'bb:81:59:68:d7:6c:cd:02:02:bf:6a:9b:55:4e:af:d2' - end + context 'personal key with valid SHA256 params' do + context 'with an existent fingerprint' do + before do + params[:fingerprint] = 'SHA256:nUhzNyftwADy8AH3wFY31tAKs7HufskYTte2aXo/lCg' + end - it 'returns nil' do - expect(subject).to be_nil - end + it 'returns key' do + expect(subject).to eq(key_1) + expect(subject.user).to eq(user) end end - context 'personal key with valid SHA256 params' do - context 'with an existent fingerprint' do - before do - params[:fingerprint] = 'SHA256:nUhzNyftwADy8AH3wFY31tAKs7HufskYTte2aXo/lCg' - end - - it 'returns key' do - expect(subject).to eq(key_1) - expect(subject.user).to eq(user) - end + context 'deploy key with an existent fingerprint' do + before do + params[:fingerprint] = 'SHA256:4DPHOVNh53i9dHb5PpY2vjfyf5qniTx1/pBFPoZLDdk' end - context 'deploy key with an existent fingerprint' do - before do - params[:fingerprint] = 'SHA256:4DPHOVNh53i9dHb5PpY2vjfyf5qniTx1/pBFPoZLDdk' - end - - it 'returns key' do - expect(subject).to eq(deploy_key) - expect(subject.user).to eq(user) - end + it 'returns key' do + expect(subject).to eq(deploy_key) + expect(subject.user).to eq(user) end + end - context 'with a non-existent fingerprint' do - before do - params[:fingerprint] = 'SHA256:xTjuFqftwADy8AH3wFY31tAKs7HufskYTte2aXi/mNp' - end + context 'with a non-existent fingerprint' do + before do + params[:fingerprint] = 'SHA256:xTjuFqftwADy8AH3wFY31tAKs7HufskYTte2aXi/mNp' + end - it 'returns nil' do - expect(subject).to be_nil - end + it 'returns nil' do + expect(subject).to be_nil end end end end + end - context 'user' do - context 'without user' do - it 'contains ssh_keys of all users in the system' do - expect(subject).to contain_exactly(key_1, key_2, key_3) - end + context 'user' do + context 'without user' do + it 'contains ssh_keys of all users in the system' do + expect(subject).to contain_exactly(key_1, key_2, key_3) end + end - context 'with user' do - before do - params[:user] = user - end + context 'with user' do + before do + params[:users] = user + end - it 'contains ssh_keys of only the specified users' do - expect(subject).to contain_exactly(key_1, key_2) - end + it 'contains ssh_keys of only the specified users' do + expect(subject).to contain_exactly(key_1, key_2) end end + end - context 'sort order' do - it 'sorts in last_used_at_desc order' do - expect(subject).to eq([key_3, key_1, key_2]) - end + context 'sort order' do + it 'sorts in last_used_at_desc order' do + expect(subject).to eq([key_3, key_1, key_2]) end end end diff --git a/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js b/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js new file mode 100644 index 00000000000..ff0b005f441 --- /dev/null +++ b/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js @@ -0,0 +1,109 @@ +import { mount } from '@vue/test-utils'; +import BlobHeaderViewerSwitcher from '~/blob/components/blob_header_viewer_switcher.vue'; +import { + RICH_BLOB_VIEWER, + RICH_BLOB_VIEWER_TITLE, + SIMPLE_BLOB_VIEWER, + SIMPLE_BLOB_VIEWER_TITLE, +} from '~/blob/components/constants'; +import { GlButtonGroup, GlButton } from '@gitlab/ui'; +import { Blob } from './mock_data'; + +describe('Blob Header Viewer Switcher', () => { + let wrapper; + + function createComponent(props = {}) { + wrapper = mount(BlobHeaderViewerSwitcher, { + propsData: { + blob: Object.assign({}, Blob, props), + }, + }); + } + + afterEach(() => { + wrapper.destroy(); + }); + + describe('intiialization', () => { + it('is initialized with rich viewer as preselected when richViewer exists', () => { + createComponent(); + expect(wrapper.vm.viewer).toBe(RICH_BLOB_VIEWER); + }); + + it('is initialized with simple viewer as preselected when richViewer does not exists', () => { + createComponent({ richViewer: null }); + expect(wrapper.vm.viewer).toBe(SIMPLE_BLOB_VIEWER); + }); + }); + + describe('rendering', () => { + let btnGroup; + let buttons; + + beforeEach(() => { + createComponent(); + btnGroup = wrapper.find(GlButtonGroup); + buttons = wrapper.findAll(GlButton); + }); + + it('renders gl-button-group component', () => { + expect(btnGroup.exists()).toBe(true); + }); + + it('renders exactly 2 buttons with predefined actions', () => { + expect(buttons.length).toBe(2); + [SIMPLE_BLOB_VIEWER_TITLE, RICH_BLOB_VIEWER_TITLE].forEach((title, i) => { + expect(buttons.at(i).attributes('title')).toBe(title); + }); + }); + }); + + describe('viewer changes', () => { + let buttons; + let simpleBtn; + let richBtn; + + beforeEach(() => { + createComponent(); + buttons = wrapper.findAll(GlButton); + simpleBtn = buttons.at(0); + richBtn = buttons.at(1); + }); + + it('does not switch the viewer if the selected one is already active', () => { + jest.spyOn(wrapper.vm, '$emit'); + + expect(wrapper.vm.viewer).toBe(RICH_BLOB_VIEWER); + richBtn.vm.$emit('click'); + expect(wrapper.vm.viewer).toBe(RICH_BLOB_VIEWER); + expect(wrapper.vm.$emit).not.toHaveBeenCalled(); + }); + + it('emits an event when a Simple Viewer button is clicked', () => { + jest.spyOn(wrapper.vm, '$emit'); + + simpleBtn.vm.$emit('click'); + + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.vm.viewer).toBe(SIMPLE_BLOB_VIEWER); + expect(wrapper.vm.$emit).toHaveBeenCalledWith('switch-viewer', SIMPLE_BLOB_VIEWER); + }); + }); + + it('emits an event when a Rich Viewer button is clicked', () => { + jest.spyOn(wrapper.vm, '$emit'); + + wrapper.setData({ viewer: SIMPLE_BLOB_VIEWER }); + + return wrapper.vm + .$nextTick() + .then(() => { + richBtn.vm.$emit('click'); + }) + .then(() => { + expect(wrapper.vm.viewer).toBe(RICH_BLOB_VIEWER); + expect(wrapper.vm.$emit).toHaveBeenCalledWith('switch-viewer', RICH_BLOB_VIEWER); + }); + }); + }); +}); diff --git a/spec/frontend/boards/components/issue_card_inner_scoped_label_spec.js b/spec/frontend/boards/components/issue_card_inner_scoped_label_spec.js new file mode 100644 index 00000000000..7389cb14ecb --- /dev/null +++ b/spec/frontend/boards/components/issue_card_inner_scoped_label_spec.js @@ -0,0 +1,43 @@ +import Vue from 'vue'; +import mountComponent from 'helpers/vue_mount_component_helper'; +import IssueCardInnerScopedLabel from '~/boards/components/issue_card_inner_scoped_label.vue'; + +describe('IssueCardInnerScopedLabel Component', () => { + let vm; + const Component = Vue.extend(IssueCardInnerScopedLabel); + const props = { + label: { title: 'Foo::Bar', description: 'Some Random Description' }, + labelStyle: { background: 'white', color: 'black' }, + scopedLabelsDocumentationLink: '/docs-link', + }; + const createComponent = () => mountComponent(Component, { ...props }); + + beforeEach(() => { + vm = createComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('should render label title', () => { + expect(vm.$el.querySelector('.color-label').textContent.trim()).toEqual('Foo::Bar'); + }); + + it('should render question mark symbol', () => { + expect(vm.$el.querySelector('.fa-question-circle')).not.toBeNull(); + }); + + it('should render label style provided', () => { + const node = vm.$el.querySelector('.color-label'); + + expect(node.style.background).toEqual(props.labelStyle.background); + expect(node.style.color).toEqual(props.labelStyle.color); + }); + + it('should render the docs link', () => { + expect(vm.$el.querySelector('a.scoped-label').href).toContain( + props.scopedLabelsDocumentationLink, + ); + }); +}); diff --git a/spec/frontend/boards/components/issue_due_date_spec.js b/spec/frontend/boards/components/issue_due_date_spec.js new file mode 100644 index 00000000000..68e26b68f04 --- /dev/null +++ b/spec/frontend/boards/components/issue_due_date_spec.js @@ -0,0 +1,65 @@ +import Vue from 'vue'; +import dateFormat from 'dateformat'; +import IssueDueDate from '~/boards/components/issue_due_date.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('Issue Due Date component', () => { + let vm; + let date; + const Component = Vue.extend(IssueDueDate); + const createComponent = (dueDate = new Date()) => + mountComponent(Component, { date: dateFormat(dueDate, 'yyyy-mm-dd', true) }); + + beforeEach(() => { + date = new Date(); + vm = createComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('should render "Today" if the due date is today', () => { + const timeContainer = vm.$el.querySelector('time'); + + expect(timeContainer.textContent.trim()).toEqual('Today'); + }); + + it('should render "Yesterday" if the due date is yesterday', () => { + date.setDate(date.getDate() - 1); + vm = createComponent(date); + + expect(vm.$el.querySelector('time').textContent.trim()).toEqual('Yesterday'); + }); + + it('should render "Tomorrow" if the due date is one day from now', () => { + date.setDate(date.getDate() + 1); + vm = createComponent(date); + + expect(vm.$el.querySelector('time').textContent.trim()).toEqual('Tomorrow'); + }); + + it('should render day of the week if due date is one week away', () => { + date.setDate(date.getDate() + 5); + vm = createComponent(date); + + expect(vm.$el.querySelector('time').textContent.trim()).toEqual(dateFormat(date, 'dddd')); + }); + + it('should render month and day for other dates', () => { + date.setDate(date.getDate() + 17); + vm = createComponent(date); + const today = new Date(); + const isDueInCurrentYear = today.getFullYear() === date.getFullYear(); + const format = isDueInCurrentYear ? 'mmm d' : 'mmm d, yyyy'; + + expect(vm.$el.querySelector('time').textContent.trim()).toEqual(dateFormat(date, format)); + }); + + it('should contain the correct `.text-danger` css class for overdue issue', () => { + date.setDate(date.getDate() - 17); + vm = createComponent(date); + + expect(vm.$el.querySelector('time').classList.contains('text-danger')).toEqual(true); + }); +}); diff --git a/spec/frontend/error_tracking/components/error_tracking_list_spec.js b/spec/frontend/error_tracking/components/error_tracking_list_spec.js index 9cf73d54d9b..b632b461eb9 100644 --- a/spec/frontend/error_tracking/components/error_tracking_list_spec.js +++ b/spec/frontend/error_tracking/components/error_tracking_list_spec.js @@ -62,6 +62,7 @@ describe('ErrorTrackingList', () => { sortByField: jest.fn(), fetchPaginatedResults: jest.fn(), updateStatus: jest.fn(), + removeIgnoredResolvedErrors: jest.fn(), }; const state = { @@ -221,6 +222,8 @@ describe('ErrorTrackingList', () => { }); describe('When the ignore button on an error is clicked', () => { + const ignoreErrorButton = () => wrapper.find({ ref: 'ignoreError' }); + beforeEach(() => { store.state.list.loading = false; store.state.list.errors = errorsList; @@ -235,20 +238,30 @@ describe('ErrorTrackingList', () => { }); it('sends the "ignored" status and error ID', () => { - wrapper.find({ ref: 'ignoreError' }).trigger('click'); + ignoreErrorButton().trigger('click'); expect(actions.updateStatus).toHaveBeenCalledWith( expect.anything(), { endpoint: `/project/test/-/error_tracking/${errorsList[0].id}.json`, - redirectUrl: '/error_tracking', status: 'ignored', }, undefined, ); }); + + it('calls an action to remove the item from the list', () => { + ignoreErrorButton().trigger('click'); + expect(actions.removeIgnoredResolvedErrors).toHaveBeenCalledWith( + expect.anything(), + '1', + undefined, + ); + }); }); describe('When the resolve button on an error is clicked', () => { + const resolveErrorButton = () => wrapper.find({ ref: 'resolveError' }); + beforeEach(() => { store.state.list.loading = false; store.state.list.errors = errorsList; @@ -263,17 +276,25 @@ describe('ErrorTrackingList', () => { }); it('sends "resolved" status and error ID', () => { - wrapper.find({ ref: 'resolveError' }).trigger('click'); + resolveErrorButton().trigger('click'); expect(actions.updateStatus).toHaveBeenCalledWith( expect.anything(), { endpoint: `/project/test/-/error_tracking/${errorsList[0].id}.json`, - redirectUrl: '/error_tracking', status: 'resolved', }, undefined, ); }); + + it('calls an action to remove the item from the list', () => { + resolveErrorButton().trigger('click'); + expect(actions.removeIgnoredResolvedErrors).toHaveBeenCalledWith( + expect.anything(), + '1', + undefined, + ); + }); }); describe('When error tracking is disabled and user is not allowed to enable it', () => { diff --git a/spec/frontend/error_tracking/store/list/mutation_spec.js b/spec/frontend/error_tracking/store/list/mutation_spec.js index 44a75b6aa1f..65f11aeeda1 100644 --- a/spec/frontend/error_tracking/store/list/mutation_spec.js +++ b/spec/frontend/error_tracking/store/list/mutation_spec.js @@ -5,6 +5,7 @@ import * as types from '~/error_tracking/store/list/mutation_types'; const ADD_RECENT_SEARCH = mutations[types.ADD_RECENT_SEARCH]; const CLEAR_RECENT_SEARCHES = mutations[types.CLEAR_RECENT_SEARCHES]; const LOAD_RECENT_SEARCHES = mutations[types.LOAD_RECENT_SEARCHES]; +const REMOVE_IGNORED_RESOLVED_ERRORS = mutations[types.REMOVE_IGNORED_RESOLVED_ERRORS]; describe('Error tracking mutations', () => { describe('SET_ERRORS', () => { @@ -114,5 +115,29 @@ describe('Error tracking mutations', () => { expect(localStorage.getItem).toHaveBeenCalledWith('recent-searches/project/errors.json'); }); }); + + describe('REMOVE_IGNORED_RESOLVED_ERRORS', () => { + it('removes ignored or resolved errors from list', () => { + state.errors = [ + { + id: 1, + status: 'unresolved', + }, + { + id: 2, + status: 'ignored', + }, + { + id: 3, + status: 'unresolved', + }, + ]; + const ignoredError = state.errors[2].id; + + REMOVE_IGNORED_RESOLVED_ERRORS(state, ignoredError); + + expect(state.errors).not.toContain(ignoredError); + }); + }); }); }); diff --git a/spec/javascripts/boards/components/issue_card_inner_scoped_label_spec.js b/spec/javascripts/boards/components/issue_card_inner_scoped_label_spec.js deleted file mode 100644 index 6ac51ebdb2d..00000000000 --- a/spec/javascripts/boards/components/issue_card_inner_scoped_label_spec.js +++ /dev/null @@ -1,43 +0,0 @@ -import Vue from 'vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import IssueCardInnerScopedLabel from '~/boards/components/issue_card_inner_scoped_label.vue'; - -describe('IssueCardInnerScopedLabel Component', () => { - let vm; - const Component = Vue.extend(IssueCardInnerScopedLabel); - const props = { - label: { title: 'Foo::Bar', description: 'Some Random Description' }, - labelStyle: { background: 'white', color: 'black' }, - scopedLabelsDocumentationLink: '/docs-link', - }; - const createComponent = () => mountComponent(Component, { ...props }); - - beforeEach(() => { - vm = createComponent(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('should render label title', () => { - expect(vm.$el.querySelector('.color-label').textContent.trim()).toEqual('Foo::Bar'); - }); - - it('should render question mark symbol', () => { - expect(vm.$el.querySelector('.fa-question-circle')).not.toBeNull(); - }); - - it('should render label style provided', () => { - const node = vm.$el.querySelector('.color-label'); - - expect(node.style.background).toEqual(props.labelStyle.background); - expect(node.style.color).toEqual(props.labelStyle.color); - }); - - it('should render the docs link', () => { - expect(vm.$el.querySelector('a.scoped-label').href).toContain( - props.scopedLabelsDocumentationLink, - ); - }); -}); diff --git a/spec/javascripts/boards/components/issue_due_date_spec.js b/spec/javascripts/boards/components/issue_due_date_spec.js deleted file mode 100644 index 68e26b68f04..00000000000 --- a/spec/javascripts/boards/components/issue_due_date_spec.js +++ /dev/null @@ -1,65 +0,0 @@ -import Vue from 'vue'; -import dateFormat from 'dateformat'; -import IssueDueDate from '~/boards/components/issue_due_date.vue'; -import mountComponent from '../../helpers/vue_mount_component_helper'; - -describe('Issue Due Date component', () => { - let vm; - let date; - const Component = Vue.extend(IssueDueDate); - const createComponent = (dueDate = new Date()) => - mountComponent(Component, { date: dateFormat(dueDate, 'yyyy-mm-dd', true) }); - - beforeEach(() => { - date = new Date(); - vm = createComponent(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('should render "Today" if the due date is today', () => { - const timeContainer = vm.$el.querySelector('time'); - - expect(timeContainer.textContent.trim()).toEqual('Today'); - }); - - it('should render "Yesterday" if the due date is yesterday', () => { - date.setDate(date.getDate() - 1); - vm = createComponent(date); - - expect(vm.$el.querySelector('time').textContent.trim()).toEqual('Yesterday'); - }); - - it('should render "Tomorrow" if the due date is one day from now', () => { - date.setDate(date.getDate() + 1); - vm = createComponent(date); - - expect(vm.$el.querySelector('time').textContent.trim()).toEqual('Tomorrow'); - }); - - it('should render day of the week if due date is one week away', () => { - date.setDate(date.getDate() + 5); - vm = createComponent(date); - - expect(vm.$el.querySelector('time').textContent.trim()).toEqual(dateFormat(date, 'dddd')); - }); - - it('should render month and day for other dates', () => { - date.setDate(date.getDate() + 17); - vm = createComponent(date); - const today = new Date(); - const isDueInCurrentYear = today.getFullYear() === date.getFullYear(); - const format = isDueInCurrentYear ? 'mmm d' : 'mmm d, yyyy'; - - expect(vm.$el.querySelector('time').textContent.trim()).toEqual(dateFormat(date, format)); - }); - - it('should contain the correct `.text-danger` css class for overdue issue', () => { - date.setDate(date.getDate() - 17); - vm = createComponent(date); - - expect(vm.$el.querySelector('time').classList.contains('text-danger')).toEqual(true); - }); -}); diff --git a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb index 9d179ef2a49..1580177eaad 100644 --- a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb +++ b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb @@ -100,19 +100,4 @@ describe Banzai::Filter::GollumTagsFilter do expect(doc.at_css('code').text).to eq '[[link-in-backticks]]' end end - - context 'table of contents' do - it 'replaces [[TOC]] with ToC result' do - doc = described_class.call("

[[TOC]]

", { project_wiki: project_wiki }, { toc: "FOO" }) - - expect(doc.to_html).to eq("FOO") - end - - it 'handles an empty ToC result' do - input = "

[[TOC]]

" - doc = described_class.call(input, project_wiki: project_wiki) - - expect(doc.to_html).to eq '' - end - end end diff --git a/spec/lib/banzai/filter/table_of_contents_tag_filter_spec.rb b/spec/lib/banzai/filter/table_of_contents_tag_filter_spec.rb new file mode 100644 index 00000000000..20f32d7347d --- /dev/null +++ b/spec/lib/banzai/filter/table_of_contents_tag_filter_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Banzai::Filter::TableOfContentsTagFilter do + include FilterSpecHelper + + context 'table of contents' do + let(:html) { '

[[TOC]]

' } + + it 'replaces [[TOC]] with ToC result' do + doc = filter(html, {}, { toc: "FOO" }) + + expect(doc.to_html).to eq("FOO") + end + + it 'handles an empty ToC result' do + doc = filter(html) + + expect(doc.to_html).to eq '' + end + end +end diff --git a/spec/lib/banzai/pipeline/full_pipeline_spec.rb b/spec/lib/banzai/pipeline/full_pipeline_spec.rb index f63b86d1451..4fa39da3eb4 100644 --- a/spec/lib/banzai/pipeline/full_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/full_pipeline_spec.rb @@ -99,4 +99,35 @@ describe Banzai::Pipeline::FullPipeline do end end end + + describe 'table of contents' do + let(:project) { create(:project, :public) } + let(:markdown) do + <<-MARKDOWN.strip_heredoc + [[_TOC_]] + + # Header + MARKDOWN + end + let(:invalid_markdown) do + <<-MARKDOWN.strip_heredoc + test [[_TOC_]] + + # Header + MARKDOWN + end + + it 'inserts a table of contents' do + output = described_class.to_html(markdown, project: project) + + expect(output).to include("