diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7819a0703..78a7a8074 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -497,7 +497,7 @@ jobs: report_name: Coverage Admin Interface type: lcov result_path: ./coverage/lcov.info - min_coverage: 96 + min_coverage: 97 token: ${{ github.token }} ############################################################################## diff --git a/admin/src/components/ContributionLink/ContributionLink.spec.js b/admin/src/components/ContributionLink/ContributionLink.spec.js index e0f09f9fd..461027e64 100644 --- a/admin/src/components/ContributionLink/ContributionLink.spec.js +++ b/admin/src/components/ContributionLink/ContributionLink.spec.js @@ -42,29 +42,72 @@ describe('ContributionLink', () => { expect(wrapper.find('div.contribution-link').exists()).toBe(true) }) - describe('function editContributionLinkData', () => { - beforeEach(() => { - wrapper.vm.editContributionLinkData() + it('has one contribution link in table', () => { + expect(wrapper.find('div.contribution-link-list').find('tbody').findAll('tr')).toHaveLength(1) + }) + + it('has contribution form not visible by default', () => { + expect(wrapper.find('#newContribution').isVisible()).toBe(false) + }) + + describe('click on create new contribution', () => { + beforeEach(async () => { + await wrapper.find('[data-test="new-contribution-link-button"]').trigger('click') }) - it('emits toggle::collapse new Contribution', async () => { - await expect(wrapper.vm.$root.$emit('bv::toggle::collapse', 'newContribution')).toBeTruthy() + + it('shows the contribution form', () => { + expect(wrapper.find('#newContribution').isVisible()).toBe(true) + }) + + describe('click on create new contribution again', () => { + beforeEach(async () => { + await wrapper.find('[data-test="new-contribution-link-button"]').trigger('click') + }) + + it('closes the contribution form', () => { + expect(wrapper.find('#newContribution').isVisible()).toBe(false) + }) + }) + + describe('click on close button', () => { + beforeEach(async () => { + await wrapper.find('button.btn-secondary').trigger('click') + }) + + it('closes the contribution form', () => { + expect(wrapper.find('#newContribution').isVisible()).toBe(false) + }) }) }) - describe('function closeContributionForm', () => { + describe('edit contribution link', () => { beforeEach(async () => { - await wrapper.setData({ visible: true }) - wrapper.vm.closeContributionForm() + await wrapper + .find('div.contribution-link-list') + .find('tbody') + .findAll('tr') + .at(0) + .findAll('button') + .at(1) + .trigger('click') }) - it('emits toggle::collapse close Contribution-Form ', async () => { - await expect(wrapper.vm.$root.$emit('bv::toggle::collapse', 'newContribution')).toBeTruthy() + it('shows the contribution form', () => { + expect(wrapper.find('#newContribution').isVisible()).toBe(true) }) - it('editContributionLink is false', async () => { - await expect(wrapper.vm.editContributionLink).toBe(false) + + it('does not show the new contribution button', () => { + expect(wrapper.find('[data-test="new-contribution-link-button"]').exists()).toBe(false) }) - it('contributionLinkData is empty', async () => { - await expect(wrapper.vm.contributionLinkData).toEqual({}) + + describe('click on close button', () => { + beforeEach(async () => { + await wrapper.find('button.btn-secondary').trigger('click') + }) + + it('closes the contribution form', () => { + expect(wrapper.find('#newContribution').isVisible()).toBe(false) + }) }) }) }) diff --git a/admin/src/components/ContributionLink/ContributionLink.vue b/admin/src/components/ContributionLink/ContributionLink.vue index ca82fcd42..b369386b1 100644 --- a/admin/src/components/ContributionLink/ContributionLink.vue +++ b/admin/src/components/ContributionLink/ContributionLink.vue @@ -10,8 +10,9 @@ > {{ $t('math.plus') }} {{ $t('contributionLink.newContributionLink') }} diff --git a/admin/src/components/ContributionLink/ContributionLinkList.vue b/admin/src/components/ContributionLink/ContributionLinkList.vue index f67feced2..9d8977d04 100644 --- a/admin/src/components/ContributionLink/ContributionLinkList.vue +++ b/admin/src/components/ContributionLink/ContributionLinkList.vue @@ -70,8 +70,6 @@ export default { formatter: (value, key, item) => { if (value) { return this.$d(new Date(value)) - } else { - return null } }, }, @@ -81,8 +79,6 @@ export default { formatter: (value, key, item) => { if (value) { return this.$d(new Date(value)) - } else { - return null } }, }, diff --git a/admin/src/components/NavBar.spec.js b/admin/src/components/NavBar.spec.js index 96b7cba9c..61f126904 100644 --- a/admin/src/components/NavBar.spec.js +++ b/admin/src/components/NavBar.spec.js @@ -68,13 +68,23 @@ describe('NavBar', () => { }) describe('wallet', () => { - const assignLocationSpy = jest.fn() + const windowLocationMock = jest.fn() + const windowLocation = window.location beforeEach(async () => { + delete window.location + window.location = { + assign: windowLocationMock, + } await wrapper.findAll('.nav-item').at(5).find('a').trigger('click') }) + afterEach(() => { + delete window.location + window.location = windowLocation + }) + it.skip('changes window location to wallet', () => { - expect(assignLocationSpy).toBeCalledWith('valid-token') + expect(windowLocationMock()).toBe('valid-token') }) it('dispatches logout to store', () => { @@ -84,6 +94,7 @@ describe('NavBar', () => { describe('logout', () => { const windowLocationMock = jest.fn() + const windowLocation = window.location beforeEach(async () => { delete window.location window.location = { @@ -92,6 +103,11 @@ describe('NavBar', () => { await wrapper.findAll('.nav-item').at(5).find('a').trigger('click') }) + afterEach(() => { + delete window.location + window.location = windowLocation + }) + it('redirects to /logout', () => { expect(windowLocationMock).toBeCalledWith('http://localhost/login') }) diff --git a/admin/src/components/NavBar.vue b/admin/src/components/NavBar.vue index f8bc2b280..7e2b7d76f 100644 --- a/admin/src/components/NavBar.vue +++ b/admin/src/components/NavBar.vue @@ -10,12 +10,11 @@ {{ $t('navbar.user_search') }} - - {{ $store.state.openCreations }} {{ $t('navbar.open_creation') }} + + {{ $t('creation') }} + + {{ $store.state.openCreations }} + {{ $t('navbar.automaticContributions') }} @@ -55,7 +54,4 @@ export default { height: 2rem; padding-left: 10px; } -.bg-color-creation { - background-color: #cf1010dc; -} diff --git a/admin/src/components/Overlay.vue b/admin/src/components/Overlay.vue index 84271a422..7eb3cf99f 100644 --- a/admin/src/components/Overlay.vue +++ b/admin/src/components/Overlay.vue @@ -13,7 +13,8 @@ {{ $t('creation_for_month') }} - {{ $d(new Date(item.date), 'month') }} {{ $d(new Date(item.date), 'year') }} + {{ $d(new Date(item.contributionDate), 'month') }} + {{ $d(new Date(item.contributionDate), 'year') }} diff --git a/admin/src/components/Tables/OpenCreationsTable.vue b/admin/src/components/Tables/OpenCreationsTable.vue index d141fc0a5..faaf3a69d 100644 --- a/admin/src/components/Tables/OpenCreationsTable.vue +++ b/admin/src/components/Tables/OpenCreationsTable.vue @@ -1,6 +1,17 @@ - + + + + + + + + + + + + + + { beforeEach(() => { wrapper = Wrapper() }) + describe('apollo returns', () => { it('calls listContributionLinks', () => { expect(apolloQueryMock).toBeCalledWith( @@ -57,7 +58,7 @@ describe('ContributionLinks', () => { }) }) - describe.skip('query transaction with error', () => { + describe('query transaction with error', () => { beforeEach(() => { apolloQueryMock.mockRejectedValue({ message: 'OUCH!' }) wrapper = Wrapper() diff --git a/admin/src/pages/CreationConfirm.spec.js b/admin/src/pages/CreationConfirm.spec.js index 99dbda219..e2d52ccf7 100644 --- a/admin/src/pages/CreationConfirm.spec.js +++ b/admin/src/pages/CreationConfirm.spec.js @@ -2,7 +2,7 @@ import { mount } from '@vue/test-utils' import CreationConfirm from './CreationConfirm.vue' import { adminDeleteContribution } from '../graphql/adminDeleteContribution' import { denyContribution } from '../graphql/denyContribution' -import { listUnconfirmedContributions } from '../graphql/listUnconfirmedContributions' +import { listAllContributions } from '../graphql/listAllContributions' import { confirmContribution } from '../graphql/confirmContribution' import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup' import VueApollo from 'vue-apollo' @@ -38,50 +38,68 @@ const mocks = { const defaultData = () => { return { - listUnconfirmedContributions: [ - { - id: 1, - firstName: 'Bibi', - lastName: 'Bloxberg', - userId: 99, - email: 'bibi@bloxberg.de', - amount: 500, - memo: 'Danke für alles', - date: new Date(), - moderator: 1, - state: 'PENDING', - creation: [500, 500, 500], - messageCount: 0, - }, - { - id: 2, - firstName: 'Räuber', - lastName: 'Hotzenplotz', - userId: 100, - email: 'raeuber@hotzenplotz.de', - amount: 1000000, - memo: 'Gut Ergattert', - date: new Date(), - moderator: 1, - state: 'PENDING', - creation: [500, 500, 500], - messageCount: 0, - }, - ], + listAllContributions: { + contributionCount: 2, + contributionList: [ + { + id: 1, + firstName: 'Bibi', + lastName: 'Bloxberg', + userId: 99, + email: 'bibi@bloxberg.de', + amount: 500, + memo: 'Danke für alles', + date: new Date(), + moderator: 1, + state: 'PENDING', + creation: [500, 500, 500], + messagesCount: 0, + deniedBy: null, + deniedAt: null, + confirmedBy: null, + confirmedAt: null, + contributionDate: new Date(), + deletedBy: null, + deletedAt: null, + createdAt: new Date(), + }, + { + id: 2, + firstName: 'Räuber', + lastName: 'Hotzenplotz', + userId: 100, + email: 'raeuber@hotzenplotz.de', + amount: 1000000, + memo: 'Gut Ergattert', + date: new Date(), + moderator: 1, + state: 'PENDING', + creation: [500, 500, 500], + messagesCount: 0, + deniedBy: null, + deniedAt: null, + confirmedBy: null, + confirmedAt: null, + contributionDate: new Date(), + deletedBy: null, + deletedAt: null, + createdAt: new Date(), + }, + ], + }, } } describe('CreationConfirm', () => { let wrapper - - const listUnconfirmedContributionsMock = jest.fn() const adminDeleteContributionMock = jest.fn() const adminDenyContributionMock = jest.fn() const confirmContributionMock = jest.fn() mockClient.setRequestHandler( - listUnconfirmedContributions, - listUnconfirmedContributionsMock + listAllContributions, + jest + .fn() .mockRejectedValueOnce({ message: 'Ouch!' }) .mockResolvedValue({ data: defaultData() }), ) @@ -117,6 +135,10 @@ describe('CreationConfirm', () => { it('toast an error message', () => { expect(toastErrorSpy).toBeCalledWith('Ouch!') }) + + it('has statusFilter ["IN_PROGRESS", "PENDING"]', () => { + expect(wrapper.vm.statusFilter).toEqual(['IN_PROGRESS', 'PENDING']) + }) }) describe('server response is succes', () => { @@ -125,17 +147,7 @@ describe('CreationConfirm', () => { }) it('has two pending creations', () => { - expect(wrapper.vm.pendingCreations).toHaveLength(2) - }) - }) - - describe('store', () => { - it('commits resetOpenCreations to store', () => { - expect(storeCommitMock).toBeCalledWith('resetOpenCreations') - }) - - it('commits setOpenCreations to store', () => { - expect(storeCommitMock).toBeCalledWith('setOpenCreations', 2) + expect(wrapper.find('tbody').findAll('tr')).toHaveLength(2) }) }) @@ -316,5 +328,94 @@ describe('CreationConfirm', () => { }) }) }) + + describe('filter tabs', () => { + describe('click tab "confirmed"', () => { + let refetchSpy + + beforeEach(async () => { + jest.clearAllMocks() + refetchSpy = jest.spyOn(wrapper.vm.$apollo.queries.ListAllContributions, 'refetch') + await wrapper.find('a[data-test="confirmed"]').trigger('click') + }) + + it('has statusFilter set to ["CONFIRMED"]', () => { + expect( + wrapper.vm.$apollo.queries.ListAllContributions.observer.options.variables, + ).toMatchObject({ statusFilter: ['CONFIRMED'] }) + }) + + it('refetches contributions', () => { + expect(refetchSpy).toBeCalled() + }) + + describe('click tab "open"', () => { + beforeEach(async () => { + jest.clearAllMocks() + refetchSpy = jest.spyOn(wrapper.vm.$apollo.queries.ListAllContributions, 'refetch') + await wrapper.find('a[data-test="open"]').trigger('click') + }) + + it('has statusFilter set to ["IN_PROGRESS", "PENDING"]', () => { + expect( + wrapper.vm.$apollo.queries.ListAllContributions.observer.options.variables, + ).toMatchObject({ statusFilter: ['IN_PROGRESS', 'PENDING'] }) + }) + + it('refetches contributions', () => { + expect(refetchSpy).toBeCalled() + }) + }) + + describe('click tab "denied"', () => { + beforeEach(async () => { + jest.clearAllMocks() + refetchSpy = jest.spyOn(wrapper.vm.$apollo.queries.ListAllContributions, 'refetch') + await wrapper.find('a[data-test="denied"]').trigger('click') + }) + + it('has statusFilter set to ["DENIED"]', () => { + expect( + wrapper.vm.$apollo.queries.ListAllContributions.observer.options.variables, + ).toMatchObject({ statusFilter: ['DENIED'] }) + }) + + it('refetches contributions', () => { + expect(refetchSpy).toBeCalled() + }) + }) + + describe('click tab "all"', () => { + beforeEach(async () => { + jest.clearAllMocks() + refetchSpy = jest.spyOn(wrapper.vm.$apollo.queries.ListAllContributions, 'refetch') + await wrapper.find('a[data-test="all"]').trigger('click') + }) + + it('has statusFilter set to ["IN_PROGRESS", "PENDING", "CONFIRMED", "DENIED", "DELETED"]', () => { + expect( + wrapper.vm.$apollo.queries.ListAllContributions.observer.options.variables, + ).toMatchObject({ + statusFilter: ['IN_PROGRESS', 'PENDING', 'CONFIRMED', 'DENIED', 'DELETED'], + }) + }) + + it('refetches contributions', () => { + expect(refetchSpy).toBeCalled() + }) + }) + }) + }) + + describe('update status', () => { + beforeEach(async () => { + await wrapper.findComponent({ name: 'OpenCreationsTable' }).vm.$emit('update-state', 2) + }) + + it.skip('updates the status', () => { + expect(wrapper.vm.items.find((obj) => obj.id === 2).messagesCount).toBe(1) + expect(wrapper.vm.items.find((obj) => obj.id === 2).state).toBe('IN_PROGRESS') + }) + }) }) }) diff --git a/admin/src/pages/CreationConfirm.vue b/admin/src/pages/CreationConfirm.vue index c6576e5ba..251971d05 100644 --- a/admin/src/pages/CreationConfirm.vue +++ b/admin/src/pages/CreationConfirm.vue @@ -1,6 +1,50 @@ + + + + + {{ $t('contributions.open') }} + + {{ $store.state.openCreations }} + + + + + + + + + + + + + @@ -24,24 +68,24 @@ - diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index f070fade5..c2f0d7d23 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -181,6 +181,7 @@ export class ContributionResolver { .select('c') .from(DbContribution, 'c') .innerJoinAndSelect('c.user', 'u') + .leftJoinAndSelect('c.messages', 'm') .where(where) .orderBy('c.createdAt', order) .limit(pageSize)