diff --git a/webapp/package.json b/webapp/package.json index 8d7c6278d..2e823dd31 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -101,6 +101,7 @@ "eslint-plugin-promise": "~4.2.1", "eslint-plugin-standard": "~4.0.0", "eslint-plugin-vue": "~5.2.3", + "flush-promises": "^1.0.2", "fuse.js": "^3.4.5", "jest": "~24.8.0", "node-sass": "~4.12.0", @@ -111,4 +112,4 @@ "vue-jest": "~3.0.4", "vue-svg-loader": "~0.12.0" } -} \ No newline at end of file +} diff --git a/webapp/pages/settings/my-social-media.spec.js b/webapp/pages/settings/my-social-media.spec.js index 0babe9f2f..b14204dcd 100644 --- a/webapp/pages/settings/my-social-media.spec.js +++ b/webapp/pages/settings/my-social-media.spec.js @@ -1,4 +1,5 @@ import { mount, createLocalVue } from '@vue/test-utils' +import flushPromises from 'flush-promises' import MySocialMedia from './my-social-media.vue' import Vuex from 'vuex' import Styleguide from '@human-connection/styleguide' @@ -12,23 +13,17 @@ localVue.use(Filters) describe('my-social-media.vue', () => { let wrapper - let store let mocks let getters - let input - let submitBtn const socialMediaUrl = 'https://freeradical.zone/@mattwr18' + const newSocialMediaUrl = 'https://twitter.com/mattwr18' + const faviconUrl = 'https://freeradical.zone/favicon.ico' beforeEach(() => { mocks = { $t: jest.fn(), $apollo: { - mutate: jest - .fn() - .mockRejectedValue({ message: 'Ouch!' }) - .mockResolvedValueOnce({ - data: { CreateSocialMeda: { id: 's1', url: socialMediaUrl } }, - }), + mutate: jest.fn(), }, $toast: { error: jest.fn(), @@ -43,75 +38,152 @@ describe('my-social-media.vue', () => { }) describe('mount', () => { + let form, input, submitButton const Wrapper = () => { - store = new Vuex.Store({ + const store = new Vuex.Store({ getters, }) return mount(MySocialMedia, { store, mocks, localVue }) } - it('renders', () => { - wrapper = Wrapper() - expect(wrapper.contains('div')).toBe(true) - }) - - describe('given currentUser has a social media account linked', () => { + describe('adding social media link', () => { beforeEach(() => { - getters = { - 'auth/user': () => { - return { - socialMedia: [{ id: 's1', url: socialMediaUrl }], - } - }, - } - }) - - it("displays a link to the currentUser's social media", () => { wrapper = Wrapper() - const socialMediaLink = wrapper.find('a').attributes().href - expect(socialMediaLink).toBe(socialMediaUrl) + form = wrapper.find('form') + input = wrapper.find('input#addSocialMedia') + submitButton = wrapper.find('button') }) - beforeEach(() => { - mocks = { - $t: jest.fn(), - $apollo: { - mutate: jest - .fn() - .mockRejectedValue({ message: 'Ouch!' }) - .mockResolvedValueOnce({ - data: { DeleteSocialMeda: { id: 's1', url: socialMediaUrl } }, - }), - }, - $toast: { - error: jest.fn(), - success: jest.fn(), - }, - } - getters = { - 'auth/user': () => { - return { - socialMedia: [{ id: 's1', url: socialMediaUrl }], - } - }, - } + it('requires the link to be a valid url', () => { + input.setValue('some value') + form.trigger('submit') + + expect(mocks.$apollo.mutate).not.toHaveBeenCalled() }) - it('displays a trash sympol after a social media and allows the user to delete it', () => { - wrapper = Wrapper() - const deleteSelector = wrapper.find({ name: 'delete' }) - expect(deleteSelector).toEqual({ selector: 'Component' }) - const icon = wrapper.find({ name: 'trash' }) - icon.trigger('click') - expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1) + it('displays an error message when not saved successfully', async () => { + mocks.$apollo.mutate.mockRejectedValue({ message: 'Ouch!' }) + input.setValue(newSocialMediaUrl) + form.trigger('submit') + + await flushPromises() + + expect(mocks.$toast.error).toHaveBeenCalledTimes(1) + }) + + describe('success', () => { + beforeEach(() => { + mocks.$apollo.mutate.mockResolvedValue({ + data: { CreateSocialMeda: { id: 's2', url: newSocialMediaUrl } }, + }) + input.setValue(newSocialMediaUrl) + form.trigger('submit') + }) + + it('sends the new url to the backend', () => { + const expected = expect.objectContaining({ + variables: { url: newSocialMediaUrl }, + }) + + expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1) + expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected) + }) + + it('displays a success message', async () => { + await flushPromises() + + expect(mocks.$toast.success).toHaveBeenCalledTimes(1) + }) + + it('clears the form', async () => { + await flushPromises() + + expect(input.value).toBe(undefined) + expect(submitButton.vm.$attrs.disabled).toBe(true) + }) }) }) - describe('currentUser does not have a social media account linked', () => { - it('allows a user to add a social media link', () => { + describe('given existing social media links', () => { + beforeEach(() => { + getters = { + 'auth/user': () => ({ + socialMedia: [{ id: 's1', url: socialMediaUrl }], + }), + } + wrapper = Wrapper() - wrapper.find('form').trigger('submit') - expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1) + form = wrapper.find('form') + }) + + it('displays the links', () => { + expect(wrapper.find(`img[src="${faviconUrl}"]`).exists()).toBe(true) + expect(wrapper.find(`a[href="${socialMediaUrl}"]`).exists()).toBe(true) + expect(wrapper.find('a[name="edit"]').exists()).toBe(true) + expect(wrapper.find('a[name="delete"]').exists()).toBe(true) + }) + + it('does not accept a duplicate url', () => { + input = wrapper.find('input#addSocialMedia') + + input.setValue(socialMediaUrl) + form.trigger('submit') + + expect(mocks.$apollo.mutate).not.toHaveBeenCalled() + }) + + describe('editing social media link', () => { + beforeEach(() => { + const editButton = wrapper.find('a[name="edit"]') + editButton.trigger('click') + input = wrapper.find('input#editSocialMedia') + }) + + it('disables adding new links while editing', () => { + const addInput = wrapper.find('input#addSocialMedia') + + expect(addInput.exists()).toBe(false) + }) + + it('sends the new url to the backend', () => { + const expected = expect.objectContaining({ + variables: { id: 's1', url: newSocialMediaUrl }, + }) + input.setValue(newSocialMediaUrl) + form.trigger('submit') + + expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1) + expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected) + }) + + it('allows the user to cancel editing', () => { + const cancelButton = wrapper.find('button#cancel') + cancelButton.trigger('click') + + expect(wrapper.find('input#editSocialMedia').exists()).toBe(false) + }) + }) + + describe('deleting social media link', () => { + beforeEach(() => { + const deleteButton = wrapper.find('a[name="delete"]') + deleteButton.trigger('click') + }) + + it('sends the link id to the backend', () => { + const expected = expect.objectContaining({ + variables: { id: 's1' }, + }) + + expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1) + expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected) + }) + + it('displays a success message', async () => { + await flushPromises() + + expect(mocks.$toast.success).toHaveBeenCalledTimes(1) + }) }) }) }) diff --git a/webapp/pages/settings/my-social-media.vue b/webapp/pages/settings/my-social-media.vue index 37688d430..dcc6414ad 100644 --- a/webapp/pages/settings/my-social-media.vue +++ b/webapp/pages/settings/my-social-media.vue @@ -16,14 +16,20 @@