diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dd1aae26c..da8521a76 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -436,7 +436,7 @@ jobs: report_name: Coverage Frontend type: lcov result_path: ./coverage/lcov.info - min_coverage: 93 + min_coverage: 95 token: ${{ github.token }} ############################################################################## diff --git a/frontend/package.json b/frontend/package.json index 02a6aa2f0..29c440988 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -66,6 +66,7 @@ "vuex-persistedstate": "^4.0.0-beta.3" }, "devDependencies": { + "@apollo/client": "^3.7.4", "@intlify/eslint-plugin-vue-i18n": "^1.4.0", "@vue/cli-plugin-babel": "^3.7.0", "@vue/cli-plugin-eslint": "^3.7.0", @@ -76,6 +77,7 @@ "babel-plugin-transform-require-context": "^0.1.1", "cross-env": "^7.0.3", "dotenv-webpack": "^7.0.3", + "mock-apollo-client": "^1.2.1", "postcss": "^8.4.8", "postcss-html": "^1.3.0", "postcss-scss": "^4.0.3", diff --git a/frontend/src/pages/Community.spec.js b/frontend/src/pages/Community.spec.js index 41ecc75e9..ecfc01716 100644 --- a/frontend/src/pages/Community.spec.js +++ b/frontend/src/pages/Community.spec.js @@ -1,58 +1,59 @@ import { mount } from '@vue/test-utils' import Community from './Community' -import { toastErrorSpy, toastSuccessSpy } from '@test/testSetup' +import { toastErrorSpy, toastSuccessSpy, toastInfoSpy } from '@test/testSetup' import { createContribution, updateContribution, deleteContribution } from '@/graphql/mutations' -import { listContributions, listAllContributions } from '@/graphql/queries' +import { listContributions, listAllContributions, openCreations } from '@/graphql/queries' +import { createMockClient } from 'mock-apollo-client' +import VueApollo from 'vue-apollo' + +const mockClient = createMockClient() +const apolloProvider = new VueApollo({ + defaultClient: mockClient, +}) const localVue = global.localVue +localVue.use(VueApollo) const mockStoreDispach = jest.fn() -const apolloQueryMock = jest.fn() -const apolloMutationMock = jest.fn() -const apolloRefetchMock = jest.fn() +const routerPushMock = jest.fn() describe('Community', () => { let wrapper - const mocks = { - $t: jest.fn((t) => t), - $d: jest.fn((d) => d), - $apollo: { - query: apolloQueryMock, - mutate: apolloMutationMock, - queries: { - OpenCreations: { - refetch: apolloRefetchMock, + mockClient.setRequestHandler( + openCreations, + jest + .fn() + .mockRejectedValueOnce({ message: 'Open Creations failed!' }) + .mockResolvedValue({ + data: { + openCreations: [ + { + month: 0, + year: 2023, + amount: '1000', + }, + { + month: 1, + year: 2023, + amount: '1000', + }, + { + month: 2, + year: 2023, + amount: '1000', + }, + ], }, - }, - }, - $store: { - dispatch: mockStoreDispach, - state: { - creation: ['1000', '1000', '1000'], - }, - }, - $i18n: { - locale: 'en', - }, - $router: { - push: jest.fn(), - }, - $route: { - hash: 'my', - }, - } + }), + ) - const Wrapper = () => { - return mount(Community, { - localVue, - mocks, - }) - } - - describe('mount', () => { - beforeEach(() => { - apolloQueryMock.mockResolvedValue({ + mockClient.setRequestHandler( + listContributions, + jest + .fn() + .mockRejectedValueOnce({ message: 'List Contributions failed!' }) + .mockResolvedValue({ data: { listContributions: { contributionList: [ @@ -64,10 +65,40 @@ describe('Community', () => { deletedAt: null, confirmedBy: null, confirmedAt: null, + firstName: 'Bibi', + contributionDate: '2022-07-15T08:47:06.000Z', + lastName: 'Bloxberg', + state: 'IN_PROGRESS', + messagesCount: 0, + }, + { + id: 1550, + amount: '200', + memo: 'Fleisig, fleisig am Arbeiten gewesen', + createdAt: '2022-07-15T08:47:06.000Z', + deletedAt: null, + confirmedBy: null, + confirmedAt: null, + firstName: 'Bibi', + contributionDate: '2022-06-15T08:47:06.000Z', + lastName: 'Bloxberg', + state: 'CONFIRMED', + messagesCount: 0, }, ], contributionCount: 1, }, + }, + }), + ) + + mockClient.setRequestHandler( + listAllContributions, + jest + .fn() + .mockRejectedValueOnce({ message: 'List All Contributions failed!' }) + .mockResolvedValue({ + data: { listAllContributions: { contributionList: [ { @@ -75,29 +106,137 @@ describe('Community', () => { amount: '200', memo: 'Fleisig, fleisig am Arbeiten mein lieber Freund, 50 Zeichen sind viel', createdAt: '2022-07-15T08:47:06.000Z', + contributionDate: '2022-07-15T08:47:06.000Z', deletedAt: null, confirmedBy: null, confirmedAt: null, + firstName: 'Bibi', + lastName: 'Bloxberg', + }, + { + id: 1550, + amount: '200', + memo: 'Fleisig, fleisig am Arbeiten gewesen', + createdAt: '2022-07-15T08:47:06.000Z', + deletedAt: null, + confirmedBy: null, + confirmedAt: null, + firstName: 'Bibi', + contributionDate: '2022-06-15T08:47:06.000Z', + lastName: 'Bloxberg', + messagesCount: 0, }, { id: 1556, amount: '400', memo: 'Ein anderer lieber Freund ist auch sehr felißig am Arbeiten!!!!', createdAt: '2022-07-16T08:47:06.000Z', + contributionDate: '2022-07-16T08:47:06.000Z', deletedAt: null, confirmedBy: null, confirmedAt: null, + firstName: 'Bob', + lastName: 'der Baumeister', }, ], - contributionCount: 2, + contributionCount: 3, }, }, - }) + }), + ) + + mockClient.setRequestHandler( + createContribution, + jest + .fn() + .mockRejectedValueOnce({ message: 'Create Contribution failed!' }) + .mockResolvedValue({ + data: { + createContribution: true, + }, + }), + ) + + mockClient.setRequestHandler( + updateContribution, + jest + .fn() + .mockRejectedValueOnce({ message: 'Update Contribution failed!' }) + .mockResolvedValue({ + data: { + updateContribution: true, + }, + }), + ) + + mockClient.setRequestHandler( + deleteContribution, + jest + .fn() + .mockRejectedValueOnce({ message: 'Delete Contribution failed!' }) + .mockResolvedValue({ + data: { + deleteContribution: true, + }, + }), + ) + + const mocks = { + $t: jest.fn((t) => t), + $d: jest.fn((d) => d), + $store: { + dispatch: mockStoreDispach, + state: { + user: { + firstName: 'Bibi', + lastName: 'Bloxberg', + }, + }, + }, + $i18n: { + locale: 'en', + }, + $router: { + push: routerPushMock, + }, + $route: { + hash: '#edit', + }, + } + + const Wrapper = () => { + return mount(Community, { + localVue, + mocks, + apolloProvider, + }) + } + + let apolloMutateSpy + let refetchContributionsSpy + let refetchAllContributionsSpy + let refetchOpenCreationsSpy + + describe('mount', () => { + beforeEach(() => { + jest.clearAllMocks() wrapper = Wrapper() + apolloMutateSpy = jest.spyOn(wrapper.vm.$apollo, 'mutate') + refetchContributionsSpy = jest.spyOn(wrapper.vm.$apollo.queries.ListContributions, 'refetch') + refetchAllContributionsSpy = jest.spyOn( + wrapper.vm.$apollo.queries.ListAllContributions, + 'refetch', + ) + refetchOpenCreationsSpy = jest.spyOn(wrapper.vm.$apollo.queries.OpenCreations, 'refetch') }) - it('has a DIV .community-page', () => { - expect(wrapper.find('div.community-page').exists()).toBe(true) + describe('server response for queries is error', () => { + it('toasts three errors', () => { + expect(toastErrorSpy).toBeCalledTimes(3) + expect(toastErrorSpy).toBeCalledWith('Open Creations failed!') + expect(toastErrorSpy).toBeCalledWith('List Contributions failed!') + expect(toastErrorSpy).toBeCalledWith('List All Contributions failed!') + }) }) describe('tabs', () => { @@ -105,60 +244,49 @@ describe('Community', () => { expect(wrapper.findAll('div[role="tabpanel"]')).toHaveLength(3) }) - it.todo('check for correct tabIndex if state is "IN_PROGRESS" or not') + it('check for correct tabIndex if state is "IN_PROGRESS" or not', () => { + expect(routerPushMock).toBeCalledWith({ path: '/community#my' }) + }) + + it('toasts an info', () => { + expect(toastInfoSpy).toBeCalledWith('contribution.alert.answerQuestionToast') + }) }) describe('API calls after creation', () => { + it('has a DIV .community-page', () => { + expect(wrapper.find('div.community-page').exists()).toBe(true) + }) + it('emits update transactions', () => { expect(wrapper.emitted('update-transactions')).toEqual([[0]]) }) - - it('queries list of own contributions', () => { - expect(apolloQueryMock).toBeCalledWith({ - fetchPolicy: 'no-cache', - query: listContributions, - variables: { - currentPage: 1, - pageSize: 25, - }, - }) - }) - - it('queries list of all contributions', () => { - expect(apolloQueryMock).toBeCalledWith({ - fetchPolicy: 'no-cache', - query: listAllContributions, - variables: { - currentPage: 1, - pageSize: 25, - }, - }) - }) - - describe('server response is error', () => { - beforeEach(() => { - jest.clearAllMocks() - apolloQueryMock.mockRejectedValue({ message: 'Ups' }) - wrapper = Wrapper() - }) - - it('toasts two errors', () => { - expect(toastErrorSpy).toBeCalledTimes(2) - expect(toastErrorSpy).toBeCalledWith('Ups') - }) - }) }) - describe('set contrubtion', () => { + describe('save contrubtion', () => { + describe('with error', () => { + const now = new Date().toISOString() + beforeEach(async () => { + await wrapper.setData({ + form: { + id: null, + date: now, + memo: 'Mein Beitrag zur Gemeinschaft für diesen Monat ...', + amount: '200', + }, + }) + await wrapper.find('form').trigger('submit') + }) + + it('toasts the error message', () => { + expect(toastErrorSpy).toBeCalledWith('Create Contribution failed!') + }) + }) + describe('with success', () => { const now = new Date().toISOString() beforeEach(async () => { jest.clearAllMocks() - apolloMutationMock.mockResolvedValue({ - data: { - createContribution: true, - }, - }) await wrapper.setData({ form: { id: null, @@ -171,7 +299,7 @@ describe('Community', () => { }) it('calls the create contribution mutation', () => { - expect(apolloMutationMock).toBeCalledWith({ + expect(apolloMutateSpy).toBeCalledWith({ fetchPolicy: 'no-cache', mutation: createContribution, variables: { @@ -187,62 +315,49 @@ describe('Community', () => { }) it('updates the contribution list', () => { - expect(apolloQueryMock).toBeCalledWith({ - fetchPolicy: 'no-cache', - query: listContributions, - variables: { - currentPage: 1, - pageSize: 25, - }, - }) + expect(refetchContributionsSpy).toBeCalled() }) - it('verifies the login (to get the new creations available)', () => { - expect(apolloRefetchMock).toBeCalled() + it('updates the all contribution list', () => { + expect(refetchAllContributionsSpy).toBeCalled() }) - it('set all data to the default values)', () => { + it('updates the open creations', () => { + expect(refetchOpenCreationsSpy).toBeCalled() + }) + + it('sets all data to the default values)', () => { expect(wrapper.vm.form.id).toBe(null) expect(wrapper.vm.form.date).toBe('') expect(wrapper.vm.form.memo).toBe('') expect(wrapper.vm.form.amount).toBe('') }) }) - - describe('with error', () => { - const now = new Date().toISOString() - beforeEach(async () => { - jest.clearAllMocks() - apolloMutationMock.mockRejectedValue({ - message: 'Ouch!', - }) - await wrapper.setData({ - form: { - id: null, - date: now, - memo: 'Mein Beitrag zur Gemeinschaft für diesen Monat ...', - amount: '200', - }, - }) - await wrapper.find('form').trigger('submit') - }) - - it('toasts the error message', () => { - expect(toastErrorSpy).toBeCalledWith('Ouch!') - }) - }) }) describe('update contrubtion', () => { + describe('with error', () => { + const now = new Date().toISOString() + beforeEach(async () => { + await wrapper + .findComponent({ name: 'ContributionForm' }) + .vm.$emit('update-contribution', { + id: 2, + date: now, + memo: 'Mein Beitrag zur Gemeinschaft für diesen Monat ...', + amount: '400', + }) + }) + + it('toasts the error message', () => { + expect(toastErrorSpy).toBeCalledWith('Update Contribution failed!') + }) + }) + describe('with success', () => { const now = new Date().toISOString() beforeEach(async () => { jest.clearAllMocks() - apolloMutationMock.mockResolvedValue({ - data: { - updateContribution: true, - }, - }) await wrapper .findComponent({ name: 'ContributionForm' }) .vm.$emit('update-contribution', { @@ -254,7 +369,7 @@ describe('Community', () => { }) it('calls the update contribution mutation', () => { - expect(apolloMutationMock).toBeCalledWith({ + expect(apolloMutateSpy).toBeCalledWith({ fetchPolicy: 'no-cache', mutation: updateContribution, variables: { @@ -271,40 +386,15 @@ describe('Community', () => { }) it('updates the contribution list', () => { - expect(apolloQueryMock).toBeCalledWith({ - fetchPolicy: 'no-cache', - query: listContributions, - variables: { - currentPage: 1, - pageSize: 25, - }, - }) + expect(refetchContributionsSpy).toBeCalled() }) - it('verifies the login (to get the new creations available)', () => { - expect(apolloRefetchMock).toBeCalled() - }) - }) - - describe('with error', () => { - const now = new Date().toISOString() - beforeEach(async () => { - jest.clearAllMocks() - apolloMutationMock.mockRejectedValue({ - message: 'Oh No!', - }) - await wrapper - .findComponent({ name: 'ContributionForm' }) - .vm.$emit('update-contribution', { - id: 2, - date: now, - memo: 'Mein Beitrag zur Gemeinschaft für diesen Monat ...', - amount: '400', - }) + it('updates the all contribution list', () => { + expect(refetchAllContributionsSpy).toBeCalled() }) - it('toasts the error message', () => { - expect(toastErrorSpy).toBeCalledWith('Oh No!') + it('updates the open creations', () => { + expect(refetchOpenCreationsSpy).toBeCalled() }) }) }) @@ -314,22 +404,28 @@ describe('Community', () => { beforeEach(async () => { await wrapper.setData({ tabIndex: 1 }) - contributionListComponent = await wrapper.findComponent({ name: 'ContributionList' }) + contributionListComponent = wrapper.findComponent({ name: 'ContributionList' }) + }) + + describe('with error', () => { + beforeEach(async () => { + jest.clearAllMocks() + contributionListComponent.vm.$emit('delete-contribution', { id: 2 }) + }) + + it('toasts the error message', () => { + expect(toastErrorSpy).toBeCalledWith('Delete Contribution failed!') + }) }) describe('with success', () => { beforeEach(async () => { jest.clearAllMocks() - apolloMutationMock.mockResolvedValue({ - data: { - deleteContribution: true, - }, - }) - contributionListComponent.vm.$emit('delete-contribution', { id: 2 }) + await contributionListComponent.vm.$emit('delete-contribution', { id: 2 }) }) it('calls the API', () => { - expect(apolloMutationMock).toBeCalledWith({ + expect(apolloMutateSpy).toBeCalledWith({ fetchPolicy: 'no-cache', mutation: deleteContribution, variables: { @@ -343,37 +439,20 @@ describe('Community', () => { }) it('updates the contribution list', () => { - expect(apolloQueryMock).toBeCalledWith({ - fetchPolicy: 'no-cache', - query: listContributions, - variables: { - currentPage: 1, - pageSize: 25, - }, - }) + expect(refetchContributionsSpy).toBeCalled() }) - it('verifies the login (to get the new creations available)', () => { - expect(apolloRefetchMock).toBeCalled() - }) - }) - - describe('with error', () => { - beforeEach(async () => { - jest.clearAllMocks() - apolloMutationMock.mockRejectedValue({ - message: 'Oh my god!', - }) - contributionListComponent.vm.$emit('delete-contribution', { id: 2 }) + it('updates the all contribution list', () => { + expect(refetchAllContributionsSpy).toBeCalled() }) - it('toasts the error message', () => { - expect(toastErrorSpy).toBeCalledWith('Oh my god!') + it('updates the open creations', () => { + expect(refetchOpenCreationsSpy).toBeCalled() }) }) }) - describe.skip('update contribution form', () => { + describe('update contribution form', () => { const now = new Date().toISOString() beforeEach(async () => { await wrapper.setData({ tabIndex: 1 }) @@ -391,12 +470,58 @@ describe('Community', () => { expect(wrapper.vm.form.id).toBe(2) expect(wrapper.vm.form.date).toBe(now) expect(wrapper.vm.form.memo).toBe('Mein Beitrag zur Gemeinschaft für diesen Monat ...') - expect(wrapper.vm.form.amount).toBe('400') + expect(wrapper.vm.form.amount).toBe('400.00') }) it('sets tab index back to 0', () => { expect(wrapper.vm.tabIndex).toBe(0) }) }) + + describe('update list all contributions', () => { + beforeEach(async () => { + jest.clearAllMocks() + await wrapper.setData({ tabIndex: 2 }) + await wrapper + .findAllComponents({ name: 'ContributionList' }) + .at(1) + .vm.$emit('update-list-contributions', { + currentPage: 2, + pageSize: 5, + }) + }) + + it('updates page size and current page', () => { + expect(wrapper.vm.pageSizeAll).toBe(5) + expect(wrapper.vm.currentPageAll).toBe(2) + }) + + it('updates the all contribution list', () => { + expect(refetchAllContributionsSpy).toBeCalled() + }) + }) + + describe('update list contributions', () => { + beforeEach(async () => { + jest.clearAllMocks() + await wrapper.setData({ tabIndex: 1 }) + await wrapper + .findAllComponents({ name: 'ContributionList' }) + .at(0) + .vm.$emit('update-list-contributions', { + currentPage: 2, + pageSize: 5, + }) + }) + + it('updates page size and current page', () => { + expect(wrapper.vm.pageSize).toBe(5) + expect(wrapper.vm.currentPage).toBe(2) + }) + + it('updates the all contribution list', () => { + expect(refetchContributionsSpy).toBeCalled() + }) + }) }) }) diff --git a/frontend/src/pages/Community.vue b/frontend/src/pages/Community.vue index 5e4186c21..5b771eac9 100644 --- a/frontend/src/pages/Community.vue +++ b/frontend/src/pages/Community.vue @@ -10,7 +10,7 @@ />