diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-12-09 15:09:42 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-12-09 15:09:42 +0300 |
commit | 1361891b0a87187364d1586395df176a8984e914 (patch) | |
tree | 4f47ddf2cd0d06cd0eb98a7bf1b7001504e6416b /spec | |
parent | 109562e64e1e1c51fe32a7443df86ee63b856115 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
21 files changed, 457 insertions, 124 deletions
diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb index a408d821833..d82fff1f7ae 100644 --- a/spec/controllers/import/github_controller_spec.rb +++ b/spec/controllers/import/github_controller_spec.rb @@ -123,26 +123,33 @@ RSpec.describe Import::GithubController do end it 'fetches repos using latest github client' do - expect_next_instance_of(Gitlab::GithubImport::Client) do |client| - expect(client).to receive(:each_page).with(:repos).and_return([].to_enum) + expect_next_instance_of(Octokit::Client) do |client| + expect(client).to receive(:repos).and_return([].to_enum) end get :status end - it 'concatenates list of repos from multiple pages' do - repo_1 = OpenStruct.new(login: 'emacs', full_name: 'asd/emacs', name: 'emacs', owner: { login: 'owner' }) - repo_2 = OpenStruct.new(login: 'vim', full_name: 'asd/vim', name: 'vim', owner: { login: 'owner' }) - repos = [OpenStruct.new(objects: [repo_1]), OpenStruct.new(objects: [repo_2])].to_enum + context 'pagination' do + context 'when no page is specified' do + it 'requests first page' do + expect_next_instance_of(Octokit::Client) do |client| + expect(client).to receive(:repos).with(nil, { page: 1, per_page: 25 }).and_return([].to_enum) + end - allow(stub_client).to receive(:each_page).and_return(repos) + get :status + end + end - get :status, format: :json + context 'when page is specified' do + it 'requests repos with specified page' do + expect_next_instance_of(Octokit::Client) do |client| + expect(client).to receive(:repos).with(nil, { page: 2, per_page: 25 }).and_return([].to_enum) + end - expect(response).to have_gitlab_http_status(:ok) - expect(json_response.dig('provider_repos').count).to eq(2) - expect(json_response.dig('provider_repos', 0, 'id')).to eq(repo_1.id) - expect(json_response.dig('provider_repos', 1, 'id')).to eq(repo_2.id) + get :status, params: { page: 2 } + end + end end context 'when filtering' do @@ -150,6 +157,7 @@ RSpec.describe Import::GithubController do let(:user_login) { 'user' } let(:collaborations_subquery) { 'repo:repo1 repo:repo2' } let(:organizations_subquery) { 'org:org1 org:org2' } + let(:search_query) { "test in:name is:public,private user:#{user_login} #{collaborations_subquery} #{organizations_subquery}" } before do allow_next_instance_of(Octokit::Client) do |client| @@ -158,20 +166,56 @@ RSpec.describe Import::GithubController do end it 'makes request to github search api' do - expected_query = "test in:name is:public,private user:#{user_login} #{collaborations_subquery} #{organizations_subquery}" + expect_next_instance_of(Octokit::Client) do |client| + expect(client).to receive(:user).and_return(double(login: user_login)) + expect(client).to receive(:search_repositories).with(search_query, { page: 1, per_page: 25 }).and_return({ items: [].to_enum }) + end expect_next_instance_of(Gitlab::GithubImport::Client) do |client| expect(client).to receive(:collaborations_subquery).and_return(collaborations_subquery) expect(client).to receive(:organizations_subquery).and_return(organizations_subquery) - expect(client).to receive(:each_page).with(:search_repositories, expected_query).and_return([].to_enum) end get :status, params: { filter: filter }, format: :json end + context 'pagination' do + context 'when no page is specified' do + it 'requests first page' do + expect_next_instance_of(Octokit::Client) do |client| + expect(client).to receive(:user).and_return(double(login: user_login)) + expect(client).to receive(:search_repositories).with(search_query, { page: 1, per_page: 25 }).and_return({ items: [].to_enum }) + end + + expect_next_instance_of(Gitlab::GithubImport::Client) do |client| + expect(client).to receive(:collaborations_subquery).and_return(collaborations_subquery) + expect(client).to receive(:organizations_subquery).and_return(organizations_subquery) + end + + get :status, params: { filter: filter }, format: :json + end + end + + context 'when page is specified' do + it 'requests repos with specified page' do + expect_next_instance_of(Octokit::Client) do |client| + expect(client).to receive(:user).and_return(double(login: user_login)) + expect(client).to receive(:search_repositories).with(search_query, { page: 2, per_page: 25 }).and_return({ items: [].to_enum }) + end + + expect_next_instance_of(Gitlab::GithubImport::Client) do |client| + expect(client).to receive(:collaborations_subquery).and_return(collaborations_subquery) + expect(client).to receive(:organizations_subquery).and_return(organizations_subquery) + end + + get :status, params: { filter: filter, page: 2 }, format: :json + end + end + end + context 'when user input contains colons and spaces' do before do - stub_client(search_repos_by_name: []) + allow(controller).to receive(:client_repos).and_return([]) end it 'sanitizes user input' do diff --git a/spec/factories/experiment_subjects.rb b/spec/factories/experiment_subjects.rb new file mode 100644 index 00000000000..c35bc370bad --- /dev/null +++ b/spec/factories/experiment_subjects.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :experiment_subject do + experiment + user + variant { :control } + end +end diff --git a/spec/features/admin/users/user_spec.rb b/spec/features/admin/users/user_spec.rb index 46380218e91..e7dd50ed514 100644 --- a/spec/features/admin/users/user_spec.rb +++ b/spec/features/admin/users/user_spec.rb @@ -9,6 +9,7 @@ RSpec.describe 'Admin::Users::User' do before do sign_in(current_user) gitlab_enable_admin_mode_sign_in(current_user) + stub_feature_flags(vue_admin_users: false) end describe 'GET /admin/users/:id' do diff --git a/spec/features/admin/users/users_spec.rb b/spec/features/admin/users/users_spec.rb index afabdcf4fb7..9482b4f8603 100644 --- a/spec/features/admin/users/users_spec.rb +++ b/spec/features/admin/users/users_spec.rb @@ -15,6 +15,7 @@ RSpec.describe 'Admin::Users' do describe 'GET /admin/users' do before do + stub_feature_flags(vue_admin_users: false) visit admin_users_path end @@ -418,6 +419,7 @@ RSpec.describe 'Admin::Users' do describe 'GET /admin/users/:id/edit' do before do + stub_feature_flags(vue_admin_users: false) visit admin_users_path click_link "edit_user_#{user.id}" end diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb index f341709b73d..239bc04a9cb 100644 --- a/spec/features/profiles/user_edit_profile_spec.rb +++ b/spec/features/profiles/user_edit_profile_spec.rb @@ -228,7 +228,7 @@ RSpec.describe 'User edit profile' do end def open_edit_status_modal - open_modal 'Edit status' + open_modal 'Edit status' end def set_user_status_in_modal @@ -291,6 +291,10 @@ RSpec.describe 'User edit profile' do toggle_busy_status set_user_status_in_modal + + wait_for_requests + visit root_path(user) + open_edit_status_modal expect(busy_status.checked?).to eq(true) @@ -368,26 +372,37 @@ RSpec.describe 'User edit profile' do expect(page).not_to have_selector '.cover-status' end - it 'clears the user status with the "Remove status" button' do - user_status = create(:user_status, user: user, message: 'Eating bread', emoji: 'stuffed_flatbread') + context 'Remove status button' do + before do + user.status = UserStatus.new(message: 'Eating bread', emoji: 'stuffed_flatbread') - visit_user - wait_for_requests + visit_user + wait_for_requests - within('.cover-status') do - expect(page).to have_emoji(user_status.emoji) - expect(page).to have_content user_status.message + open_edit_status_modal + + page.within "#set-user-status-modal" do + click_button 'Remove status' + end + + wait_for_requests end - open_edit_status_modal + it 'clears the user status with the "Remove status" button' do + visit_user - page.within "#set-user-status-modal" do - click_button 'Remove status' + expect(page).not_to have_selector '.cover-status' end - visit_user + it 'shows the "Set status" menu item in the user menu' do + visit root_path(user) - expect(page).not_to have_selector '.cover-status' + find('.header-user-dropdown-toggle').click + + page.within ".header-user" do + expect(page).to have_content('Set status') + end + end end it 'displays a default emoji if only message is entered' do diff --git a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb index 189aa45ff75..ca8e4fdac57 100644 --- a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb +++ b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb @@ -165,7 +165,7 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons' do context 'when the project does not have a README' do it 'shows the single file editor "Add README" button' do - allow(project.repository).to receive(:readme).and_return(nil) + allow(project.repository).to receive(:readme_path).and_return(nil) visit project_path(project) diff --git a/spec/fixtures/api/schemas/entities/admin_users_data_attributes_paths.json b/spec/fixtures/api/schemas/entities/admin_users_data_attributes_paths.json new file mode 100644 index 00000000000..eab8b626876 --- /dev/null +++ b/spec/fixtures/api/schemas/entities/admin_users_data_attributes_paths.json @@ -0,0 +1,30 @@ +{ + "type": "object", + "properties": { + "edit": { "type": "string" }, + "approve": { "type": "string" }, + "reject": { "type": "string" }, + "unblock": { "type": "string" }, + "block": { "type": "string" }, + "deactivate": { "type": "string" }, + "activate": { "type": "string" }, + "unlock": { "type": "string" }, + "delete": { "type": "string" }, + "delete_with_contributions": { "type": "string" }, + "admin_user": { "type": "string" } + }, + "required": [ + "edit", + "approve", + "reject", + "unblock", + "block", + "deactivate", + "activate", + "unlock", + "delete", + "delete_with_contributions", + "admin_user" + ], + "additionalProperties": false +} diff --git a/spec/frontend/admin/users/index_spec.js b/spec/frontend/admin/users/index_spec.js new file mode 100644 index 00000000000..171d54c8f4f --- /dev/null +++ b/spec/frontend/admin/users/index_spec.js @@ -0,0 +1,35 @@ +import { createWrapper } from '@vue/test-utils'; +import initAdminUsers from '~/admin/users'; +import AdminUsersApp from '~/admin/users/components/app.vue'; +import { users, paths } from './mock_data'; + +describe('initAdminUsersApp', () => { + let wrapper; + let el; + + const findApp = () => wrapper.find(AdminUsersApp); + + beforeEach(() => { + el = document.createElement('div'); + el.setAttribute('data-users', JSON.stringify(users)); + el.setAttribute('data-paths', JSON.stringify(paths)); + + document.body.appendChild(el); + + wrapper = createWrapper(initAdminUsers(el)); + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + el.remove(); + el = null; + }); + + it('parses and passes props', () => { + expect(findApp().props()).toMatchObject({ + users, + paths, + }); + }); +}); diff --git a/spec/frontend/admin/users/mock_data.js b/spec/frontend/admin/users/mock_data.js new file mode 100644 index 00000000000..b80d04454b0 --- /dev/null +++ b/spec/frontend/admin/users/mock_data.js @@ -0,0 +1,29 @@ +export const users = [ + { + id: 2177, + name: 'Nikki', + createdAt: '2020-11-13T12:26:54.177Z', + email: 'nikki@example.com', + username: 'nikki', + lastActivityOn: null, + avatarUrl: + 'https://secure.gravatar.com/avatar/054f062d8b1a42b123f17e13a173cda8?s=80\\u0026d=identicon', + badges: [], + projectsCount: 0, + actions: [], + }, +]; + +export const paths = { + edit: '/admin/users/id/edit', + approve: '/admin/users/id/approve', + reject: '/admin/users/id/reject', + unblock: '/admin/users/id/unblock', + block: '/admin/users/id/block', + deactivate: '/admin/users/id/deactivate', + activate: '/admin/users/id/activate', + unlock: '/admin/users/id/unlock', + delete: '/admin/users/id', + deleteWithContributions: '/admin/users/id', + adminUser: '/admin/users/id', +}; diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js index 360665c77be..c55b01da5cc 100644 --- a/spec/frontend/boards/stores/actions_spec.js +++ b/spec/frontend/boards/stores/actions_spec.js @@ -18,6 +18,9 @@ import issueMoveListMutation from '~/boards/graphql/issue_move_list.mutation.gra import destroyBoardListMutation from '~/boards/graphql/board_list_destroy.mutation.graphql'; import updateAssignees from '~/vue_shared/components/sidebar/queries/updateAssignees.mutation.graphql'; import { fullBoardId, formatListIssues, formatBoardLists } from '~/boards/boards_util'; +import createFlash from '~/flash'; + +jest.mock('~/flash'); const expectNotImplemented = action => { it('is not implemented', () => { @@ -666,46 +669,59 @@ describe('setAssignees', () => { const refPath = `${projectPath}#3`; const iid = '1'; - beforeEach(() => { - jest.spyOn(gqlClient, 'mutate').mockResolvedValue({ - data: { issueSetAssignees: { issue: { assignees: { nodes: [{ ...node }] } } } }, + describe('when succeeds', () => { + beforeEach(() => { + jest.spyOn(gqlClient, 'mutate').mockResolvedValue({ + data: { issueSetAssignees: { issue: { assignees: { nodes: [{ ...node }] } } } }, + }); }); - }); - it('calls mutate with the correct values', async () => { - await actions.setAssignees( - { commit: () => {}, getters: { activeIssue: { iid, referencePath: refPath } } }, - [name], - ); + it('calls mutate with the correct values', async () => { + await actions.setAssignees( + { commit: () => {}, getters: { activeIssue: { iid, referencePath: refPath } } }, + [name], + ); - expect(gqlClient.mutate).toHaveBeenCalledWith({ - mutation: updateAssignees, - variables: { iid, assigneeUsernames: [name], projectPath }, + expect(gqlClient.mutate).toHaveBeenCalledWith({ + mutation: updateAssignees, + variables: { iid, assigneeUsernames: [name], projectPath }, + }); + }); + + it('calls the correct mutation with the correct values', done => { + testAction( + actions.setAssignees, + {}, + { activeIssue: { iid, referencePath: refPath }, commit: () => {} }, + [ + { type: types.SET_ASSIGNEE_LOADING, payload: true }, + { + type: 'UPDATE_ISSUE_BY_ID', + payload: { prop: 'assignees', issueId: undefined, value: [node] }, + }, + { type: types.SET_ASSIGNEE_LOADING, payload: false }, + ], + [], + done, + ); }); }); - it('calls the correct mutation with the correct values', done => { - testAction( - actions.setAssignees, - {}, - { activeIssue: { iid, referencePath: refPath }, commit: () => {} }, - [ - { - type: 'SET_ASSIGNEE_LOADING', - payload: true, - }, - { - type: 'UPDATE_ISSUE_BY_ID', - payload: { prop: 'assignees', issueId: undefined, value: [node] }, - }, - { - type: 'SET_ASSIGNEE_LOADING', - payload: false, - }, - ], - [], - done, - ); + describe('when fails', () => { + beforeEach(() => { + jest.spyOn(gqlClient, 'mutate').mockRejectedValue(); + }); + + it('calls createFlash', async () => { + await actions.setAssignees({ + commit: () => {}, + getters: { activeIssue: { iid, referencePath: refPath } }, + }); + + expect(createFlash).toHaveBeenCalledWith({ + message: 'An error occurred while updating assignees.', + }); + }); }); }); diff --git a/spec/frontend/helpers/vue_test_utils_helper.js b/spec/frontend/helpers/vue_test_utils_helper.js index ead898f04d3..0e9127b5c65 100644 --- a/spec/frontend/helpers/vue_test_utils_helper.js +++ b/spec/frontend/helpers/vue_test_utils_helper.js @@ -1,3 +1,5 @@ +import { isArray } from 'lodash'; + const vNodeContainsText = (vnode, text) => (vnode.text && vnode.text.includes(text)) || (vnode.children && vnode.children.filter(child => vNodeContainsText(child, text)).length); @@ -34,9 +36,18 @@ export const waitForMutation = (store, expectedMutationType) => }); }); -export const extendedWrapper = wrapper => - Object.defineProperty(wrapper, 'findByTestId', { +export const extendedWrapper = wrapper => { + if (isArray(wrapper) || !wrapper?.find) { + // eslint-disable-next-line no-console + console.warn( + '[vue-test-utils-helper]: you are trying to extend an object that is not a VueWrapper.', + ); + return wrapper; + } + + return Object.defineProperty(wrapper, 'findByTestId', { value(id) { return this.find(`[data-testid="${id}"]`); }, }); +}; diff --git a/spec/frontend/helpers/vue_test_utils_helper_spec.js b/spec/frontend/helpers/vue_test_utils_helper_spec.js index 41714066da5..31c4ccd5dbb 100644 --- a/spec/frontend/helpers/vue_test_utils_helper_spec.js +++ b/spec/frontend/helpers/vue_test_utils_helper_spec.js @@ -1,5 +1,5 @@ import { shallowMount } from '@vue/test-utils'; -import { shallowWrapperContainsSlotText } from './vue_test_utils_helper'; +import { extendedWrapper, shallowWrapperContainsSlotText } from './vue_test_utils_helper'; describe('Vue test utils helpers', () => { describe('shallowWrapperContainsSlotText', () => { @@ -45,4 +45,48 @@ describe('Vue test utils helpers', () => { expect(shallowWrapperContainsSlotText(mockComponent, 'namedSlot', searchText)).toBe(false); }); }); + + describe('extendedWrapper', () => { + describe('when an invalid wrapper is provided', () => { + beforeEach(() => { + // eslint-disable-next-line no-console + console.warn = jest.fn(); + }); + + it.each` + wrapper + ${{}} + ${[]} + ${null} + ${undefined} + ${1} + ${''} + `('should warn with an error when the wrapper is $wrapper', ({ wrapper }) => { + extendedWrapper(wrapper); + /* eslint-disable no-console */ + expect(console.warn).toHaveBeenCalled(); + expect(console.warn).toHaveBeenCalledWith( + '[vue-test-utils-helper]: you are trying to extend an object that is not a VueWrapper.', + ); + /* eslint-enable no-console */ + }); + }); + + describe('findByTestId', () => { + const testId = 'a-component'; + let mockComponent; + + beforeEach(() => { + mockComponent = extendedWrapper( + shallowMount({ + template: `<div data-testid="${testId}"></div>`, + }), + ); + }); + + it('should find the component by test id', () => { + expect(mockComponent.findByTestId(testId).exists()).toBe(true); + }); + }); + }); }); diff --git a/spec/frontend/packages/details/components/package_history_spec.js b/spec/frontend/packages/details/components/package_history_spec.js index f745a457b0a..311aa9e7e68 100644 --- a/spec/frontend/packages/details/components/package_history_spec.js +++ b/spec/frontend/packages/details/components/package_history_spec.js @@ -2,6 +2,7 @@ import { shallowMount } from '@vue/test-utils'; import { GlLink, GlSprintf } from '@gitlab/ui'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import HistoryItem from '~/vue_shared/components/registry/history_item.vue'; +import { HISTORY_PIPELINES_LIMIT } from '~/packages/details/constants'; import component from '~/packages/details/components/package_history.vue'; import { mavenPackage, mockPipelineInfo } from '../../mock_data'; @@ -13,6 +14,9 @@ describe('Package History', () => { packageEntity: { ...mavenPackage }, }; + const createPipelines = amount => + [...Array(amount)].map((x, index) => ({ ...mockPipelineInfo, id: index + 1 })); + const mountComponent = props => { wrapper = shallowMount(component, { propsData: { ...defaultProps, ...props }, @@ -56,55 +60,58 @@ describe('Package History', () => { expect.arrayContaining(['timeline', 'main-notes-list', 'notes']), ); }); - describe.each` - name | icon | text | timeAgoTooltip | link - ${'created-on'} | ${'clock'} | ${'Test package version 1.0.0 was created'} | ${mavenPackage.created_at} | ${null} - ${'updated-at'} | ${'pencil'} | ${'Test package version 1.0.0 was updated'} | ${mavenPackage.updated_at} | ${null} - ${'commit'} | ${'commit'} | ${'Commit sha-baz on branch branch-name'} | ${null} | ${mockPipelineInfo.project.commit_url} - ${'pipeline'} | ${'pipeline'} | ${'Pipeline #1 triggered by foo'} | ${mockPipelineInfo.created_at} | ${mockPipelineInfo.project.pipeline_url} - ${'published'} | ${'package'} | ${'Published to the baz project Package Registry'} | ${mavenPackage.created_at} | ${null} - `('history element $name', ({ name, icon, text, timeAgoTooltip, link }) => { - let element; - - beforeEach(() => { - mountComponent({ packageEntity: { ...mavenPackage, pipeline: mockPipelineInfo } }); - element = findHistoryElement(name); - }); - - it('has the correct icon', () => { - expect(element.props('icon')).toBe(icon); - }); - - it('has the correct text', () => { - expect(element.text()).toBe(text); - }); - - it('time-ago tooltip', () => { - const timeAgo = findElementTimeAgo(element); - const exist = Boolean(timeAgoTooltip); - - expect(timeAgo.exists()).toBe(exist); - if (exist) { - expect(timeAgo.props('time')).toBe(timeAgoTooltip); - } - }); - - it('link', () => { - const linkElement = findElementLink(element); - const exist = Boolean(link); - - expect(linkElement.exists()).toBe(exist); - if (exist) { - expect(linkElement.attributes('href')).toBe(link); - } - }); - }); - - describe('when pipelineInfo is missing', () => { - it.each(['commit', 'pipeline'])('%s history element is hidden', name => { - mountComponent(); - expect(findHistoryElement(name).exists()).toBe(false); - }); - }); + name | amount | icon | text | timeAgoTooltip | link + ${'created-on'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'clock'} | ${'Test package version 1.0.0 was first created'} | ${mavenPackage.created_at} | ${null} + ${'first-pipeline-commit'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'commit'} | ${'Created by commit #sha-baz on branch branch-name'} | ${null} | ${mockPipelineInfo.project.commit_url} + ${'first-pipeline-pipeline'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'pipeline'} | ${'Built by pipeline #1 triggered by foo'} | ${mockPipelineInfo.created_at} | ${mockPipelineInfo.project.pipeline_url} + ${'published'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'package'} | ${'Published to the baz project Package Registry'} | ${mavenPackage.created_at} | ${null} + ${'archived'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'history'} | ${'Package has 1 archived update'} | ${null} | ${null} + ${'archived'} | ${HISTORY_PIPELINES_LIMIT + 3} | ${'history'} | ${'Package has 2 archived updates'} | ${null} | ${null} + ${'pipeline-entry'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'pencil'} | ${'Package updated by commit #sha-baz on branch branch-name, built by pipeline #3, and published to the registry'} | ${mavenPackage.created_at} | ${mockPipelineInfo.project.commit_url} + `( + 'with $amount pipelines history element $name', + ({ name, icon, text, timeAgoTooltip, link, amount }) => { + let element; + + beforeEach(() => { + mountComponent({ + packageEntity: { ...mavenPackage, pipelines: createPipelines(amount) }, + }); + element = findHistoryElement(name); + }); + + it('exists', () => { + expect(element.exists()).toBe(true); + }); + + it('has the correct icon', () => { + expect(element.props('icon')).toBe(icon); + }); + + it('has the correct text', () => { + expect(element.text()).toBe(text); + }); + + it('time-ago tooltip', () => { + const timeAgo = findElementTimeAgo(element); + const exist = Boolean(timeAgoTooltip); + + expect(timeAgo.exists()).toBe(exist); + if (exist) { + expect(timeAgo.props('time')).toBe(timeAgoTooltip); + } + }); + + it('link', () => { + const linkElement = findElementLink(element); + const exist = Boolean(link); + + expect(linkElement.exists()).toBe(exist); + if (exist) { + expect(linkElement.attributes('href')).toBe(link); + } + }); + }, + ); }); diff --git a/spec/helpers/tree_helper_spec.rb b/spec/helpers/tree_helper_spec.rb index 620bf248d7b..136ec07e73d 100644 --- a/spec/helpers/tree_helper_spec.rb +++ b/spec/helpers/tree_helper_spec.rb @@ -216,6 +216,24 @@ RSpec.describe TreeHelper do web_ide_url: "/-/ide/project/#{project.full_path}/edit/#{sha}/-/#{@path}" ) end + + it 'does not load blob from repository again' do + blob + + expect(repository).not_to receive(:blob_at) + + subject + end + end + + context 'nil blob is passed' do + let(:blob) { nil } + + it 'does not load blob from repository' do + expect(repository).not_to receive(:blob_at) + + subject + end end context 'user does not have write access but a personal fork exists' do diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb index 5b559e40a80..c92c6e6e78e 100644 --- a/spec/helpers/users_helper_spec.rb +++ b/spec/helpers/users_helper_spec.rb @@ -333,4 +333,21 @@ RSpec.describe UsersHelper do allow(helper).to receive(:can?).with(current_user, :read_user_profile, user).and_return(allowed) end end + + describe '#admin_users_data_attributes' do + subject(:data) { helper.admin_users_data_attributes([user]) } + + it 'users matches the serialized json' do + entity = double + expect_next_instance_of(Admin::UserSerializer) do |instance| + expect(instance).to receive(:represent).with([user]).and_return(entity) + end + expect(entity).to receive(:to_json).and_return("{\"username\":\"admin\"}") + expect(data[:users]).to eq "{\"username\":\"admin\"}" + end + + it 'paths matches the schema' do + expect(data[:paths]).to match_schema('entities/admin_users_data_attributes_paths') + end + end end diff --git a/spec/lib/gitlab/danger/commit_linter_spec.rb b/spec/lib/gitlab/danger/commit_linter_spec.rb index 35d9f5021cc..368193decda 100644 --- a/spec/lib/gitlab/danger/commit_linter_spec.rb +++ b/spec/lib/gitlab/danger/commit_linter_spec.rb @@ -192,7 +192,9 @@ RSpec.describe Gitlab::Danger::CommitLinter do '[Ci skip] A commit message', '[API] A commit message', 'api: A commit message', - 'API: A commit message' + 'API: A commit message', + 'API: a commit message', + 'API: a commit message' ].each do |message| context "when subject is '#{message}'" do let(:commit_message) { message } @@ -209,8 +211,6 @@ RSpec.describe Gitlab::Danger::CommitLinter do '[ci skip]A commit message', '[Ci skip] A commit message', '[ci skip] a commit message', - 'API: a commit message', - 'API: a commit message', 'api: a commit message', '! A commit message' ].each do |message| diff --git a/spec/lib/gitlab/github_import/client_spec.rb b/spec/lib/gitlab/github_import/client_spec.rb index e6dd156f3d8..4000e0b2611 100644 --- a/spec/lib/gitlab/github_import/client_spec.rb +++ b/spec/lib/gitlab/github_import/client_spec.rb @@ -500,7 +500,7 @@ RSpec.describe Gitlab::GithubImport::Client do it 'searches for repositories based on name' do expected_search_query = 'test in:name is:public,private user:user repo:repo1 repo:repo2 org:org1 org:org2' - expect(client).to receive(:each_page).with(:search_repositories, expected_search_query) + expect(client.octokit).to receive(:search_repositories).with(expected_search_query, {}) client.search_repos_by_name('test') end diff --git a/spec/models/experiment_spec.rb b/spec/models/experiment_spec.rb index 4106630ea20..1bf7b8b4850 100644 --- a/spec/models/experiment_spec.rb +++ b/spec/models/experiment_spec.rb @@ -7,6 +7,7 @@ RSpec.describe Experiment do describe 'associations' do it { is_expected.to have_many(:experiment_users) } + it { is_expected.to have_many(:experiment_subjects) } end describe 'validations' do diff --git a/spec/models/experiment_subject_spec.rb b/spec/models/experiment_subject_spec.rb new file mode 100644 index 00000000000..4850814c5f5 --- /dev/null +++ b/spec/models/experiment_subject_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ExperimentSubject, type: :model do + describe 'associations' do + it { is_expected.to belong_to(:experiment) } + it { is_expected.to belong_to(:user) } + it { is_expected.to belong_to(:group) } + it { is_expected.to belong_to(:project) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:experiment) } + + describe 'must_have_one_subject_present' do + let(:experiment_subject) { build(:experiment_subject, user: nil, group: nil, project: nil) } + let(:error_message) { 'Must have exactly one of User, Group, or Project.' } + + it 'fails when no subject is present' do + expect(experiment_subject).not_to be_valid + expect(experiment_subject.errors[:base]).to include(error_message) + end + + it 'passes when user subject is present' do + experiment_subject.user = build(:user) + expect(experiment_subject).to be_valid + end + + it 'passes when group subject is present' do + experiment_subject.group = build(:group) + expect(experiment_subject).to be_valid + end + + it 'passes when project subject is present' do + experiment_subject.project = build(:project) + expect(experiment_subject).to be_valid + end + + it 'fails when more than one subject is present', :aggregate_failures do + # two subjects + experiment_subject.user = build(:user) + experiment_subject.group = build(:group) + expect(experiment_subject).not_to be_valid + expect(experiment_subject.errors[:base]).to include(error_message) + + # three subjects + experiment_subject.project = build(:project) + expect(experiment_subject).not_to be_valid + expect(experiment_subject.errors[:base]).to include(error_message) + end + end + end +end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 31211f8ff2c..c1f073e26d1 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -2335,7 +2335,7 @@ RSpec.describe Repository do end it 'caches the response' do - expect(repository).to receive(:readme).and_call_original.once + expect(repository.head_tree).to receive(:readme_path).and_call_original.once 2.times do expect(repository.readme_path).to eq("README.md") diff --git a/spec/presenters/project_presenter_spec.rb b/spec/presenters/project_presenter_spec.rb index b7fee5253f8..a9050c233af 100644 --- a/spec/presenters/project_presenter_spec.rb +++ b/spec/presenters/project_presenter_spec.rb @@ -375,7 +375,7 @@ RSpec.describe ProjectPresenter do it 'returns anchor data' do project.add_developer(user) - allow(project.repository).to receive(:readme).and_return(nil) + allow(project.repository).to receive(:readme_path).and_return(nil) expect(presenter.readme_anchor_data).to have_attributes( is_link: false, @@ -387,7 +387,7 @@ RSpec.describe ProjectPresenter do context 'when README exists' do it 'returns anchor data' do - allow(project.repository).to receive(:readme).and_return(double(name: 'readme')) + allow(project.repository).to receive(:readme_path).and_return('readme') expect(presenter.readme_anchor_data).to have_attributes( is_link: false, @@ -561,7 +561,7 @@ RSpec.describe ProjectPresenter do let(:project) { build_stubbed(:project) } it 'orders the items correctly' do - allow(project.repository).to receive(:readme).and_return(double(name: 'readme')) + allow(project.repository).to receive(:readme_path).and_return('readme') allow(project.repository).to receive(:license_blob).and_return(nil) allow(project.repository).to receive(:changelog).and_return(nil) allow(project.repository).to receive(:contribution_guide).and_return(double(name: 'foo')) |