From 00c8264faf6e02f741240b5c430cebcebd730280 Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Fri, 24 May 2019 13:11:29 -0500 Subject: Jestify activities spec Also add `jQuery` to global in `test_setup`. This was needed because: - `vendor/jquery.endless-scroll` depends on it. - Adding it to `global` in the spec itself didn't work. --- spec/frontend/activities_spec.js | 70 ++++++++++++++++++++++++++++++++++++ spec/javascripts/activities_spec.js | 71 ------------------------------------- 2 files changed, 70 insertions(+), 71 deletions(-) create mode 100644 spec/frontend/activities_spec.js delete mode 100644 spec/javascripts/activities_spec.js diff --git a/spec/frontend/activities_spec.js b/spec/frontend/activities_spec.js new file mode 100644 index 00000000000..d14be3a1f26 --- /dev/null +++ b/spec/frontend/activities_spec.js @@ -0,0 +1,70 @@ +/* eslint-disable no-unused-expressions, no-prototype-builtins, no-new, no-shadow */ + +import $ from 'jquery'; +import Activities from '~/activities'; +import Pager from '~/pager'; + +describe('Activities', () => { + window.gon || (window.gon = {}); + const fixtureTemplate = 'static/event_filter.html'; + const filters = [ + { + id: 'all', + }, + { + id: 'push', + name: 'push events', + }, + { + id: 'merged', + name: 'merge events', + }, + { + id: 'comments', + }, + { + id: 'team', + }, + ]; + + function getEventName(index) { + const filter = filters[index]; + return filter.hasOwnProperty('name') ? filter.name : filter.id; + } + + function getSelector(index) { + const filter = filters[index]; + return `#${filter.id}_event_filter`; + } + + beforeEach(() => { + loadFixtures(fixtureTemplate); + jest.spyOn(Pager, 'init').mockImplementation(() => {}); + new Activities(); + }); + + for (let i = 0; i < filters.length; i += 1) { + (i => { + describe(`when selecting ${getEventName(i)}`, () => { + beforeEach(() => { + $(getSelector(i)).click(); + }); + + for (let x = 0; x < filters.length; x += 1) { + (x => { + const shouldHighlight = i === x; + const testName = shouldHighlight ? 'should highlight' : 'should not highlight'; + + it(`${testName} ${getEventName(x)}`, () => { + expect( + $(getSelector(x)) + .parent() + .hasClass('active'), + ).toEqual(shouldHighlight); + }); + })(x); + } + }); + })(i); + } +}); diff --git a/spec/javascripts/activities_spec.js b/spec/javascripts/activities_spec.js deleted file mode 100644 index 23b6de7e4e0..00000000000 --- a/spec/javascripts/activities_spec.js +++ /dev/null @@ -1,71 +0,0 @@ -/* eslint-disable no-unused-expressions, no-prototype-builtins, no-new, no-shadow */ - -import $ from 'jquery'; -import 'vendor/jquery.endless-scroll'; -import Activities from '~/activities'; -import Pager from '~/pager'; - -describe('Activities', () => { - window.gon || (window.gon = {}); - const fixtureTemplate = 'static/event_filter.html'; - const filters = [ - { - id: 'all', - }, - { - id: 'push', - name: 'push events', - }, - { - id: 'merged', - name: 'merge events', - }, - { - id: 'comments', - }, - { - id: 'team', - }, - ]; - - function getEventName(index) { - const filter = filters[index]; - return filter.hasOwnProperty('name') ? filter.name : filter.id; - } - - function getSelector(index) { - const filter = filters[index]; - return `#${filter.id}_event_filter`; - } - - beforeEach(() => { - loadFixtures(fixtureTemplate); - spyOn(Pager, 'init').and.stub(); - new Activities(); - }); - - for (let i = 0; i < filters.length; i += 1) { - (i => { - describe(`when selecting ${getEventName(i)}`, () => { - beforeEach(() => { - $(getSelector(i)).click(); - }); - - for (let x = 0; x < filters.length; x += 1) { - (x => { - const shouldHighlight = i === x; - const testName = shouldHighlight ? 'should highlight' : 'should not highlight'; - - it(`${testName} ${getEventName(x)}`, () => { - expect( - $(getSelector(x)) - .parent() - .hasClass('active'), - ).toEqual(shouldHighlight); - }); - })(x); - } - }); - })(i); - } -}); -- cgit v1.2.1 From 2bf5135af31a2ff133f3682f59323954458cc2c3 Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Fri, 24 May 2019 13:12:03 -0500 Subject: Jestify api spec --- spec/frontend/api_spec.js | 477 +++++++++++++++++++++++++++++++++++++++++++ spec/javascripts/api_spec.js | 477 ------------------------------------------- 2 files changed, 477 insertions(+), 477 deletions(-) create mode 100644 spec/frontend/api_spec.js delete mode 100644 spec/javascripts/api_spec.js diff --git a/spec/frontend/api_spec.js b/spec/frontend/api_spec.js new file mode 100644 index 00000000000..13615be1f27 --- /dev/null +++ b/spec/frontend/api_spec.js @@ -0,0 +1,477 @@ +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import Api from '~/api'; + +describe('Api', () => { + const dummyApiVersion = 'v3000'; + const dummyUrlRoot = '/gitlab'; + const dummyGon = { + api_version: dummyApiVersion, + relative_url_root: dummyUrlRoot, + }; + let originalGon; + let mock; + + beforeEach(() => { + mock = new MockAdapter(axios); + originalGon = window.gon; + window.gon = Object.assign({}, dummyGon); + }); + + afterEach(() => { + mock.restore(); + window.gon = originalGon; + }); + + describe('buildUrl', () => { + it('adds URL root and fills in API version', () => { + const input = '/api/:version/foo/bar'; + const expectedOutput = `${dummyUrlRoot}/api/${dummyApiVersion}/foo/bar`; + + const builtUrl = Api.buildUrl(input); + + expect(builtUrl).toEqual(expectedOutput); + }); + + [null, '', '/'].forEach(root => { + it(`works when relative_url_root is ${root}`, () => { + window.gon.relative_url_root = root; + const input = '/api/:version/foo/bar'; + const expectedOutput = `/api/${dummyApiVersion}/foo/bar`; + + const builtUrl = Api.buildUrl(input); + + expect(builtUrl).toEqual(expectedOutput); + }); + }); + }); + + describe('group', () => { + it('fetches a group', done => { + const groupId = '123456'; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}`; + mock.onGet(expectedUrl).reply(200, { + name: 'test', + }); + + Api.group(groupId, response => { + expect(response.name).toBe('test'); + done(); + }); + }); + }); + + describe('groupMembers', () => { + it('fetches group members', done => { + const groupId = '54321'; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}/members`; + const expectedData = [{ id: 7 }]; + mock.onGet(expectedUrl).reply(200, expectedData); + + Api.groupMembers(groupId) + .then(({ data }) => { + expect(data).toEqual(expectedData); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('groups', () => { + it('fetches groups', done => { + const query = 'dummy query'; + const options = { unused: 'option' }; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups.json`; + mock.onGet(expectedUrl).reply(200, [ + { + name: 'test', + }, + ]); + + Api.groups(query, options, response => { + expect(response.length).toBe(1); + expect(response[0].name).toBe('test'); + done(); + }); + }); + }); + + describe('namespaces', () => { + it('fetches namespaces', done => { + const query = 'dummy query'; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/namespaces.json`; + mock.onGet(expectedUrl).reply(200, [ + { + name: 'test', + }, + ]); + + Api.namespaces(query, response => { + expect(response.length).toBe(1); + expect(response[0].name).toBe('test'); + done(); + }); + }); + }); + + describe('projects', () => { + it('fetches projects with membership when logged in', done => { + const query = 'dummy query'; + const options = { unused: 'option' }; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json`; + window.gon.current_user_id = 1; + mock.onGet(expectedUrl).reply(200, [ + { + name: 'test', + }, + ]); + + Api.projects(query, options, response => { + expect(response.length).toBe(1); + expect(response[0].name).toBe('test'); + done(); + }); + }); + + it('fetches projects without membership when not logged in', done => { + const query = 'dummy query'; + const options = { unused: 'option' }; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json`; + mock.onGet(expectedUrl).reply(200, [ + { + name: 'test', + }, + ]); + + Api.projects(query, options, response => { + expect(response.length).toBe(1); + expect(response[0].name).toBe('test'); + done(); + }); + }); + }); + + describe('projectMergeRequests', () => { + const projectPath = 'abc'; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests`; + + it('fetches all merge requests for a project', done => { + const mockData = [{ source_branch: 'foo' }, { source_branch: 'bar' }]; + mock.onGet(expectedUrl).reply(200, mockData); + Api.projectMergeRequests(projectPath) + .then(({ data }) => { + expect(data.length).toEqual(2); + expect(data[0].source_branch).toBe('foo'); + expect(data[1].source_branch).toBe('bar'); + }) + .then(done) + .catch(done.fail); + }); + + it('fetches merge requests filtered with passed params', done => { + const params = { + source_branch: 'bar', + }; + const mockData = [{ source_branch: 'bar' }]; + mock.onGet(expectedUrl, { params }).reply(200, mockData); + + Api.projectMergeRequests(projectPath, params) + .then(({ data }) => { + expect(data.length).toEqual(1); + expect(data[0].source_branch).toBe('bar'); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('projectMergeRequest', () => { + it('fetches a merge request', done => { + const projectPath = 'abc'; + const mergeRequestId = '123456'; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}`; + mock.onGet(expectedUrl).reply(200, { + title: 'test', + }); + + Api.projectMergeRequest(projectPath, mergeRequestId) + .then(({ data }) => { + expect(data.title).toBe('test'); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('projectMergeRequestChanges', () => { + it('fetches the changes of a merge request', done => { + const projectPath = 'abc'; + const mergeRequestId = '123456'; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}/changes`; + mock.onGet(expectedUrl).reply(200, { + title: 'test', + }); + + Api.projectMergeRequestChanges(projectPath, mergeRequestId) + .then(({ data }) => { + expect(data.title).toBe('test'); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('projectMergeRequestVersions', () => { + it('fetches the versions of a merge request', done => { + const projectPath = 'abc'; + const mergeRequestId = '123456'; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}/versions`; + mock.onGet(expectedUrl).reply(200, [ + { + id: 123, + }, + ]); + + Api.projectMergeRequestVersions(projectPath, mergeRequestId) + .then(({ data }) => { + expect(data.length).toBe(1); + expect(data[0].id).toBe(123); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('projectRunners', () => { + it('fetches the runners of a project', done => { + const projectPath = 7; + const params = { scope: 'active' }; + const mockData = [{ id: 4 }]; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/runners`; + mock.onGet(expectedUrl, { params }).reply(200, mockData); + + Api.projectRunners(projectPath, { params }) + .then(({ data }) => { + expect(data).toEqual(mockData); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('newLabel', () => { + it('creates a new label', done => { + const namespace = 'some namespace'; + const project = 'some project'; + const labelData = { some: 'data' }; + const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/labels`; + const expectedData = { + label: labelData, + }; + mock.onPost(expectedUrl).reply(config => { + expect(config.data).toBe(JSON.stringify(expectedData)); + + return [ + 200, + { + name: 'test', + }, + ]; + }); + + Api.newLabel(namespace, project, labelData, response => { + expect(response.name).toBe('test'); + done(); + }); + }); + + it('creates a group label', done => { + const namespace = 'group/subgroup'; + const labelData = { some: 'data' }; + const expectedUrl = Api.buildUrl(Api.groupLabelsPath).replace(':namespace_path', namespace); + const expectedData = { + label: labelData, + }; + mock.onPost(expectedUrl).reply(config => { + expect(config.data).toBe(JSON.stringify(expectedData)); + + return [ + 200, + { + name: 'test', + }, + ]; + }); + + Api.newLabel(namespace, undefined, labelData, response => { + expect(response.name).toBe('test'); + done(); + }); + }); + }); + + describe('groupProjects', () => { + it('fetches group projects', done => { + const groupId = '123456'; + const query = 'dummy query'; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}/projects.json`; + mock.onGet(expectedUrl).reply(200, [ + { + name: 'test', + }, + ]); + + Api.groupProjects(groupId, query, {}, response => { + expect(response.length).toBe(1); + expect(response[0].name).toBe('test'); + done(); + }); + }); + }); + + describe('issueTemplate', () => { + it('fetches an issue template', done => { + const namespace = 'some namespace'; + const project = 'some project'; + const templateKey = ' template #%?.key '; + const templateType = 'template type'; + const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/templates/${templateType}/${encodeURIComponent( + templateKey, + )}`; + mock.onGet(expectedUrl).reply(200, 'test'); + + Api.issueTemplate(namespace, project, templateKey, templateType, (error, response) => { + expect(response).toBe('test'); + done(); + }); + }); + }); + + describe('projectTemplates', () => { + it('fetches a list of templates', done => { + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/gitlab-org%2Fgitlab-ce/templates/licenses`; + + mock.onGet(expectedUrl).reply(200, 'test'); + + Api.projectTemplates('gitlab-org/gitlab-ce', 'licenses', {}, response => { + expect(response).toBe('test'); + done(); + }); + }); + }); + + describe('projectTemplate', () => { + it('fetches a single template', done => { + const data = { unused: 'option' }; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/gitlab-org%2Fgitlab-ce/templates/licenses/test%20license`; + + mock.onGet(expectedUrl).reply(200, 'test'); + + Api.projectTemplate('gitlab-org/gitlab-ce', 'licenses', 'test license', data, response => { + expect(response).toBe('test'); + done(); + }); + }); + }); + + describe('users', () => { + it('fetches users', done => { + const query = 'dummy query'; + const options = { unused: 'option' }; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users.json`; + mock.onGet(expectedUrl).reply(200, [ + { + name: 'test', + }, + ]); + + Api.users(query, options) + .then(({ data }) => { + expect(data.length).toBe(1); + expect(data[0].name).toBe('test'); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('user', () => { + it('fetches single user', done => { + const userId = '123456'; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users/${userId}`; + mock.onGet(expectedUrl).reply(200, { + name: 'testuser', + }); + + Api.user(userId) + .then(({ data }) => { + expect(data.name).toBe('testuser'); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('user status', () => { + it('fetches single user status', done => { + const userId = '123456'; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users/${userId}/status`; + mock.onGet(expectedUrl).reply(200, { + message: 'testmessage', + }); + + Api.userStatus(userId) + .then(({ data }) => { + expect(data.message).toBe('testmessage'); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('commitPipelines', () => { + it('fetches pipelines for a given commit', done => { + const projectId = 'example/foobar'; + const commitSha = 'abc123def'; + const expectedUrl = `${dummyUrlRoot}/${projectId}/commit/${commitSha}/pipelines`; + mock.onGet(expectedUrl).reply(200, [ + { + name: 'test', + }, + ]); + + Api.commitPipelines(projectId, commitSha) + .then(({ data }) => { + expect(data.length).toBe(1); + expect(data[0].name).toBe('test'); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('createBranch', () => { + it('creates new branch', done => { + const ref = 'master'; + const branch = 'new-branch-name'; + const dummyProjectPath = 'gitlab-org/gitlab-ce'; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${encodeURIComponent( + dummyProjectPath, + )}/repository/branches`; + + jest.spyOn(axios, 'post'); + + mock.onPost(expectedUrl).replyOnce(200, { + name: branch, + }); + + Api.createBranch(dummyProjectPath, { ref, branch }) + .then(({ data }) => { + expect(data.name).toBe(branch); + expect(axios.post).toHaveBeenCalledWith(expectedUrl, { ref, branch }); + }) + .then(done) + .catch(done.fail); + }); + }); +}); diff --git a/spec/javascripts/api_spec.js b/spec/javascripts/api_spec.js deleted file mode 100644 index 805bb10bda6..00000000000 --- a/spec/javascripts/api_spec.js +++ /dev/null @@ -1,477 +0,0 @@ -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import Api from '~/api'; - -describe('Api', () => { - const dummyApiVersion = 'v3000'; - const dummyUrlRoot = '/gitlab'; - const dummyGon = { - api_version: dummyApiVersion, - relative_url_root: dummyUrlRoot, - }; - let originalGon; - let mock; - - beforeEach(() => { - mock = new MockAdapter(axios); - originalGon = window.gon; - window.gon = Object.assign({}, dummyGon); - }); - - afterEach(() => { - mock.restore(); - window.gon = originalGon; - }); - - describe('buildUrl', () => { - it('adds URL root and fills in API version', () => { - const input = '/api/:version/foo/bar'; - const expectedOutput = `${dummyUrlRoot}/api/${dummyApiVersion}/foo/bar`; - - const builtUrl = Api.buildUrl(input); - - expect(builtUrl).toEqual(expectedOutput); - }); - - [null, '', '/'].forEach(root => { - it(`works when relative_url_root is ${root}`, () => { - window.gon.relative_url_root = root; - const input = '/api/:version/foo/bar'; - const expectedOutput = `/api/${dummyApiVersion}/foo/bar`; - - const builtUrl = Api.buildUrl(input); - - expect(builtUrl).toEqual(expectedOutput); - }); - }); - }); - - describe('group', () => { - it('fetches a group', done => { - const groupId = '123456'; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}`; - mock.onGet(expectedUrl).reply(200, { - name: 'test', - }); - - Api.group(groupId, response => { - expect(response.name).toBe('test'); - done(); - }); - }); - }); - - describe('groupMembers', () => { - it('fetches group members', done => { - const groupId = '54321'; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}/members`; - const expectedData = [{ id: 7 }]; - mock.onGet(expectedUrl).reply(200, expectedData); - - Api.groupMembers(groupId) - .then(({ data }) => { - expect(data).toEqual(expectedData); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('groups', () => { - it('fetches groups', done => { - const query = 'dummy query'; - const options = { unused: 'option' }; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups.json`; - mock.onGet(expectedUrl).reply(200, [ - { - name: 'test', - }, - ]); - - Api.groups(query, options, response => { - expect(response.length).toBe(1); - expect(response[0].name).toBe('test'); - done(); - }); - }); - }); - - describe('namespaces', () => { - it('fetches namespaces', done => { - const query = 'dummy query'; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/namespaces.json`; - mock.onGet(expectedUrl).reply(200, [ - { - name: 'test', - }, - ]); - - Api.namespaces(query, response => { - expect(response.length).toBe(1); - expect(response[0].name).toBe('test'); - done(); - }); - }); - }); - - describe('projects', () => { - it('fetches projects with membership when logged in', done => { - const query = 'dummy query'; - const options = { unused: 'option' }; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json`; - window.gon.current_user_id = 1; - mock.onGet(expectedUrl).reply(200, [ - { - name: 'test', - }, - ]); - - Api.projects(query, options, response => { - expect(response.length).toBe(1); - expect(response[0].name).toBe('test'); - done(); - }); - }); - - it('fetches projects without membership when not logged in', done => { - const query = 'dummy query'; - const options = { unused: 'option' }; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json`; - mock.onGet(expectedUrl).reply(200, [ - { - name: 'test', - }, - ]); - - Api.projects(query, options, response => { - expect(response.length).toBe(1); - expect(response[0].name).toBe('test'); - done(); - }); - }); - }); - - describe('projectMergeRequests', () => { - const projectPath = 'abc'; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests`; - - it('fetches all merge requests for a project', done => { - const mockData = [{ source_branch: 'foo' }, { source_branch: 'bar' }]; - mock.onGet(expectedUrl).reply(200, mockData); - Api.projectMergeRequests(projectPath) - .then(({ data }) => { - expect(data.length).toEqual(2); - expect(data[0].source_branch).toBe('foo'); - expect(data[1].source_branch).toBe('bar'); - }) - .then(done) - .catch(done.fail); - }); - - it('fetches merge requests filtered with passed params', done => { - const params = { - source_branch: 'bar', - }; - const mockData = [{ source_branch: 'bar' }]; - mock.onGet(expectedUrl, { params }).reply(200, mockData); - - Api.projectMergeRequests(projectPath, params) - .then(({ data }) => { - expect(data.length).toEqual(1); - expect(data[0].source_branch).toBe('bar'); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('projectMergeRequest', () => { - it('fetches a merge request', done => { - const projectPath = 'abc'; - const mergeRequestId = '123456'; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}`; - mock.onGet(expectedUrl).reply(200, { - title: 'test', - }); - - Api.projectMergeRequest(projectPath, mergeRequestId) - .then(({ data }) => { - expect(data.title).toBe('test'); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('projectMergeRequestChanges', () => { - it('fetches the changes of a merge request', done => { - const projectPath = 'abc'; - const mergeRequestId = '123456'; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}/changes`; - mock.onGet(expectedUrl).reply(200, { - title: 'test', - }); - - Api.projectMergeRequestChanges(projectPath, mergeRequestId) - .then(({ data }) => { - expect(data.title).toBe('test'); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('projectMergeRequestVersions', () => { - it('fetches the versions of a merge request', done => { - const projectPath = 'abc'; - const mergeRequestId = '123456'; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}/versions`; - mock.onGet(expectedUrl).reply(200, [ - { - id: 123, - }, - ]); - - Api.projectMergeRequestVersions(projectPath, mergeRequestId) - .then(({ data }) => { - expect(data.length).toBe(1); - expect(data[0].id).toBe(123); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('projectRunners', () => { - it('fetches the runners of a project', done => { - const projectPath = 7; - const params = { scope: 'active' }; - const mockData = [{ id: 4 }]; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/runners`; - mock.onGet(expectedUrl, { params }).reply(200, mockData); - - Api.projectRunners(projectPath, { params }) - .then(({ data }) => { - expect(data).toEqual(mockData); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('newLabel', () => { - it('creates a new label', done => { - const namespace = 'some namespace'; - const project = 'some project'; - const labelData = { some: 'data' }; - const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/labels`; - const expectedData = { - label: labelData, - }; - mock.onPost(expectedUrl).reply(config => { - expect(config.data).toBe(JSON.stringify(expectedData)); - - return [ - 200, - { - name: 'test', - }, - ]; - }); - - Api.newLabel(namespace, project, labelData, response => { - expect(response.name).toBe('test'); - done(); - }); - }); - - it('creates a group label', done => { - const namespace = 'group/subgroup'; - const labelData = { some: 'data' }; - const expectedUrl = Api.buildUrl(Api.groupLabelsPath).replace(':namespace_path', namespace); - const expectedData = { - label: labelData, - }; - mock.onPost(expectedUrl).reply(config => { - expect(config.data).toBe(JSON.stringify(expectedData)); - - return [ - 200, - { - name: 'test', - }, - ]; - }); - - Api.newLabel(namespace, undefined, labelData, response => { - expect(response.name).toBe('test'); - done(); - }); - }); - }); - - describe('groupProjects', () => { - it('fetches group projects', done => { - const groupId = '123456'; - const query = 'dummy query'; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}/projects.json`; - mock.onGet(expectedUrl).reply(200, [ - { - name: 'test', - }, - ]); - - Api.groupProjects(groupId, query, {}, response => { - expect(response.length).toBe(1); - expect(response[0].name).toBe('test'); - done(); - }); - }); - }); - - describe('issueTemplate', () => { - it('fetches an issue template', done => { - const namespace = 'some namespace'; - const project = 'some project'; - const templateKey = ' template #%?.key '; - const templateType = 'template type'; - const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/templates/${templateType}/${encodeURIComponent( - templateKey, - )}`; - mock.onGet(expectedUrl).reply(200, 'test'); - - Api.issueTemplate(namespace, project, templateKey, templateType, (error, response) => { - expect(response).toBe('test'); - done(); - }); - }); - }); - - describe('projectTemplates', () => { - it('fetches a list of templates', done => { - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/gitlab-org%2Fgitlab-ce/templates/licenses`; - - mock.onGet(expectedUrl).reply(200, 'test'); - - Api.projectTemplates('gitlab-org/gitlab-ce', 'licenses', {}, response => { - expect(response).toBe('test'); - done(); - }); - }); - }); - - describe('projectTemplate', () => { - it('fetches a single template', done => { - const data = { unused: 'option' }; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/gitlab-org%2Fgitlab-ce/templates/licenses/test%20license`; - - mock.onGet(expectedUrl).reply(200, 'test'); - - Api.projectTemplate('gitlab-org/gitlab-ce', 'licenses', 'test license', data, response => { - expect(response).toBe('test'); - done(); - }); - }); - }); - - describe('users', () => { - it('fetches users', done => { - const query = 'dummy query'; - const options = { unused: 'option' }; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users.json`; - mock.onGet(expectedUrl).reply(200, [ - { - name: 'test', - }, - ]); - - Api.users(query, options) - .then(({ data }) => { - expect(data.length).toBe(1); - expect(data[0].name).toBe('test'); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('user', () => { - it('fetches single user', done => { - const userId = '123456'; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users/${userId}`; - mock.onGet(expectedUrl).reply(200, { - name: 'testuser', - }); - - Api.user(userId) - .then(({ data }) => { - expect(data.name).toBe('testuser'); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('user status', () => { - it('fetches single user status', done => { - const userId = '123456'; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users/${userId}/status`; - mock.onGet(expectedUrl).reply(200, { - message: 'testmessage', - }); - - Api.userStatus(userId) - .then(({ data }) => { - expect(data.message).toBe('testmessage'); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('commitPipelines', () => { - it('fetches pipelines for a given commit', done => { - const projectId = 'example/foobar'; - const commitSha = 'abc123def'; - const expectedUrl = `${dummyUrlRoot}/${projectId}/commit/${commitSha}/pipelines`; - mock.onGet(expectedUrl).reply(200, [ - { - name: 'test', - }, - ]); - - Api.commitPipelines(projectId, commitSha) - .then(({ data }) => { - expect(data.length).toBe(1); - expect(data[0].name).toBe('test'); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('createBranch', () => { - it('creates new branch', done => { - const ref = 'master'; - const branch = 'new-branch-name'; - const dummyProjectPath = 'gitlab-org/gitlab-ce'; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${encodeURIComponent( - dummyProjectPath, - )}/repository/branches`; - - spyOn(axios, 'post').and.callThrough(); - - mock.onPost(expectedUrl).replyOnce(200, { - name: branch, - }); - - Api.createBranch(dummyProjectPath, { ref, branch }) - .then(({ data }) => { - expect(data.name).toBe(branch); - expect(axios.post).toHaveBeenCalledWith(expectedUrl, { ref, branch }); - }) - .then(done) - .catch(done.fail); - }); - }); -}); -- cgit v1.2.1 From a9850b25c3624fe31d0230ad3f0df00fec6b7d48 Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Fri, 24 May 2019 13:14:24 -0500 Subject: Jestify autosave spec and add localStorage helper The helper was needed because `jest.spyOn` would not work on this special window object property. The only way we could successfully spy on `localStorage` was with this helper. --- spec/frontend/autosave_spec.js | 151 +++++++++++++++++++++++++ spec/frontend/helpers/local_storage_helper.js | 41 +++++++ spec/javascripts/autosave_spec.js | 154 -------------------------- 3 files changed, 192 insertions(+), 154 deletions(-) create mode 100644 spec/frontend/autosave_spec.js create mode 100644 spec/frontend/helpers/local_storage_helper.js delete mode 100644 spec/javascripts/autosave_spec.js diff --git a/spec/frontend/autosave_spec.js b/spec/frontend/autosave_spec.js new file mode 100644 index 00000000000..4d9c8f96d62 --- /dev/null +++ b/spec/frontend/autosave_spec.js @@ -0,0 +1,151 @@ +import $ from 'jquery'; +import Autosave from '~/autosave'; +import AccessorUtilities from '~/lib/utils/accessor'; +import { useLocalStorageSpy } from 'helpers/local_storage_helper'; + +describe('Autosave', () => { + useLocalStorageSpy(); + + let autosave; + const field = $(''); + const key = 'key'; + + describe('class constructor', () => { + beforeEach(() => { + jest.spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').mockReturnValue(true); + jest.spyOn(Autosave.prototype, 'restore').mockImplementation(() => {}); + }); + + it('should set .isLocalStorageAvailable', () => { + autosave = new Autosave(field, key); + + expect(AccessorUtilities.isLocalStorageAccessSafe).toHaveBeenCalled(); + expect(autosave.isLocalStorageAvailable).toBe(true); + }); + }); + + describe('restore', () => { + beforeEach(() => { + autosave = { + field, + key, + }; + }); + + describe('if .isLocalStorageAvailable is `false`', () => { + beforeEach(() => { + autosave.isLocalStorageAvailable = false; + + Autosave.prototype.restore.call(autosave); + }); + + it('should not call .getItem', () => { + expect(window.localStorage.getItem).not.toHaveBeenCalled(); + }); + }); + + describe('if .isLocalStorageAvailable is `true`', () => { + beforeEach(() => { + autosave.isLocalStorageAvailable = true; + }); + + it('should call .getItem', () => { + Autosave.prototype.restore.call(autosave); + + expect(window.localStorage.getItem).toHaveBeenCalledWith(key); + }); + + it('triggers jquery event', () => { + jest.spyOn(autosave.field, 'trigger').mockImplementation(() => {}); + + Autosave.prototype.restore.call(autosave); + + expect(field.trigger).toHaveBeenCalled(); + }); + + it('triggers native event', done => { + autosave.field.get(0).addEventListener('change', () => { + done(); + }); + + Autosave.prototype.restore.call(autosave); + }); + }); + + describe('if field gets deleted from DOM', () => { + beforeEach(() => { + autosave.field = $('.not-a-real-element'); + }); + + it('does not trigger event', () => { + jest.spyOn(field, 'trigger'); + + expect(field.trigger).not.toHaveBeenCalled(); + }); + }); + }); + + describe('save', () => { + beforeEach(() => { + autosave = { reset: jest.fn() }; + autosave.field = field; + field.val('value'); + }); + + describe('if .isLocalStorageAvailable is `false`', () => { + beforeEach(() => { + autosave.isLocalStorageAvailable = false; + + Autosave.prototype.save.call(autosave); + }); + + it('should not call .setItem', () => { + expect(window.localStorage.setItem).not.toHaveBeenCalled(); + }); + }); + + describe('if .isLocalStorageAvailable is `true`', () => { + beforeEach(() => { + autosave.isLocalStorageAvailable = true; + + Autosave.prototype.save.call(autosave); + }); + + it('should call .setItem', () => { + expect(window.localStorage.setItem).toHaveBeenCalled(); + }); + }); + }); + + describe('reset', () => { + beforeEach(() => { + autosave = { + key, + }; + }); + + describe('if .isLocalStorageAvailable is `false`', () => { + beforeEach(() => { + autosave.isLocalStorageAvailable = false; + + Autosave.prototype.reset.call(autosave); + }); + + it('should not call .removeItem', () => { + expect(window.localStorage.removeItem).not.toHaveBeenCalled(); + }); + }); + + describe('if .isLocalStorageAvailable is `true`', () => { + beforeEach(() => { + autosave.isLocalStorageAvailable = true; + + Autosave.prototype.reset.call(autosave); + }); + + it('should call .removeItem', () => { + expect(window.localStorage.removeItem).toHaveBeenCalledWith(key); + }); + }); + }); +}); diff --git a/spec/frontend/helpers/local_storage_helper.js b/spec/frontend/helpers/local_storage_helper.js new file mode 100644 index 00000000000..48e66b11767 --- /dev/null +++ b/spec/frontend/helpers/local_storage_helper.js @@ -0,0 +1,41 @@ +/** + * Manage the instance of a custom `window.localStorage` + * + * This only encapsulates the setup / teardown logic so that it can easily be + * reused with different implementations (i.e. a spy or a [fake][1]) + * + * [1]: https://stackoverflow.com/a/41434763/1708147 + * + * @param {() => any} fn Function that returns the object to use for localStorage + */ +const useLocalStorage = fn => { + const origLocalStorage = window.localStorage; + let currentLocalStorage; + + Object.defineProperty(window, 'localStorage', { + get: () => currentLocalStorage, + }); + + beforeEach(() => { + currentLocalStorage = fn(); + }); + + afterEach(() => { + currentLocalStorage = origLocalStorage; + }); +}; + +/** + * Create an object with the localStorage interface but `jest.fn()` implementations. + */ +export const createLocalStorageSpy = () => ({ + clear: jest.fn(), + getItem: jest.fn(), + setItem: jest.fn(), + removeItem: jest.fn(), +}); + +/** + * Before each test, overwrite `window.localStorage` with a spy implementation. + */ +export const useLocalStorageSpy = () => useLocalStorage(createLocalStorageSpy); diff --git a/spec/javascripts/autosave_spec.js b/spec/javascripts/autosave_spec.js deleted file mode 100644 index dcb1c781591..00000000000 --- a/spec/javascripts/autosave_spec.js +++ /dev/null @@ -1,154 +0,0 @@ -import $ from 'jquery'; -import Autosave from '~/autosave'; -import AccessorUtilities from '~/lib/utils/accessor'; - -describe('Autosave', () => { - let autosave; - const field = $(''); - const key = 'key'; - - describe('class constructor', () => { - beforeEach(() => { - spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').and.returnValue(true); - spyOn(Autosave.prototype, 'restore'); - }); - - it('should set .isLocalStorageAvailable', () => { - autosave = new Autosave(field, key); - - expect(AccessorUtilities.isLocalStorageAccessSafe).toHaveBeenCalled(); - expect(autosave.isLocalStorageAvailable).toBe(true); - }); - }); - - describe('restore', () => { - beforeEach(() => { - autosave = { - field, - key, - }; - - spyOn(window.localStorage, 'getItem'); - }); - - describe('if .isLocalStorageAvailable is `false`', () => { - beforeEach(() => { - autosave.isLocalStorageAvailable = false; - - Autosave.prototype.restore.call(autosave); - }); - - it('should not call .getItem', () => { - expect(window.localStorage.getItem).not.toHaveBeenCalled(); - }); - }); - - describe('if .isLocalStorageAvailable is `true`', () => { - beforeEach(() => { - autosave.isLocalStorageAvailable = true; - }); - - it('should call .getItem', () => { - Autosave.prototype.restore.call(autosave); - - expect(window.localStorage.getItem).toHaveBeenCalledWith(key); - }); - - it('triggers jquery event', () => { - spyOn(autosave.field, 'trigger').and.callThrough(); - - Autosave.prototype.restore.call(autosave); - - expect(field.trigger).toHaveBeenCalled(); - }); - - it('triggers native event', done => { - autosave.field.get(0).addEventListener('change', () => { - done(); - }); - - Autosave.prototype.restore.call(autosave); - }); - }); - - describe('if field gets deleted from DOM', () => { - beforeEach(() => { - autosave.field = $('.not-a-real-element'); - }); - - it('does not trigger event', () => { - spyOn(field, 'trigger').and.callThrough(); - - expect(field.trigger).not.toHaveBeenCalled(); - }); - }); - }); - - describe('save', () => { - beforeEach(() => { - autosave = jasmine.createSpyObj('autosave', ['reset']); - autosave.field = field; - field.val('value'); - - spyOn(window.localStorage, 'setItem'); - }); - - describe('if .isLocalStorageAvailable is `false`', () => { - beforeEach(() => { - autosave.isLocalStorageAvailable = false; - - Autosave.prototype.save.call(autosave); - }); - - it('should not call .setItem', () => { - expect(window.localStorage.setItem).not.toHaveBeenCalled(); - }); - }); - - describe('if .isLocalStorageAvailable is `true`', () => { - beforeEach(() => { - autosave.isLocalStorageAvailable = true; - - Autosave.prototype.save.call(autosave); - }); - - it('should call .setItem', () => { - expect(window.localStorage.setItem).toHaveBeenCalled(); - }); - }); - }); - - describe('reset', () => { - beforeEach(() => { - autosave = { - key, - }; - - spyOn(window.localStorage, 'removeItem'); - }); - - describe('if .isLocalStorageAvailable is `false`', () => { - beforeEach(() => { - autosave.isLocalStorageAvailable = false; - - Autosave.prototype.reset.call(autosave); - }); - - it('should not call .removeItem', () => { - expect(window.localStorage.removeItem).not.toHaveBeenCalled(); - }); - }); - - describe('if .isLocalStorageAvailable is `true`', () => { - beforeEach(() => { - autosave.isLocalStorageAvailable = true; - - Autosave.prototype.reset.call(autosave); - }); - - it('should call .removeItem', () => { - expect(window.localStorage.removeItem).toHaveBeenCalledWith(key); - }); - }); - }); -}); -- cgit v1.2.1