diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9c67126e2..2bec6cebb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -267,7 +267,7 @@ jobs: report_name: Coverage Webapp type: lcov result_path: ./coverage/lcov.info - min_coverage: 52 + min_coverage: 65 token: ${{ github.token }} ############################################################################## diff --git a/webapp/components/ComponentSlider/ComponentSlider.spec.js b/webapp/components/ComponentSlider/ComponentSlider.spec.js new file mode 100644 index 000000000..25bf3e7f4 --- /dev/null +++ b/webapp/components/ComponentSlider/ComponentSlider.spec.js @@ -0,0 +1,64 @@ +import { mount } from '@vue/test-utils' +import ComponentSlider from './ComponentSlider.vue' + +const localVue = global.localVue + +describe('ComponentSlider.vue', () => { + let wrapper + let mocks + let propsData + + beforeEach(() => { + mocks = { + $t: jest.fn(), + } + propsData = { + sliderData: { + sliderIndex: 0, + sliderSelectorCallback: jest.fn().mockResolvedValue(true), + sliders: [ + { + validated: true, + button: { + icon: 'smile', + callback: jest.fn().mockResolvedValue(true), + sliderCallback: jest.fn().mockResolvedValue(true), + }, + }, + { + validated: true, + button: { + icon: 'smile', + callback: jest.fn().mockResolvedValue(true), + sliderCallback: jest.fn().mockResolvedValue(true), + }, + }, + ], + }, + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(ComponentSlider, { + mocks, + localVue, + propsData, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('div')).toBe(true) + }) + + it('click on next Button', async () => { + await wrapper.find('.base-button[data-test="next-button"]').trigger('click') + await wrapper.vm.$nextTick() + expect(propsData.sliderData.sliderSelectorCallback).toHaveBeenCalled() + }) + }) +}) diff --git a/webapp/components/FollowButton.spec.js b/webapp/components/FollowButton.spec.js new file mode 100644 index 000000000..000745081 --- /dev/null +++ b/webapp/components/FollowButton.spec.js @@ -0,0 +1,47 @@ +import { mount } from '@vue/test-utils' +import FollowButton from './FollowButton.vue' + +const localVue = global.localVue + +describe('FollowButton.vue', () => { + let mocks + let propsData + + beforeEach(() => { + mocks = { + $t: jest.fn(), + $apollo: { + mutate: jest.fn(), + }, + } + propsData = {} + }) + + describe('mount', () => { + let wrapper + const Wrapper = () => { + return mount(FollowButton, { mocks, propsData, localVue }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders button and text', () => { + expect(mocks.$t).toHaveBeenCalledWith('followButton.follow') + expect(wrapper.findAll('.base-button')).toHaveLength(1) + }) + + it('renders button and text when followed', () => { + propsData.isFollowed = true + wrapper = Wrapper() + expect(mocks.$t).toHaveBeenCalledWith('followButton.following') + expect(wrapper.findAll('.base-button')).toHaveLength(1) + }) + + it.skip('toggle the button', async () => { + wrapper.find('.base-button').trigger('click') // This does not work since @click.prevent is used + expect(wrapper.vm.isFollowed).toBe(true) + }) + }) +}) diff --git a/webapp/components/InviteButton/InviteButton.spec.js b/webapp/components/InviteButton/InviteButton.spec.js new file mode 100644 index 000000000..f28045612 --- /dev/null +++ b/webapp/components/InviteButton/InviteButton.spec.js @@ -0,0 +1,53 @@ +import { config, mount } from '@vue/test-utils' +import InviteButton from './InviteButton.vue' + +config.stubs['v-popover'] = '' + +describe('InviteButton.vue', () => { + let wrapper + let mocks + let propsData + + beforeEach(() => { + mocks = { + $t: jest.fn(), + navigator: { + clipboard: { + writeText: jest.fn(), + }, + }, + } + propsData = {} + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(InviteButton, { mocks, propsData }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.contains('.invite-button')).toBe(true) + }) + + it('open popup', () => { + wrapper.find('.base-button').trigger('click') + expect(wrapper.contains('.invite-button')).toBe(true) + }) + + it('invite codes not available', async () => { + wrapper.find('.base-button').trigger('click') // open popup + wrapper.find('.invite-button').trigger('click') // click copy button + expect(mocks.$t).toHaveBeenCalledWith('invite-codes.not-available') + }) + + it.skip('invite codes copied to clipboard', async () => { + wrapper.find('.base-button').trigger('click') // open popup + wrapper.find('.invite-button').trigger('click') // click copy button + expect(mocks.$t).toHaveBeenCalledWith('invite-codes.not-available') + }) + }) +}) diff --git a/webapp/components/Logo/Logo.spec.js b/webapp/components/Logo/Logo.spec.js new file mode 100644 index 000000000..a712a529b --- /dev/null +++ b/webapp/components/Logo/Logo.spec.js @@ -0,0 +1,29 @@ +import { mount } from '@vue/test-utils' +import Logo from './Logo.vue' + +const localVue = global.localVue + +describe('Logo.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: jest.fn(), + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(Logo, { mocks, localVue }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.findAll('.ds-logo')).toHaveLength(1) + }) + }) +}) diff --git a/webapp/components/Modal.spec.js b/webapp/components/Modal.spec.js index 3ebff8771..c08c90f51 100644 --- a/webapp/components/Modal.spec.js +++ b/webapp/components/Modal.spec.js @@ -131,6 +131,42 @@ describe('Modal.vue', () => { }) }) }) + + describe('store/modal data contains an user', () => { + it('passes user name to report modal', () => { + state.data = { + type: 'user', + resource: { + id: 'u456', + name: 'Username', + }, + } + wrapper = Wrapper() + expect(wrapper.find(DisableModal).props()).toEqual({ + type: 'user', + name: 'Username', + id: 'u456', + }) + }) + }) + + describe('store/modal data contains no valid datatype', () => { + it('passes something as datatype to modal', () => { + state.data = { + type: 'something', + resource: { + id: 's456', + name: 'Username', + }, + } + wrapper = Wrapper() + expect(wrapper.find(DisableModal).props()).toEqual({ + type: 'something', + name: null, + id: 's456', + }) + }) + }) }) }) }) diff --git a/webapp/components/NotificationList/NotificationList.spec.js b/webapp/components/NotificationList/NotificationList.spec.js index ce20a2765..219c1fdbb 100644 --- a/webapp/components/NotificationList/NotificationList.spec.js +++ b/webapp/components/NotificationList/NotificationList.spec.js @@ -81,4 +81,23 @@ describe('NotificationList.vue', () => { }) }) }) + + describe('shallowMount with no notifications', () => { + const Wrapper = () => { + return shallowMount(NotificationList, { + propsData: {}, + mocks, + store, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders Notification.vue zero times', () => { + expect(wrapper.findAll(Notification)).toHaveLength(0) + }) + }) }) diff --git a/webapp/components/PageFooter/PageFooter.spec.js b/webapp/components/PageFooter/PageFooter.spec.js new file mode 100644 index 000000000..0edc0fed2 --- /dev/null +++ b/webapp/components/PageFooter/PageFooter.spec.js @@ -0,0 +1,44 @@ +import { config, mount } from '@vue/test-utils' +import PageFooter from './PageFooter.vue' +import links from '~/constants/links.js' + +const localVue = global.localVue + +config.stubs['nuxt-link'] = '' + +describe('PageFooter.vue', () => { + let mocks + + beforeEach(() => { + mocks = { + $t: jest.fn(), + $env: { + VERSION: 'v1.0.0', + }, + links, + } + }) + + describe('mount', () => { + let wrapper + const Wrapper = () => { + return mount(PageFooter, { mocks, localVue }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders three links', () => { + expect(wrapper.findAll('a')).toHaveLength(3) + }) + + it('renders four nuxt-links', () => { + expect(wrapper.findAll('.nuxt-link')).toHaveLength(4) + }) + + it('renders version', () => { + expect(wrapper.find('.ds-footer').text()).toContain('v1.0.0') + }) + }) +}) diff --git a/webapp/components/Password/Change.spec.js b/webapp/components/Password/Change.spec.js index 8416a0fce..95b7c1a3a 100644 --- a/webapp/components/Password/Change.spec.js +++ b/webapp/components/Password/Change.spec.js @@ -126,21 +126,18 @@ describe('ChangePassword.vue', () => { }) }) - // TODO This is not a valid testcase - we have to decide if we catch the same password on clientside - /* describe('mutation rejects', () => { + describe('mutation rejects', () => { beforeEach(async () => { await wrapper.find('input#oldPassword').setValue('supersecret') await wrapper.find('input#password').setValue('supersecret') await wrapper.find('input#passwordConfirmation').setValue('supersecret') + await wrapper.find('form').trigger('submit') }) it('displays error message', async () => { - await wrapper.find('form').trigger('submit') - await mocks.$apollo.mutate - expect(mocks.$toast.error).toHaveBeenCalledWith('Ouch!') }) - }) */ + }) }) }) }) diff --git a/webapp/components/PasswordReset/ChangePassword.spec.js b/webapp/components/PasswordReset/ChangePassword.spec.js index b1b55cb06..d6f451604 100644 --- a/webapp/components/PasswordReset/ChangePassword.spec.js +++ b/webapp/components/PasswordReset/ChangePassword.spec.js @@ -76,6 +76,22 @@ describe('ChangePassword ', () => { }) }) }) + + describe('password reset not successful', () => { + beforeEach(() => { + mocks.$apollo.mutate = jest.fn().mockRejectedValue({ + message: 'Ouch!', + }) + wrapper = Wrapper() + wrapper.find('input#password').setValue('supersecret') + wrapper.find('input#passwordConfirmation').setValue('supersecret') + wrapper.find('form').trigger('submit') + }) + + it('display a toast error', () => { + expect(mocks.$toast.error).toHaveBeenCalled() + }) + }) }) }) }) diff --git a/webapp/components/PasswordReset/Request.spec.js b/webapp/components/PasswordReset/Request.spec.js index 83459814e..e601030c6 100644 --- a/webapp/components/PasswordReset/Request.spec.js +++ b/webapp/components/PasswordReset/Request.spec.js @@ -95,5 +95,20 @@ describe('Request', () => { expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected) }) }) + + describe('backend throws an error', () => { + beforeEach(() => { + mocks.$apollo.mutate = jest.fn().mockRejectedValue({ + message: 'Ouch!', + }) + wrapper = Wrapper() + wrapper.find('input#email').setValue('mail@gmail.com') + wrapper.find('form').trigger('submit') + }) + + it('display a toast error', () => { + expect(mocks.$toast.error).toHaveBeenCalled() + }) + }) }) }) diff --git a/webapp/components/Registration/Signup.spec.js b/webapp/components/Registration/Signup.spec.js index 36b16903c..dda0cbb9d 100644 --- a/webapp/components/Registration/Signup.spec.js +++ b/webapp/components/Registration/Signup.spec.js @@ -76,6 +76,21 @@ describe('Signup', () => { expect(mocks.$t).toHaveBeenCalledWith(...expected) }) + describe('mutation is rejected', () => { + beforeEach(async () => { + mocks.$apollo.mutate = jest.fn().mockRejectedValue({ + message: 'Ouch!', + }) + wrapper = Wrapper() + wrapper.find('input#email').setValue('mail@example.org') + await wrapper.find('form').trigger('submit') + }) + + it('displays error message', async () => { + expect(mocks.$toast.error).toHaveBeenCalledWith('Ouch!') + }) + }) + describe('after animation', () => { beforeEach(jest.runAllTimers) diff --git a/webapp/components/ShoutButton.spec.js b/webapp/components/ShoutButton.spec.js new file mode 100644 index 000000000..c3af134c1 --- /dev/null +++ b/webapp/components/ShoutButton.spec.js @@ -0,0 +1,57 @@ +import { mount } from '@vue/test-utils' +import ShoutButton from './ShoutButton.vue' +import Vue from 'vue' + +const localVue = global.localVue + +describe('ShoutButton.vue', () => { + let mocks + + beforeEach(() => { + mocks = { + $t: jest.fn(), + $apollo: { + mutate: jest.fn(), + }, + } + }) + + describe('mount', () => { + let wrapper + const Wrapper = () => { + return mount(ShoutButton, { mocks, localVue }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders button and text', () => { + expect(mocks.$t).toHaveBeenCalledWith('shoutButton.shouted') + expect(wrapper.findAll('.base-button')).toHaveLength(1) + expect(wrapper.findAll('.shout-button-text')).toHaveLength(1) + expect(wrapper.vm.shouted).toBe(false) + expect(wrapper.vm.shoutedCount).toBe(0) + }) + + it('toggle the button', async () => { + mocks.$apollo.mutate = jest.fn().mockResolvedValue({ data: { shout: 'WeDoShout' } }) + wrapper.find('.base-button').trigger('click') + expect(wrapper.vm.shouted).toBe(true) + expect(wrapper.vm.shoutedCount).toBe(1) + await Vue.nextTick() + expect(wrapper.vm.shouted).toBe(true) + expect(wrapper.vm.shoutedCount).toBe(1) + }) + + it('toggle the button, but backend fails', async () => { + mocks.$apollo.mutate = jest.fn().mockRejectedValue({ message: 'Ouch!' }) + await wrapper.find('.base-button').trigger('click') + expect(wrapper.vm.shouted).toBe(true) + expect(wrapper.vm.shoutedCount).toBe(1) + await Vue.nextTick() + expect(wrapper.vm.shouted).toBe(false) + expect(wrapper.vm.shoutedCount).toBe(0) + }) + }) +}) diff --git a/webapp/components/features/ReportsTable/ReportsTable.spec.js b/webapp/components/features/ReportsTable/ReportsTable.spec.js index a9baeea4f..c80e4fea5 100644 --- a/webapp/components/features/ReportsTable/ReportsTable.spec.js +++ b/webapp/components/features/ReportsTable/ReportsTable.spec.js @@ -34,7 +34,7 @@ describe('ReportsTable', () => { describe('given no reports', () => { beforeEach(() => { - propsData = { ...propsData, reports: [] } + propsData = { ...propsData } wrapper = Wrapper() }) diff --git a/webapp/components/generic/SearchableInput/SearchableInput.spec.js b/webapp/components/generic/SearchableInput/SearchableInput.spec.js index 53c361997..e0e9f9831 100644 --- a/webapp/components/generic/SearchableInput/SearchableInput.spec.js +++ b/webapp/components/generic/SearchableInput/SearchableInput.spec.js @@ -120,5 +120,15 @@ describe('SearchableInput.vue', () => { query: { search: 'ab' }, }) }) + + it('replaces irregular whitespace with a single space', async () => { + select.element.value = 'peter lustig' + select.trigger('input') + select.trigger('keyup.enter') + expect(mocks.$router.push).toHaveBeenCalledWith({ + path: '/search/search-results', + query: { search: 'peter lustig' }, + }) + }) }) }) diff --git a/webapp/layouts/basic.spec.js b/webapp/layouts/basic.spec.js new file mode 100644 index 000000000..5094a970b --- /dev/null +++ b/webapp/layouts/basic.spec.js @@ -0,0 +1,34 @@ +import { config, shallowMount } from '@vue/test-utils' +import Basic from './basic.vue' + +const localVue = global.localVue + +config.stubs.nuxt = '' + +describe('basic.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: jest.fn(), + } + }) + + describe('shallow mount', () => { + const Wrapper = () => { + return shallowMount(Basic, { + mocks, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('.layout-blank')).toBe(true) + }) + }) +}) diff --git a/webapp/layouts/blank.spec.js b/webapp/layouts/blank.spec.js new file mode 100644 index 000000000..a3ea3120c --- /dev/null +++ b/webapp/layouts/blank.spec.js @@ -0,0 +1,34 @@ +import { config, shallowMount } from '@vue/test-utils' +import Blank from './blank.vue' + +const localVue = global.localVue + +config.stubs.nuxt = '' + +describe('blank.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: jest.fn(), + } + }) + + describe('shallow mount', () => { + const Wrapper = () => { + return shallowMount(Blank, { + mocks, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('.layout-blank')).toBe(true) + }) + }) +}) diff --git a/webapp/layouts/default.spec.js b/webapp/layouts/default.spec.js new file mode 100644 index 000000000..3d465ce76 --- /dev/null +++ b/webapp/layouts/default.spec.js @@ -0,0 +1,52 @@ +import Vuex from 'vuex' +import { config, shallowMount } from '@vue/test-utils' +import Default from './default.vue' + +const localVue = global.localVue +localVue.directive('scrollTo', jest.fn()) + +config.stubs.nuxt = '' +config.stubs['client-only'] = '' +config.stubs['nuxt-link'] = '' + +describe('default.vue', () => { + let wrapper + let mocks + let store + + beforeEach(() => { + mocks = { + $route: { + matched: [{ name: 'index' }], + }, + $scrollTo: jest.fn(), + $t: jest.fn(), + $env: { + INVITE_REGISTRATION: true, + }, + } + store = new Vuex.Store({ + getters: { + 'auth/isLoggedIn': () => true, + }, + }) + }) + + describe('shallow mount', () => { + const Wrapper = () => { + return shallowMount(Default, { + store, + mocks, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('.layout-default')).toBe(true) + }) + }) +}) diff --git a/webapp/package.json b/webapp/package.json index 84cdcbc7c..7abe34e4a 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -24,9 +24,17 @@ "verbose": true, "collectCoverageFrom": [ "**/*.{js,vue}", + "!**/?(*.)+(spec|test|story).js?(x)", "!**/node_modules/**", "!**/.nuxt/**", - "!**/?(*.)+(spec|test).js?(x)" + "!**/storybook/**", + "!**/coverage/**", + "!**/config/**", + "!**/maintenance/**", + "!**/plugins/**", + "!**/.eslintrc.js", + "!**/.prettierrc.js", + "!**/nuxt.config.js" ], "coverageReporters": [ "lcov" @@ -41,10 +49,10 @@ "vue" ], "moduleNameMapper": { - "^@/(.*)$": "/src/$1", - "^~/(.*)$": "/$1", + "\\.(svg)$": "/test/fileMock.js", "\\.(css|less)$": "identity-obj-proxy", - "\\.(svg)$": "/test/fileMock.js" + "^@/(.*)$": "/src/$1", + "^~/(.*)$": "/$1" }, "setupFiles": [ "/test/registerContext.js", diff --git a/webapp/pages/admin.spec.js b/webapp/pages/admin.spec.js new file mode 100644 index 000000000..fc3849fc4 --- /dev/null +++ b/webapp/pages/admin.spec.js @@ -0,0 +1,34 @@ +import { config, mount } from '@vue/test-utils' +import admin from './admin.vue' + +config.stubs['nuxt-child'] = '' + +const localVue = global.localVue + +describe('admin.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: jest.fn(), + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(admin, { + mocks, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('div')).toBe(true) + }) + }) +}) diff --git a/webapp/pages/admin/categories.spec.js b/webapp/pages/admin/categories.spec.js new file mode 100644 index 000000000..55715e74b --- /dev/null +++ b/webapp/pages/admin/categories.spec.js @@ -0,0 +1,32 @@ +import { mount } from '@vue/test-utils' +import Categories from './categories.vue' + +const localVue = global.localVue + +describe('categories.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: jest.fn(), + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(Categories, { + mocks, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('.base-card')).toBe(true) + }) + }) +}) diff --git a/webapp/pages/admin/donations.spec.js b/webapp/pages/admin/donations.spec.js new file mode 100644 index 000000000..2bc219dce --- /dev/null +++ b/webapp/pages/admin/donations.spec.js @@ -0,0 +1,32 @@ +import { mount } from '@vue/test-utils' +import Donations from './donations.vue' + +const localVue = global.localVue + +describe('donations.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: jest.fn(), + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(Donations, { + mocks, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('.base-card')).toBe(true) + }) + }) +}) diff --git a/webapp/pages/admin/hashtags.spec.js b/webapp/pages/admin/hashtags.spec.js new file mode 100644 index 000000000..cd2d308d1 --- /dev/null +++ b/webapp/pages/admin/hashtags.spec.js @@ -0,0 +1,32 @@ +import { mount } from '@vue/test-utils' +import Hashtags from './hashtags.vue' + +const localVue = global.localVue + +describe('hashtags.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: jest.fn(), + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(Hashtags, { + mocks, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('.base-card')).toBe(true) + }) + }) +}) diff --git a/webapp/pages/admin/invite.spec.js b/webapp/pages/admin/invite.spec.js new file mode 100644 index 000000000..e3e882119 --- /dev/null +++ b/webapp/pages/admin/invite.spec.js @@ -0,0 +1,35 @@ +import { mount } from '@vue/test-utils' +import Invite from './invite.vue' + +const localVue = global.localVue + +describe('invite.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: jest.fn(), + $apollo: { + loading: false, + }, + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(Invite, { + mocks, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('.ds-section')).toBe(true) + }) + }) +}) diff --git a/webapp/pages/admin/notifications.spec.js b/webapp/pages/admin/notifications.spec.js new file mode 100644 index 000000000..c9acf81a6 --- /dev/null +++ b/webapp/pages/admin/notifications.spec.js @@ -0,0 +1,35 @@ +import { mount } from '@vue/test-utils' +import Notifications from './notifications.vue' + +const localVue = global.localVue + +describe('notifications.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: jest.fn(), + $apollo: { + loading: false, + }, + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(Notifications, { + mocks, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('.base-card')).toBe(true) + }) + }) +}) diff --git a/webapp/pages/admin/organizations.spec.js b/webapp/pages/admin/organizations.spec.js new file mode 100644 index 000000000..d019d9485 --- /dev/null +++ b/webapp/pages/admin/organizations.spec.js @@ -0,0 +1,35 @@ +import { mount } from '@vue/test-utils' +import Organizations from './organizations.vue' + +const localVue = global.localVue + +describe('organizations.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: jest.fn(), + $apollo: { + loading: false, + }, + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(Organizations, { + mocks, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('.base-card')).toBe(true) + }) + }) +}) diff --git a/webapp/pages/admin/pages.spec.js b/webapp/pages/admin/pages.spec.js new file mode 100644 index 000000000..e0c3c9fb4 --- /dev/null +++ b/webapp/pages/admin/pages.spec.js @@ -0,0 +1,35 @@ +import { mount } from '@vue/test-utils' +import Pages from './pages.vue' + +const localVue = global.localVue + +describe('pages.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: jest.fn(), + $apollo: { + loading: false, + }, + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(Pages, { + mocks, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('.base-card')).toBe(true) + }) + }) +}) diff --git a/webapp/pages/admin/settings.spec.js b/webapp/pages/admin/settings.spec.js new file mode 100644 index 000000000..78a5beb94 --- /dev/null +++ b/webapp/pages/admin/settings.spec.js @@ -0,0 +1,35 @@ +import { mount } from '@vue/test-utils' +import Settings from './settings.vue' + +const localVue = global.localVue + +describe('settings.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: jest.fn(), + $apollo: { + loading: false, + }, + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(Settings, { + mocks, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('.base-card')).toBe(true) + }) + }) +}) diff --git a/webapp/pages/code-of-conduct.spec.js b/webapp/pages/code-of-conduct.spec.js new file mode 100644 index 000000000..75e244c79 --- /dev/null +++ b/webapp/pages/code-of-conduct.spec.js @@ -0,0 +1,38 @@ +import { mount } from '@vue/test-utils' +import CodeOfConduct from './code-of-conduct.vue' +import VueMeta from 'vue-meta' + +const localVue = global.localVue +localVue.use(VueMeta, { keyName: 'head' }) + +describe('code-of-conduct.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: (t) => t, + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(CodeOfConduct, { + mocks, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('div')).toBe(true) + }) + + it('has correct content', () => { + expect(wrapper.vm.$metaInfo.title).toBe('site.code-of-conduct') + }) + }) +}) diff --git a/webapp/pages/data-privacy.spec.js b/webapp/pages/data-privacy.spec.js new file mode 100644 index 000000000..a919bb742 --- /dev/null +++ b/webapp/pages/data-privacy.spec.js @@ -0,0 +1,38 @@ +import { mount } from '@vue/test-utils' +import DataPrivacy from './data-privacy.vue' +import VueMeta from 'vue-meta' + +const localVue = global.localVue +localVue.use(VueMeta, { keyName: 'head' }) + +describe('data-privacy.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: (t) => t, + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(DataPrivacy, { + mocks, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('div')).toBe(true) + }) + + it('has correct content', () => { + expect(wrapper.vm.$metaInfo.title).toBe('site.data-privacy') + }) + }) +}) diff --git a/webapp/pages/imprint.spec.js b/webapp/pages/imprint.spec.js new file mode 100644 index 000000000..1a84b5794 --- /dev/null +++ b/webapp/pages/imprint.spec.js @@ -0,0 +1,38 @@ +import { mount } from '@vue/test-utils' +import Imprint from './imprint.vue' +import VueMeta from 'vue-meta' + +const localVue = global.localVue +localVue.use(VueMeta, { keyName: 'head' }) + +describe('imprint.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: (t) => t, + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(Imprint, { + mocks, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('div')).toBe(true) + }) + + it('has correct content', () => { + expect(wrapper.vm.$metaInfo.title).toBe('site.imprint') + }) + }) +}) diff --git a/webapp/pages/login.spec.js b/webapp/pages/login.spec.js new file mode 100644 index 000000000..09c1b066e --- /dev/null +++ b/webapp/pages/login.spec.js @@ -0,0 +1,74 @@ +import Vuex from 'vuex' +import { config, mount } from '@vue/test-utils' +import login from './login.vue' + +const localVue = global.localVue + +config.stubs['client-only'] = '' +config.stubs['nuxt-link'] = '' + +describe('Login.vue', () => { + let store + let mocks + let wrapper + let asyncData + let tosVersion + let redirect + + beforeEach(() => { + mocks = { + $t: jest.fn(), + $i18n: { + locale: () => 'en', + }, + } + asyncData = false + tosVersion = '0.0.0' + redirect = jest.fn() + }) + + describe('mount', () => { + const Wrapper = async () => { + store = new Vuex.Store({ + getters: { + 'auth/user': () => { + return { termsAndConditionsAgreedVersion: tosVersion } + }, + }, + }) + if (asyncData) { + const data = login.data ? login.data() : {} + const aData = await login.asyncData({ + store, + redirect, + }) + login.data = function () { + return { ...data, ...aData } + } + } + return mount(login, { + store, + mocks, + localVue, + }) + } + + it('renders', async () => { + wrapper = await Wrapper() + expect(wrapper.findAll('.login-form')).toHaveLength(1) + }) + + it('renders with asyncData and wrong TOS Version', async () => { + asyncData = true + wrapper = await Wrapper() + expect(redirect).not.toHaveBeenCalled() + }) + + it('renders with asyncData and correct TOS Version', async () => { + asyncData = true + tosVersion = '0.0.4' + wrapper = await Wrapper() + expect(redirect).toBeCalledWith('/') + }) + }) +}) diff --git a/webapp/pages/logout.spec.js b/webapp/pages/logout.spec.js new file mode 100644 index 000000000..4ec777bf6 --- /dev/null +++ b/webapp/pages/logout.spec.js @@ -0,0 +1,43 @@ +import { mount } from '@vue/test-utils' +import Logout from './logout.vue' + +const localVue = global.localVue + +describe('logout.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: jest.fn(), + $store: { + dispatch: jest.fn(), + }, + $router: { + replace: jest.fn(), + }, + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(Logout, { + mocks, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('div')).toBe(true) + }) + + it('logs out and redirects to login', () => { + expect(mocks.$store.dispatch).toBeCalledWith('auth/logout') + expect(mocks.$router.replace).toBeCalledWith('/login') + }) + }) +}) diff --git a/webapp/pages/moderation.spec.js b/webapp/pages/moderation.spec.js new file mode 100644 index 000000000..2eeae9f7c --- /dev/null +++ b/webapp/pages/moderation.spec.js @@ -0,0 +1,34 @@ +import { config, mount } from '@vue/test-utils' +import moderation from './moderation.vue' + +config.stubs['nuxt-child'] = '' + +const localVue = global.localVue + +describe('moderation.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: jest.fn(), + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(moderation, { + mocks, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('div')).toBe(true) + }) + }) +}) diff --git a/webapp/pages/moderation/index.spec.js b/webapp/pages/moderation/index.spec.js new file mode 100644 index 000000000..249752aa3 --- /dev/null +++ b/webapp/pages/moderation/index.spec.js @@ -0,0 +1,30 @@ +import { config, mount } from '@vue/test-utils' +import Moderation from './index.vue' + +const localVue = global.localVue +config.stubs['client-only'] = '' + +describe('moderation/index.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: jest.fn(), + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(Moderation, { mocks, localVue }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('.base-card')).toBe(true) + }) + }) +}) diff --git a/webapp/pages/password-reset.spec.js b/webapp/pages/password-reset.spec.js new file mode 100644 index 000000000..01052e89c --- /dev/null +++ b/webapp/pages/password-reset.spec.js @@ -0,0 +1,71 @@ +import Vuex from 'vuex' +import { config, mount } from '@vue/test-utils' +import PasswordReset from './password-reset.vue' + +const localVue = global.localVue + +config.stubs['client-only'] = '' +config.stubs['nuxt-child'] = '' + +describe('password-reset.vue', () => { + let wrapper + let mocks + let asyncData + let store + let redirect + let isLoggedIn + + beforeEach(() => { + mocks = { + $t: (t) => t, + $i18n: { + locale: () => 'en', + }, + } + asyncData = false + isLoggedIn = false + redirect = jest.fn() + }) + + describe('mount', () => { + const Wrapper = async () => { + store = new Vuex.Store({ + getters: { + 'auth/isLoggedIn': () => isLoggedIn, + }, + }) + if (asyncData) { + const data = PasswordReset.data ? PasswordReset.data() : {} + const aData = await PasswordReset.asyncData({ + store, + redirect, + }) + PasswordReset.data = function () { + return { ...data, ...aData } + } + } + return mount(PasswordReset, { + mocks, + localVue, + }) + } + + it('renders', async () => { + wrapper = await Wrapper() + expect(wrapper.is('div')).toBe(true) + }) + + it('renders with asyncData and not loggedIn', async () => { + asyncData = true + wrapper = await Wrapper() + expect(redirect).not.toHaveBeenCalled() + }) + + it('renders with asyncData and loggedIn', async () => { + asyncData = true + isLoggedIn = true + wrapper = await Wrapper() + expect(redirect).toBeCalledWith('/') + }) + }) +}) diff --git a/webapp/pages/password-reset/change-password.spec.js b/webapp/pages/password-reset/change-password.spec.js new file mode 100644 index 000000000..cad031c95 --- /dev/null +++ b/webapp/pages/password-reset/change-password.spec.js @@ -0,0 +1,35 @@ +import { mount } from '@vue/test-utils' +import changePassword from './change-password.vue' + +const localVue = global.localVue + +describe('enter-nonce.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: jest.fn(), + $route: { + query: jest.fn().mockResolvedValue({ email: 'peter@lustig.de', nonce: '12345' }), + }, + $apollo: { + loading: false, + }, + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(changePassword, { mocks, localVue }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.findAll('.ds-form')).toHaveLength(1) + }) + }) +}) diff --git a/webapp/pages/password-reset/enter-nonce.spec.js b/webapp/pages/password-reset/enter-nonce.spec.js new file mode 100644 index 000000000..664e1f7ca --- /dev/null +++ b/webapp/pages/password-reset/enter-nonce.spec.js @@ -0,0 +1,34 @@ +import { config, mount } from '@vue/test-utils' +import enterNonce from './enter-nonce.vue' + +const localVue = global.localVue + +config.stubs['nuxt-link'] = '' + +describe('enter-nonce.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: jest.fn(), + $route: { + query: jest.fn().mockResolvedValue({ email: 'peter@lustig.de' }), + }, + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(enterNonce, { mocks, localVue }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.findAll('.ds-form')).toHaveLength(1) + }) + }) +}) diff --git a/webapp/pages/password-reset/request.spec.js b/webapp/pages/password-reset/request.spec.js new file mode 100644 index 000000000..f9bcefd79 --- /dev/null +++ b/webapp/pages/password-reset/request.spec.js @@ -0,0 +1,55 @@ +import { config, mount } from '@vue/test-utils' +import request from './request.vue' + +const localVue = global.localVue + +// config.stubs['sweetalert-icon'] = '' +config.stubs['nuxt-link'] = '' + +describe('request.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + /* $toast: { + success: jest.fn(), + error: jest.fn(), + }, */ + $t: jest.fn(), + $apollo: { + loading: false, + // mutate: jest.fn().mockResolvedValue({ data: { reqestPasswordReset: true } }), + }, + /* $router: { + push: jest.fn() + } */ + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(request, { mocks, localVue }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.findAll('.ds-form')).toHaveLength(1) + }) + + it.skip('calls "handlePasswordResetRequested" on submit', async () => { + await jest.useFakeTimers() + await wrapper.find('input#email').setValue('mail@example.org') + await wrapper.findAll('.ds-form').trigger('submit') + await jest.runAllTimers() + expect(wrapper.emitted('handleSubmitted')).toEqual([[{ email: 'mail@example.org' }]]) + expect(mocks.$router.push).toHaveBeenCalledWith({ + path: 'enter-nonce', + query: { email: 'mail@example.org' }, + }) + }) + }) +}) diff --git a/webapp/pages/post/_id.spec.js b/webapp/pages/post/_id.spec.js new file mode 100644 index 000000000..7e6812002 --- /dev/null +++ b/webapp/pages/post/_id.spec.js @@ -0,0 +1,37 @@ +import { config, mount } from '@vue/test-utils' +import _id from './_id.vue' + +const localVue = global.localVue + +config.stubs['nuxt-child'] = '' + +describe('post/_id.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: jest.fn(), + $route: { + params: { + id: '1234', + slug: 'my-post', + }, + }, + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(_id, { mocks, localVue }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.findAll('.post-side-navigation')).toHaveLength(1) + }) + }) +}) diff --git a/webapp/pages/post/_id/_slug/index.spec.js b/webapp/pages/post/_id/_slug/index.spec.js index bc54edf53..4289bb53d 100644 --- a/webapp/pages/post/_id/_slug/index.spec.js +++ b/webapp/pages/post/_id/_slug/index.spec.js @@ -4,6 +4,7 @@ import Vue from 'vue' import PostSlug from './index.vue' import CommentList from '~/components/CommentList/CommentList' import HcHashtag from '~/components/Hashtag/Hashtag' +import VueMeta from 'vue-meta' config.stubs['client-only'] = '' config.stubs['nuxt-link'] = '' @@ -11,6 +12,7 @@ config.stubs['router-link'] = '' const localVue = global.localVue localVue.directive('scrollTo', jest.fn()) +localVue.use(VueMeta, { keyName: 'head' }) describe('PostSlug', () => { let wrapper, Wrapper, backendData, mocks, stubs @@ -91,6 +93,11 @@ describe('PostSlug', () => { return wrapper } + it('has correct content', async () => { + wrapper = await Wrapper() + expect(wrapper.vm.$metaInfo.title).toBe('loading') + }) + describe('given author is `null`', () => { it('does not crash', async () => { backendData = { diff --git a/webapp/pages/post/create.spec.js b/webapp/pages/post/create.spec.js new file mode 100644 index 000000000..951edba03 --- /dev/null +++ b/webapp/pages/post/create.spec.js @@ -0,0 +1,29 @@ +import { mount } from '@vue/test-utils' +import create from './create.vue' + +const localVue = global.localVue + +describe('create.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: jest.fn(), + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(create, { mocks, localVue }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.findAll('.contribution-form')).toHaveLength(1) + }) + }) +}) diff --git a/webapp/pages/post/edit/_id.spec.js b/webapp/pages/post/edit/_id.spec.js new file mode 100644 index 000000000..ea8ec61d8 --- /dev/null +++ b/webapp/pages/post/edit/_id.spec.js @@ -0,0 +1,82 @@ +import Vuex from 'vuex' +import { mount } from '@vue/test-utils' +import _id from './_id.vue' + +const localVue = global.localVue + +describe('post/_id.vue', () => { + let wrapper + let mocks + let store + let asyncData + let error + let userId + let authorId + + beforeEach(() => { + asyncData = false + error = jest.fn() + }) + + describe('mount', () => { + const Wrapper = async () => { + mocks = { + $t: jest.fn(), + $i18n: { + locale: () => 'en', + }, + apolloProvider: { + defaultClient: { + query: jest.fn().mockResolvedValue({ + data: { + Post: [{ author: { id: authorId } }], + }, + }), + }, + }, + } + store = new Vuex.Store({ + getters: { + 'auth/user': () => { + return { id: userId } + }, + }, + }) + if (asyncData) { + const data = _id.data ? _id.data() : {} + const aData = await _id.asyncData({ + app: mocks, + store, + error, + params: { id: '123' }, + }) + _id.data = function () { + return { ...data, ...aData } + } + } + return mount(_id, { store, mocks, localVue }) + } + + it('renders', async () => { + asyncData = false + wrapper = await Wrapper() + expect(wrapper.findAll('.contribution-form')).toHaveLength(1) + }) + + it('renders with asyncData of different users', async () => { + asyncData = true + authorId = 'some-author' + userId = 'some-user' + wrapper = await Wrapper() + expect(error).toBeCalledWith({ message: 'error-pages.cannot-edit-post', statusCode: 403 }) + }) + + it('renders with asyncData of same user', async () => { + asyncData = true + authorId = 'some-author' + userId = 'some-author' + wrapper = await Wrapper() + expect(error).not.toHaveBeenCalled() + }) + }) +}) diff --git a/webapp/pages/profile/_id.spec.js b/webapp/pages/profile/_id.spec.js new file mode 100644 index 000000000..aab216569 --- /dev/null +++ b/webapp/pages/profile/_id.spec.js @@ -0,0 +1,33 @@ +import { config, mount } from '@vue/test-utils' +import _id from './_id.vue' + +const localVue = global.localVue + +config.stubs['nuxt-child'] = '' + +describe('Profile _id.vue', () => { + let wrapper + let Wrapper + let mocks + + beforeEach(() => { + mocks = {} + }) + + describe('mount', () => { + Wrapper = () => { + return mount(_id, { + mocks, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.findAll('.nuxt-child')).toHaveLength(1) + }) + }) +}) diff --git a/webapp/pages/registration.spec.js b/webapp/pages/registration.spec.js index b83cb6ad4..74fb7d710 100644 --- a/webapp/pages/registration.spec.js +++ b/webapp/pages/registration.spec.js @@ -1,3 +1,4 @@ +import Vuex from 'vuex' import { config, mount } from '@vue/test-utils' import Registration from './registration.vue' import Vue from 'vue' @@ -13,6 +14,10 @@ describe('Registration', () => { let wrapper let Wrapper let mocks + let asyncData + let store + let redirect + let isLoggedIn beforeEach(() => { mocks = { @@ -25,10 +30,42 @@ describe('Registration', () => { }, $env: {}, } + asyncData = false + isLoggedIn = false + redirect = jest.fn() }) describe('mount', () => { - Wrapper = () => { + Wrapper = async () => { + if (asyncData) { + store = new Vuex.Store({ + getters: { + 'auth/isLoggedIn': () => isLoggedIn, + }, + }) + const data = { + method: mocks, + overwriteSliderData: { + collectedInputData: { + inviteCode: null, + email: null, + emailSend: !!null, + nonce: null, + }, + }, + publicRegistration: false, + inviteRegistration: false, + } + const aData = await Registration.asyncData({ + store, + redirect, + }) + Registration.data = function () { + return { ...data, ...aData } + } + } else { + Registration.data = Registration.backupData ? Registration.backupData : Registration.data + } return mount(Registration, { mocks, localVue, @@ -43,25 +80,25 @@ describe('Registration', () => { } }) - it('no "method" query in URI show "RegistrationSlideNoPublic"', () => { + it('no "method" query in URI show "RegistrationSlideNoPublic"', async () => { mocks.$route.query = {} - wrapper = Wrapper() + wrapper = await Wrapper() expect(wrapper.find('.hc-empty').exists()).toBe(true) expect(wrapper.find('.enter-invite').exists()).toBe(false) expect(wrapper.find('.enter-email').exists()).toBe(false) }) describe('"method=invite-mail" in URI show "RegistrationSlideNonce"', () => { - it('no "email" query in URI', () => { + it('no "email" query in URI', async () => { mocks.$route.query = { method: 'invite-mail' } - wrapper = Wrapper() + wrapper = await Wrapper() expect(wrapper.find('.enter-nonce').exists()).toBe(true) }) describe('"email=user%40example.org" query in URI', () => { - it('have email displayed', () => { + it('have email displayed', async () => { mocks.$route.query = { method: 'invite-mail', email: 'user@example.org' } - wrapper = Wrapper() + wrapper = await Wrapper() expect(wrapper.find('.enter-nonce').text()).toContain('user@example.org') }) @@ -71,7 +108,7 @@ describe('Registration', () => { email: 'user@example.org', nonce: '64835', } - wrapper = Wrapper() + wrapper = await Wrapper() await Vue.nextTick() const form = wrapper.find('.enter-nonce') expect(form.vm.formData.nonce).toEqual('64835') @@ -80,15 +117,15 @@ describe('Registration', () => { }) describe('"method=invite-code" in URI show "RegistrationSlideNoPublic"', () => { - it('no "inviteCode" query in URI', () => { + it('no "inviteCode" query in URI', async () => { mocks.$route.query = { method: 'invite-code' } - wrapper = Wrapper() + wrapper = await Wrapper() expect(wrapper.find('.hc-empty').exists()).toBe(true) }) - it('"inviteCode=AAAAAA" query in URI', () => { + it('"inviteCode=AAAAAA" query in URI', async () => { mocks.$route.query = { method: 'invite-code', inviteCode: 'AAAAAA' } - wrapper = Wrapper() + wrapper = await Wrapper() expect(wrapper.find('.hc-empty').exists()).toBe(true) }) }) @@ -102,24 +139,24 @@ describe('Registration', () => { } }) - it('no "method" query in URI show "RegistrationSlideInvite"', () => { + it('no "method" query in URI show "RegistrationSlideInvite"', async () => { mocks.$route.query = {} - wrapper = Wrapper() + wrapper = await Wrapper() expect(wrapper.find('.enter-invite').exists()).toBe(true) expect(wrapper.find('.enter-email').exists()).toBe(false) }) describe('"method=invite-mail" in URI show "RegistrationSlideNonce"', () => { - it('no "inviteCode" query in URI', () => { + it('no "inviteCode" query in URI', async () => { mocks.$route.query = { method: 'invite-mail' } - wrapper = Wrapper() + wrapper = await Wrapper() expect(wrapper.find('.enter-nonce').exists()).toBe(true) }) describe('"email=user%40example.org" query in URI', () => { - it('have email displayed', () => { + it('have email displayed', async () => { mocks.$route.query = { method: 'invite-mail', email: 'user@example.org' } - wrapper = Wrapper() + wrapper = await Wrapper() expect(wrapper.find('.enter-nonce').text()).toContain('user@example.org') }) @@ -129,7 +166,7 @@ describe('Registration', () => { email: 'user@example.org', nonce: '64835', } - wrapper = Wrapper() + wrapper = await Wrapper() await Vue.nextTick() const form = wrapper.find('.enter-nonce') expect(form.vm.formData.nonce).toEqual('64835') @@ -138,15 +175,15 @@ describe('Registration', () => { }) describe('"method=invite-code" in URI show "RegistrationSlideInvite"', () => { - it('no "inviteCode" query in URI', () => { + it('no "inviteCode" query in URI', async () => { mocks.$route.query = { method: 'invite-code' } - wrapper = Wrapper() + wrapper = await Wrapper() expect(wrapper.find('.enter-invite').exists()).toBe(true) }) it('"inviteCode=AAAAAA" query in URI have invite code in input', async () => { mocks.$route.query = { method: 'invite-code', inviteCode: 'AAAAAA' } - wrapper = Wrapper() + wrapper = await Wrapper() await Vue.nextTick() const form = wrapper.find('.enter-invite') expect(form.vm.formData.inviteCode).toEqual('AAAAAA') @@ -162,24 +199,24 @@ describe('Registration', () => { } }) - it('no "method" query in URI show "RegistrationSlideEmail"', () => { + it('no "method" query in URI show "RegistrationSlideEmail"', async () => { mocks.$route.query = {} - wrapper = Wrapper() + wrapper = await Wrapper() expect(wrapper.find('.enter-email').exists()).toBe(true) expect(wrapper.find('.enter-invite').exists()).toBe(false) }) describe('"method=invite-mail" in URI show "RegistrationSlideNonce"', () => { - it('no "inviteCode" query in URI', () => { + it('no "inviteCode" query in URI', async () => { mocks.$route.query = { method: 'invite-mail' } - wrapper = Wrapper() + wrapper = await Wrapper() expect(wrapper.find('.enter-nonce').exists()).toBe(true) }) describe('"email=user%40example.org" query in URI', () => { - it('have email displayed', () => { + it('have email displayed', async () => { mocks.$route.query = { method: 'invite-mail', email: 'user@example.org' } - wrapper = Wrapper() + wrapper = await Wrapper() expect(wrapper.find('.enter-nonce').text()).toContain('user@example.org') }) @@ -189,7 +226,7 @@ describe('Registration', () => { email: 'user@example.org', nonce: '64835', } - wrapper = Wrapper() + wrapper = await Wrapper() await Vue.nextTick() const form = wrapper.find('.enter-nonce') expect(form.vm.formData.nonce).toEqual('64835') @@ -198,9 +235,9 @@ describe('Registration', () => { }) describe('"method=invite-code" in URI show "RegistrationSlideEmail"', () => { - it('no "inviteCode" query in URI', () => { + it('no "inviteCode" query in URI', async () => { mocks.$route.query = { method: 'invite-code' } - wrapper = Wrapper() + wrapper = await Wrapper() expect(wrapper.find('.enter-email').exists()).toBe(true) expect(wrapper.find('.enter-invite').exists()).toBe(false) }) @@ -215,24 +252,24 @@ describe('Registration', () => { } }) - it('no "method" query in URI show "RegistrationSlideEmail"', () => { + it('no "method" query in URI show "RegistrationSlideEmail"', async () => { mocks.$route.query = {} - wrapper = Wrapper() + wrapper = await Wrapper() expect(wrapper.find('.enter-email').exists()).toBe(true) expect(wrapper.find('.enter-invite').exists()).toBe(false) }) describe('"method=invite-mail" in URI show "RegistrationSlideNonce"', () => { - it('no "inviteCode" query in URI', () => { + it('no "inviteCode" query in URI', async () => { mocks.$route.query = { method: 'invite-mail' } - wrapper = Wrapper() + wrapper = await Wrapper() expect(wrapper.find('.enter-nonce').exists()).toBe(true) }) describe('"email=user%40example.org" query in URI', () => { - it('have email displayed', () => { + it('have email displayed', async () => { mocks.$route.query = { method: 'invite-mail', email: 'user@example.org' } - wrapper = Wrapper() + wrapper = await Wrapper() expect(wrapper.find('.enter-nonce').text()).toContain('user@example.org') }) @@ -242,7 +279,7 @@ describe('Registration', () => { email: 'user@example.org', nonce: '64835', } - wrapper = Wrapper() + wrapper = await Wrapper() await Vue.nextTick() const form = wrapper.find('.enter-nonce') expect(form.vm.formData.nonce).toEqual('64835') @@ -251,15 +288,15 @@ describe('Registration', () => { }) describe('"method=invite-code" in URI show "RegistrationSlideInvite"', () => { - it('no "inviteCode" query in URI', () => { + it('no "inviteCode" query in URI', async () => { mocks.$route.query = { method: 'invite-code' } - wrapper = Wrapper() + wrapper = await Wrapper() expect(wrapper.find('.enter-invite').exists()).toBe(true) }) it('"inviteCode=AAAAAA" query in URI have invite code in input', async () => { mocks.$route.query = { method: 'invite-code', inviteCode: 'AAAAAA' } - wrapper = Wrapper() + wrapper = await Wrapper() await Vue.nextTick() const form = wrapper.find('.enter-invite') expect(form.vm.formData.inviteCode).toEqual('AAAAAA') @@ -267,6 +304,25 @@ describe('Registration', () => { }) }) + it('renders', async () => { + wrapper = await Wrapper() + expect(wrapper.is('.login-form')).toBe(true) + }) + + // The asyncTests must go last + it('renders with asyncData and not loggedIn', async () => { + asyncData = true + wrapper = await Wrapper() + expect(redirect).not.toHaveBeenCalled() + }) + + it('renders with asyncData and loggedIn', async () => { + asyncData = true + isLoggedIn = true + wrapper = await Wrapper() + expect(redirect).toBeCalledWith('/') + }) + // copied from webapp/components/Registration/Signup.spec.js as testing template // describe('with invitation code', () => { // let action diff --git a/webapp/pages/search/search-results.spec.js b/webapp/pages/search/search-results.spec.js new file mode 100644 index 000000000..c594f3e56 --- /dev/null +++ b/webapp/pages/search/search-results.spec.js @@ -0,0 +1,61 @@ +import { config, mount } from '@vue/test-utils' +import searchResults from './search-results.vue' +import VueMeta from 'vue-meta' + +const localVue = global.localVue +localVue.use(VueMeta, { keyName: 'head' }) + +config.stubs['client-only'] = '' + +describe('search-results.vue', () => { + let wrapper + let mocks + let asyncData + let query + + beforeEach(() => { + mocks = { + $t: (t) => t, + } + asyncData = false + query = {} + }) + + describe('mount', () => { + const Wrapper = async () => { + if (asyncData) { + const data = searchResults.data ? searchResults.data() : {} + const aData = await searchResults.asyncData({ + query, + }) + searchResults.data = function () { + return { ...data, ...aData } + } + } + return mount(searchResults, { mocks, localVue }) + } + + it('renders', async () => { + wrapper = await Wrapper() + expect(wrapper.findAll('.search-results')).toHaveLength(1) + }) + + it('has correct content', async () => { + wrapper = await Wrapper() + expect(wrapper.vm.$metaInfo.title).toBe('search.title') + }) + + it('renders with asyncData and no query', async () => { + asyncData = true + wrapper = await Wrapper() + expect(wrapper.vm.search).toBe(null) + }) + + it('renders with asyncData and query', async () => { + asyncData = true + query = { search: 'hello' } + wrapper = await Wrapper() + expect(wrapper.vm.search).toBe('hello') + }) + }) +}) diff --git a/webapp/pages/settings.spec.js b/webapp/pages/settings.spec.js new file mode 100644 index 000000000..353f1e6b8 --- /dev/null +++ b/webapp/pages/settings.spec.js @@ -0,0 +1,34 @@ +import { config, mount } from '@vue/test-utils' +import settings from './settings.vue' + +const localVue = global.localVue + +config.stubs['nuxt-child'] = '' + +describe('settings.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: jest.fn(), + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(settings, { + mocks, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('div')).toBe(true) + }) + }) +}) diff --git a/webapp/pages/settings/data-download.spec.js b/webapp/pages/settings/data-download.spec.js new file mode 100644 index 000000000..b50c8d046 --- /dev/null +++ b/webapp/pages/settings/data-download.spec.js @@ -0,0 +1,32 @@ +import { mount } from '@vue/test-utils' +import DataDownload from './data-download.vue' + +const localVue = global.localVue + +describe('data-download.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: jest.fn(), + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(DataDownload, { + mocks, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('.base-card')).toBe(true) + }) + }) +}) diff --git a/webapp/pages/settings/delete-account.spec.js b/webapp/pages/settings/delete-account.spec.js new file mode 100644 index 000000000..aa8ffd954 --- /dev/null +++ b/webapp/pages/settings/delete-account.spec.js @@ -0,0 +1,42 @@ +import Vuex from 'vuex' +import { mount } from '@vue/test-utils' +import DeleteAccount from './delete-account.vue' + +const localVue = global.localVue + +describe('delete-account.vue', () => { + let wrapper + let mocks + let store + + beforeEach(() => { + mocks = { + $t: jest.fn(), + } + store = new Vuex.Store({ + getters: { + 'auth/user': () => { + return { id: 'u343', name: 'Delete MyAccount' } + }, + }, + }) + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(DeleteAccount, { + store, + mocks, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('.delete-data')).toBe(true) + }) + }) +}) diff --git a/webapp/pages/settings/embeds.spec.js b/webapp/pages/settings/embeds.spec.js new file mode 100644 index 000000000..75247ddf0 --- /dev/null +++ b/webapp/pages/settings/embeds.spec.js @@ -0,0 +1,42 @@ +import Vuex from 'vuex' +import { mount } from '@vue/test-utils' +import Embeds from './embeds.vue' + +const localVue = global.localVue + +describe('embeds.vue', () => { + let wrapper + let mocks + let store + + beforeEach(() => { + mocks = { + $t: jest.fn(), + } + store = new Vuex.Store({ + getters: { + 'auth/user': () => { + return { id: 'u343', name: 'Delete MyAccount', allowEmbedIframes: true } + }, + }, + }) + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(Embeds, { + store, + mocks, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('.base-card')).toBe(true) + }) + }) +}) diff --git a/webapp/pages/settings/invites.spec.js b/webapp/pages/settings/invites.spec.js new file mode 100644 index 000000000..cbc8d1765 --- /dev/null +++ b/webapp/pages/settings/invites.spec.js @@ -0,0 +1,32 @@ +import { mount } from '@vue/test-utils' +import Invites from './invites.vue' + +const localVue = global.localVue + +describe('invites.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: jest.fn(), + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(Invites, { + mocks, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('.base-card')).toBe(true) + }) + }) +}) diff --git a/webapp/pages/settings/languages.spec.js b/webapp/pages/settings/languages.spec.js new file mode 100644 index 000000000..0e3665739 --- /dev/null +++ b/webapp/pages/settings/languages.spec.js @@ -0,0 +1,32 @@ +import { mount } from '@vue/test-utils' +import Languages from './languages.vue' + +const localVue = global.localVue + +describe('languages.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: jest.fn(), + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(Languages, { + mocks, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('.base-card')).toBe(true) + }) + }) +}) diff --git a/webapp/pages/settings/my-email-address/index.spec.js b/webapp/pages/settings/my-email-address/index.spec.js index 22654afd0..808aee3be 100644 --- a/webapp/pages/settings/my-email-address/index.spec.js +++ b/webapp/pages/settings/my-email-address/index.spec.js @@ -111,6 +111,21 @@ describe('EmailSettingsIndexPage', () => { expect(wrapper.text()).toContain('registration.signup.form.errors.email-exists') }) }) + + describe('if backend sends any other error', () => { + beforeEach(() => { + mocks.$apollo.mutate = jest.fn().mockRejectedValue({ + message: 'Ouch!', + }) + wrapper = Wrapper() + wrapper.find('#email').setValue('already-taken@example.org') + wrapper.find('form').trigger('submit') + }) + + it('display a toast error', () => { + expect(mocks.$toast.error).toHaveBeenCalled() + }) + }) }) }) }) diff --git a/webapp/pages/settings/my-organizations.spec.js b/webapp/pages/settings/my-organizations.spec.js new file mode 100644 index 000000000..7f11b9871 --- /dev/null +++ b/webapp/pages/settings/my-organizations.spec.js @@ -0,0 +1,32 @@ +import { mount } from '@vue/test-utils' +import MyOrganizations from './my-organizations.vue' + +const localVue = global.localVue + +describe('my-organizations.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: jest.fn(), + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(MyOrganizations, { + mocks, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('.base-card')).toBe(true) + }) + }) +}) diff --git a/webapp/pages/settings/privacy.spec.js b/webapp/pages/settings/privacy.spec.js new file mode 100644 index 000000000..eb9cb90b3 --- /dev/null +++ b/webapp/pages/settings/privacy.spec.js @@ -0,0 +1,70 @@ +import Vuex from 'vuex' +import { mount } from '@vue/test-utils' +import Privacy from './privacy.vue' + +const localVue = global.localVue + +describe('privacy.vue', () => { + let wrapper + let mocks + let store + + beforeEach(() => { + mocks = { + $t: jest.fn(), + $apollo: { + mutate: jest.fn(), + }, + $toast: { + success: jest.fn(), + error: jest.fn(), + }, + } + store = new Vuex.Store({ + getters: { + 'auth/user': () => { + return { + id: 'u343', + name: 'MyAccount', + showShoutsPublicly: true, + } + }, + }, + }) + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(Privacy, { + store, + mocks, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('.base-card')).toBe(true) + }) + + it('clicking on submit changes shoutsAllowed to false', async () => { + wrapper.find('#allow-shouts').trigger('click') + await wrapper.vm.$nextTick() + wrapper.find('.base-button').trigger('click') + expect(wrapper.vm.shoutsAllowed).toBe(false) + }) + + it('clicking on submit with a server error shows a toast and shoutsAllowed is still true', async () => { + mocks.$apollo.mutate = jest.fn().mockRejectedValue({ message: 'Ouch!' }) + wrapper.find('#allow-shouts').trigger('click') + await wrapper.vm.$nextTick() + await wrapper.find('.base-button').trigger('click') + await wrapper.vm.$nextTick() + expect(mocks.$toast.error).toHaveBeenCalledWith('Ouch!') + expect(wrapper.vm.shoutsAllowed).toBe(true) + }) + }) +}) diff --git a/webapp/pages/settings/privacy.vue b/webapp/pages/settings/privacy.vue index 14f27bf8f..71fd31946 100644 --- a/webapp/pages/settings/privacy.vue +++ b/webapp/pages/settings/privacy.vue @@ -52,6 +52,7 @@ export default { }, }) } catch (error) { + this.shoutsAllowed = !this.shoutsAllowed this.$toast.error(error.message) } }, diff --git a/webapp/pages/settings/security.spec.js b/webapp/pages/settings/security.spec.js new file mode 100644 index 000000000..dee9e640a --- /dev/null +++ b/webapp/pages/settings/security.spec.js @@ -0,0 +1,32 @@ +import { mount } from '@vue/test-utils' +import Security from './security.vue' + +const localVue = global.localVue + +describe('security.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: jest.fn(), + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(Security, { + mocks, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('.base-card')).toBe(true) + }) + }) +}) diff --git a/webapp/pages/terms-and-conditions-confirm.spec.js b/webapp/pages/terms-and-conditions-confirm.spec.js new file mode 100644 index 000000000..098e73a92 --- /dev/null +++ b/webapp/pages/terms-and-conditions-confirm.spec.js @@ -0,0 +1,74 @@ +import Vuex from 'vuex' +import { config, mount } from '@vue/test-utils' +import TermsAndConditionsConfirm from './terms-and-conditions-confirm.vue' +import VueMeta from 'vue-meta' + +const localVue = global.localVue +localVue.use(VueMeta, { keyName: 'head' }) + +config.stubs['nuxt-link'] = '' + +describe('terms-and-conditions-confirm.vue', () => { + let wrapper + let mocks + let store + let asyncData + let tosAgree + let redirect + + beforeEach(() => { + mocks = { + $t: (t) => t, + } + asyncData = false + tosAgree = false + redirect = jest.fn() + }) + + describe('mount', () => { + const Wrapper = async () => { + store = new Vuex.Store({ + getters: { + 'auth/termsAndConditionsAgreed': () => tosAgree, + }, + }) + if (asyncData) { + const data = TermsAndConditionsConfirm.data ? TermsAndConditionsConfirm.data() : {} + const aData = await TermsAndConditionsConfirm.asyncData({ + store, + redirect, + }) + TermsAndConditionsConfirm.data = function () { + return { ...data, ...aData } + } + } + return mount(TermsAndConditionsConfirm, { + mocks, + localVue, + }) + } + + it('renders', async () => { + wrapper = await Wrapper() + expect(wrapper.is('div')).toBe(true) + }) + + it('has correct content', async () => { + wrapper = await Wrapper() + expect(wrapper.vm.$metaInfo.title).toBe('termsAndConditions.newTermsAndConditions') + }) + + it('renders with asyncData and did not agree to TOS', async () => { + asyncData = true + wrapper = await Wrapper() + expect(redirect).not.toHaveBeenCalled() + }) + + it('renders with asyncData and did agree to TOS', async () => { + asyncData = true + tosAgree = true + wrapper = await Wrapper() + expect(redirect).toBeCalledWith('/') + }) + }) +}) diff --git a/webapp/pages/terms-and-conditions.spec.js b/webapp/pages/terms-and-conditions.spec.js new file mode 100644 index 000000000..d6ae6dce7 --- /dev/null +++ b/webapp/pages/terms-and-conditions.spec.js @@ -0,0 +1,38 @@ +import { mount } from '@vue/test-utils' +import TermsAndConditions from './terms-and-conditions.vue' +import VueMeta from 'vue-meta' + +const localVue = global.localVue +localVue.use(VueMeta, { keyName: 'head' }) + +describe('terms-and-conditions.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: (t) => t, + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(TermsAndConditions, { + mocks, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('div')).toBe(true) + }) + + it('has correct content', () => { + expect(wrapper.vm.$metaInfo.title).toBe('site.termsAndConditions') + }) + }) +}) diff --git a/webapp/test/fileMock.js b/webapp/test/fileMock.js index 0e56c5b5f..c77f5e0de 100644 --- a/webapp/test/fileMock.js +++ b/webapp/test/fileMock.js @@ -1 +1,3 @@ -module.exports = 'test-file-stub' +module.exports = { + render: () => 'test-file-stub', +}