diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dc91f2fa..d52ba760c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,72 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +#### [1.20.0](https://github.com/gradido/gradido/compare/1.19.1...1.20.0) + +- fix(backend): no await for emails [`#2918`](https://github.com/gradido/gradido/pull/2918) +- fix(frontend): no receiver on send by link [`#2933`](https://github.com/gradido/gradido/pull/2933) +- fix(admin): pagination set currentPage by switch tabs [`#2902`](https://github.com/gradido/gradido/pull/2902) +- fix(federation): correct export of the community url [`#2931`](https://github.com/gradido/gradido/pull/2931) +- fix(frontend): displayed decay duration [`#2927`](https://github.com/gradido/gradido/pull/2927) +- fix(frontend): community tab navigation [`#2928`](https://github.com/gradido/gradido/pull/2928) +- fix(frontend): moderator id missing [`#2925`](https://github.com/gradido/gradido/pull/2925) +- fix(frontend): reset button send coins [`#2924`](https://github.com/gradido/gradido/pull/2924) +- refactor(backend): eslint plugin import export style [`#2908`](https://github.com/gradido/gradido/pull/2908) +- fix(backend): vscode intellisense fixes [`#2919`](https://github.com/gradido/gradido/pull/2919) +- refactor(backend): eslint import-no-cycle enabled [`#2905`](https://github.com/gradido/gradido/pull/2905) +- docs(backend): alias rules and conventions [`#2881`](https://github.com/gradido/gradido/pull/2881) +- refactor(backend): get transaction list [`#2923`](https://github.com/gradido/gradido/pull/2923) +- feat(backend): previous balance in transaction [`#2914`](https://github.com/gradido/gradido/pull/2914) +- refactor(backend): eslint update packages [`#2829`](https://github.com/gradido/gradido/pull/2829) +- feat(frontend): send coins via gradido ID [`#2837`](https://github.com/gradido/gradido/pull/2837) +- refactor(database): cleanup database [`#2808`](https://github.com/gradido/gradido/pull/2808) +- refactor(backend): eslint plugin n + fixes [`#2828`](https://github.com/gradido/gradido/pull/2828) +- feat(frontend): add link to download QR-Code [`#2889`](https://github.com/gradido/gradido/pull/2889) +- fix(other): delete node_modules and /tmp/yarn--* on start.sh [`#2904`](https://github.com/gradido/gradido/pull/2904) +- fix(admin): add confirmation modal for user role change and user (un)deletion [`#2880`](https://github.com/gradido/gradido/pull/2880) +- fix(admin): admin open contribution edit button [`#2811`](https://github.com/gradido/gradido/pull/2811) +- fix(backend): import order [`#2911`](https://github.com/gradido/gradido/pull/2911) +- fix(other): use default values for undefined .env database values [`#2910`](https://github.com/gradido/gradido/pull/2910) +- refactor(backend): eslint plugin import + fixes [`#2827`](https://github.com/gradido/gradido/pull/2827) +- refactor(backend): missing event tests [`#2900`](https://github.com/gradido/gradido/pull/2900) +- refactor(backend): unify event names [`#2799`](https://github.com/gradido/gradido/pull/2799) +- feat(backend): events for users [`#2797`](https://github.com/gradido/gradido/pull/2797) +- fix(backend): subscription get's email and languages from user [`#2885`](https://github.com/gradido/gradido/pull/2885) +- refactor(backend): eslint-plugin-jest + fixes [`#2816`](https://github.com/gradido/gradido/pull/2816) +- fix(frontend): fr change ` and ´ to ' [`#2888`](https://github.com/gradido/gradido/pull/2888) +- feat(backend): events for transaction links [`#2792`](https://github.com/gradido/gradido/pull/2792) +- feat(backend): events for contributions [`#2784`](https://github.com/gradido/gradido/pull/2784) +- feat(backend): events for contribution messages [`#2783`](https://github.com/gradido/gradido/pull/2783) +- refactor(backend): upgrade coverage to 85% [`#2884`](https://github.com/gradido/gradido/pull/2884) +- refactor(backend): add klicktipp-api library [`#2883`](https://github.com/gradido/gradido/pull/2883) +- feat(backend): events for contribution links [`#2780`](https://github.com/gradido/gradido/pull/2780) +- refactor(backend): separate events in own files [`#2777`](https://github.com/gradido/gradido/pull/2777) +- refactor(backend): rename `evenProtocolType` to `eventType` [`#2776`](https://github.com/gradido/gradido/pull/2776) +- refactor(other): add yarn installAll on gradido folder [`#2703`](https://github.com/gradido/gradido/pull/2703) +- docs(federation): describe the technical federation architecture [`#2716`](https://github.com/gradido/gradido/pull/2716) +- refactor(admin): admin list contributions for creation transaction list query [`#2791`](https://github.com/gradido/gradido/pull/2791) +- refactor(workflow): separate workflow with file filter for nginx testing [`#2871`](https://github.com/gradido/gradido/pull/2871) +- feat(backend): read communities data from database [`#2807`](https://github.com/gradido/gradido/pull/2807) +- feat(federation): implement a graphql endpoint to answer getpublickey-request [`#2651`](https://github.com/gradido/gradido/pull/2651) +- refactor(workflow): add file filters to dht node and federation workflows [`#2838`](https://github.com/gradido/gradido/pull/2838) +- refactor(workflow): separate test workflow for end-to-end tests [`#2836`](https://github.com/gradido/gradido/pull/2836) +- refactor(workflow): separate test workflow for frontend [`#2835`](https://github.com/gradido/gradido/pull/2835) +- feat(federation): add federation modul to deployment scripts [`#2733`](https://github.com/gradido/gradido/pull/2733) +- refactor(database): event table [`#2720`](https://github.com/gradido/gradido/pull/2720) +- refactor(workflow): separate test workflow with file change filters for admin interface [`#2734`](https://github.com/gradido/gradido/pull/2734) +- fix(admin): fix translation in menu (english) [`#2814`](https://github.com/gradido/gradido/pull/2814) +- refactor(workflow): configure jest to directly check coverage to schlanken the test workflows [`#2790`](https://github.com/gradido/gradido/pull/2790) +- fix(database): removed commands from package.json not working [`#2805`](https://github.com/gradido/gradido/pull/2805) +- fix(other): repair UserProfile.ChangePassword.feature scenario [`#2782`](https://github.com/gradido/gradido/pull/2782) +- feat(backend): test double redeem transaction links [`#2788`](https://github.com/gradido/gradido/pull/2788) +- feat(backend): admin open creations query [`#2813`](https://github.com/gradido/gradido/pull/2813) +- refactor(backend): eslint-plugin-type-graphql + fixes [`#2745`](https://github.com/gradido/gradido/pull/2745) + #### [1.19.1](https://github.com/gradido/gradido/compare/1.19.0...1.19.1) +> 10 March 2023 + +- chore(other): upgrade version to 1.19.1 [`#2812`](https://github.com/gradido/gradido/pull/2812) - fix(frontend): admin question clickable [`#2810`](https://github.com/gradido/gradido/pull/2810) - refactor(frontend): change b-img to b-icon send [`#2809`](https://github.com/gradido/gradido/pull/2809) - fix(admin): update openCreation in case of tab open. [`#2806`](https://github.com/gradido/gradido/pull/2806) diff --git a/admin/package.json b/admin/package.json index 3406c326a..04c9a60e8 100644 --- a/admin/package.json +++ b/admin/package.json @@ -3,7 +3,7 @@ "description": "Administraion Interface for Gradido", "main": "index.js", "author": "Moriz Wahl", - "version": "1.19.1", + "version": "1.20.0", "license": "Apache-2.0", "private": false, "scripts": { @@ -33,6 +33,7 @@ "bootstrap": "4.3.1", "bootstrap-vue": "^2.21.2", "core-js": "^3.6.5", + "date-fns": "^2.29.3", "dotenv-webpack": "^7.0.3", "express": "^4.17.1", "graphql": "^15.6.1", diff --git a/admin/src/components/ChangeUserRoleFormular.spec.js b/admin/src/components/ChangeUserRoleFormular.spec.js index 7f2154ecc..381d2ce43 100644 --- a/admin/src/components/ChangeUserRoleFormular.spec.js +++ b/admin/src/components/ChangeUserRoleFormular.spec.js @@ -28,6 +28,7 @@ const mocks = { let propsData let wrapper +let spy describe('ChangeUserRoleFormular', () => { const Wrapper = () => { @@ -70,12 +71,16 @@ describe('ChangeUserRoleFormular', () => { expect(wrapper.text()).toContain('userRole.notChangeYourSelf') }) - it('has role select disabled', () => { - expect(wrapper.find('select[disabled="disabled"]').exists()).toBe(true) + it('has no role select', () => { + expect(wrapper.find('select.role-select').exists()).toBe(false) + }) + + it('has no button', () => { + expect(wrapper.find('button.btn.btn-dange').exists()).toBe(false) }) }) - describe('change others role', () => { + describe("change other user's role", () => { let rolesToSelect describe('general', () => { @@ -106,19 +111,12 @@ describe('ChangeUserRoleFormular', () => { expect(wrapper.find('select.role-select[disabled="disabled"]').exists()).toBe(false) }) - describe('on API error', () => { - beforeEach(() => { - apolloMutateMock.mockRejectedValue({ message: 'Oh no!' }) - rolesToSelect.at(1).setSelected() - }) - - it('toasts an error message', () => { - expect(toastErrorSpy).toBeCalledWith('Oh no!') - }) + it('has "change_user_role" button', () => { + expect(wrapper.find('button.btn.btn-danger').text()).toBe('change_user_role') }) }) - describe('user is usual user', () => { + describe('user has role "usual user"', () => { beforeEach(() => { apolloMutateMock.mockResolvedValue({ data: { @@ -141,6 +139,10 @@ describe('ChangeUserRoleFormular', () => { describe('change select to', () => { describe('same role', () => { + it('has "change_user_role" button disabled', () => { + expect(wrapper.find('button.btn.btn-danger[disabled="disabled"]').exists()).toBe(true) + }) + it('does not call the API', () => { rolesToSelect.at(0).setSelected() expect(apolloMutateMock).not.toHaveBeenCalled() @@ -152,39 +154,75 @@ describe('ChangeUserRoleFormular', () => { rolesToSelect.at(1).setSelected() }) - it('calls the API', () => { - expect(apolloMutateMock).toBeCalledWith( - expect.objectContaining({ - mutation: setUserRole, - variables: { - userId: 1, - isAdmin: true, - }, - }), + it('has "change_user_role" button enabled', () => { + expect(wrapper.find('button.btn.btn-danger').exists()).toBe(true) + expect(wrapper.find('button.btn.btn-danger[disabled="disabled"]').exists()).toBe( + false, ) }) - it('emits "updateIsAdmin"', () => { - expect(wrapper.emitted('updateIsAdmin')).toEqual( - expect.arrayContaining([ - expect.arrayContaining([ - { - userId: 1, - isAdmin: expect.any(Date), - }, - ]), - ]), - ) - }) + describe('clicking the "change_user_role" button', () => { + beforeEach(async () => { + spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') + spy.mockImplementation(() => Promise.resolve(true)) + await wrapper.find('button').trigger('click') + await wrapper.vm.$nextTick() + }) - it('toasts success message', () => { - expect(toastSuccessSpy).toBeCalledWith('userRole.successfullyChangedTo') + it('calls the modal', () => { + expect(wrapper.emitted('showModal')) + expect(spy).toHaveBeenCalled() + }) + + describe('confirm role change with success', () => { + it('calls the API', () => { + expect(apolloMutateMock).toBeCalledWith( + expect.objectContaining({ + mutation: setUserRole, + variables: { + userId: 1, + isAdmin: true, + }, + }), + ) + }) + + it('emits "updateIsAdmin"', () => { + expect(wrapper.emitted('updateIsAdmin')).toEqual( + expect.arrayContaining([ + expect.arrayContaining([ + { + userId: 1, + isAdmin: expect.any(Date), + }, + ]), + ]), + ) + }) + + it('toasts success message', () => { + expect(toastSuccessSpy).toBeCalledWith('userRole.successfullyChangedTo') + }) + }) + + describe('confirm role change with error', () => { + beforeEach(async () => { + spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') + apolloMutateMock.mockRejectedValue({ message: 'Oh no!' }) + await wrapper.find('button').trigger('click') + await wrapper.vm.$nextTick() + }) + + it('toasts an error message', () => { + expect(toastErrorSpy).toBeCalledWith('Oh no!') + }) + }) }) }) }) }) - describe('user is admin', () => { + describe('user has role "admin"', () => { beforeEach(() => { apolloMutateMock.mockResolvedValue({ data: { @@ -207,6 +245,10 @@ describe('ChangeUserRoleFormular', () => { describe('change select to', () => { describe('same role', () => { + it('has "change_user_role" button disabled', () => { + expect(wrapper.find('button.btn.btn-danger[disabled="disabled"]').exists()).toBe(true) + }) + it('does not call the API', () => { rolesToSelect.at(1).setSelected() expect(apolloMutateMock).not.toHaveBeenCalled() @@ -218,33 +260,69 @@ describe('ChangeUserRoleFormular', () => { rolesToSelect.at(0).setSelected() }) - it('calls the API', () => { - expect(apolloMutateMock).toBeCalledWith( - expect.objectContaining({ - mutation: setUserRole, - variables: { - userId: 1, - isAdmin: false, - }, - }), + it('has "change_user_role" button enabled', () => { + expect(wrapper.find('button.btn.btn-danger').exists()).toBe(true) + expect(wrapper.find('button.btn.btn-danger[disabled="disabled"]').exists()).toBe( + false, ) }) - it('emits "updateIsAdmin"', () => { - expect(wrapper.emitted('updateIsAdmin')).toEqual( - expect.arrayContaining([ - expect.arrayContaining([ - { - userId: 1, - isAdmin: null, - }, - ]), - ]), - ) - }) + describe('clicking the "change_user_role" button', () => { + beforeEach(async () => { + spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') + spy.mockImplementation(() => Promise.resolve(true)) + await wrapper.find('button').trigger('click') + await wrapper.vm.$nextTick() + }) - it('toasts success message', () => { - expect(toastSuccessSpy).toBeCalledWith('userRole.successfullyChangedTo') + it('calls the modal', () => { + expect(wrapper.emitted('showModal')) + expect(spy).toHaveBeenCalled() + }) + + describe('confirm role change with success', () => { + it('calls the API', () => { + expect(apolloMutateMock).toBeCalledWith( + expect.objectContaining({ + mutation: setUserRole, + variables: { + userId: 1, + isAdmin: false, + }, + }), + ) + }) + + it('emits "updateIsAdmin"', () => { + expect(wrapper.emitted('updateIsAdmin')).toEqual( + expect.arrayContaining([ + expect.arrayContaining([ + { + userId: 1, + isAdmin: null, + }, + ]), + ]), + ) + }) + + it('toasts success message', () => { + expect(toastSuccessSpy).toBeCalledWith('userRole.successfullyChangedTo') + }) + }) + + describe('confirm role change with error', () => { + beforeEach(async () => { + spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') + apolloMutateMock.mockRejectedValue({ message: 'Oh no!' }) + await wrapper.find('button').trigger('click') + await wrapper.vm.$nextTick() + }) + + it('toasts an error message', () => { + expect(toastErrorSpy).toBeCalledWith('Oh no!') + }) + }) }) }) }) diff --git a/admin/src/components/ChangeUserRoleFormular.vue b/admin/src/components/ChangeUserRoleFormular.vue index 1217ce7f0..677a12f56 100644 --- a/admin/src/components/ChangeUserRoleFormular.vue +++ b/admin/src/components/ChangeUserRoleFormular.vue @@ -4,19 +4,23 @@
{{ $t('userRole.notChangeYourSelf') }}
-
+
- + +
+ + {{ $t('change_user_role') }} + +
- diff --git a/admin/src/components/Fedaration/FederationVisualizeItem.spec.js b/admin/src/components/Fedaration/FederationVisualizeItem.spec.js new file mode 100644 index 000000000..6058cc6f4 --- /dev/null +++ b/admin/src/components/Fedaration/FederationVisualizeItem.spec.js @@ -0,0 +1,183 @@ +import { mount } from '@vue/test-utils' +import FederationVisualizeItem from './FederationVisualizeItem.vue' + +const localVue = global.localVue +const today = new Date() +const createdDate = new Date() +createdDate.setDate(createdDate.getDate() - 3) + +let propsData = { + item: { + id: 7590, + foreign: false, + publicKey: 'eaf6a426b24fd54f8fbae11c17700fc595080ca25159579c63d38dbc64284ba7', + url: 'http://localhost/api/2_0', + lastAnnouncedAt: createdDate, + verifiedAt: today, + lastErrorAt: null, + createdAt: createdDate, + updatedAt: null, + }, +} + +const mocks = { + $i18n: { + locale: 'en', + }, +} + +describe('FederationVisualizeItem', () => { + let wrapper + + const Wrapper = () => { + return mount(FederationVisualizeItem, { localVue, mocks, propsData }) + } + + describe('mount', () => { + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders the component', () => { + expect(wrapper.find('div.federation-visualize-item').exists()).toBe(true) + }) + + describe('rendering item properties', () => { + it('has the url', () => { + expect(wrapper.find('.row > div:nth-child(2) > div').text()).toBe( + 'http://localhost/api/2_0', + ) + }) + + it('has the public key', () => { + expect(wrapper.find('.row > div:nth-child(2) > small').text()).toContain( + 'eaf6a426b24fd54f8fbae11c17700fc595080ca25159579c63d38dbc64284ba7'.substring(0, 26), + ) + }) + + describe('verified item', () => { + it('has the check icon', () => { + expect(wrapper.find('svg.bi-check').exists()).toBe(true) + }) + + it('has the text variant "success"', () => { + expect(wrapper.find('.text-success').exists()).toBe(true) + }) + }) + + describe('not verified item', () => { + beforeEach(() => { + propsData = { + item: { + id: 7590, + foreign: false, + publicKey: 'eaf6a426b24fd54f8fbae11c17700fc595080ca25159579c63d38dbc64284ba7', + url: 'http://localhost/api/2_0', + lastAnnouncedAt: createdDate, + verifiedAt: null, + lastErrorAt: null, + createdAt: createdDate, + updatedAt: null, + }, + } + wrapper = Wrapper() + }) + + it('has the x-circle icon', () => { + expect(wrapper.find('svg.bi-x-circle').exists()).toBe(true) + }) + + it('has the text variant "danger"', () => { + expect(wrapper.find('.text-danger').exists()).toBe(true) + }) + }) + + // describe('with different locales (de, en, fr, es, nl)', () => { + describe('lastAnnouncedAt', () => { + it('computes the time string for different locales (de, en, fr, es, nl)', () => { + wrapper.vm.$i18n.locale = 'de' + wrapper = Wrapper() + expect(wrapper.vm.lastAnnouncedAt).toBe('vor 3 Tagen') + + wrapper.vm.$i18n.locale = 'fr' + wrapper = Wrapper() + expect(wrapper.vm.lastAnnouncedAt).toBe('il y a 3 jours') + + wrapper.vm.$i18n.locale = 'es' + wrapper = Wrapper() + expect(wrapper.vm.lastAnnouncedAt).toBe('hace 3 días') + + wrapper.vm.$i18n.locale = 'nl' + wrapper = Wrapper() + expect(wrapper.vm.lastAnnouncedAt).toBe('3 dagen geleden') + }) + + describe('lastAnnouncedAt == null', () => { + beforeEach(() => { + propsData = { + item: { + id: 7590, + foreign: false, + publicKey: 'eaf6a426b24fd54f8fbae11c17700fc595080ca25159579c63d38dbc64284ba7', + url: 'http://localhost/api/2_0', + lastAnnouncedAt: null, + verifiedAt: null, + lastErrorAt: null, + createdAt: createdDate, + updatedAt: null, + }, + } + wrapper = Wrapper() + }) + + it('computes empty string', async () => { + expect(wrapper.vm.lastAnnouncedAt).toBe('') + }) + }) + }) + + describe('createdAt', () => { + it('computes the time string for different locales (de, en, fr, es, nl)', () => { + wrapper.vm.$i18n.locale = 'de' + wrapper = Wrapper() + expect(wrapper.vm.createdAt).toBe('vor 3 Tagen') + + wrapper.vm.$i18n.locale = 'fr' + wrapper = Wrapper() + expect(wrapper.vm.createdAt).toBe('il y a 3 jours') + + wrapper.vm.$i18n.locale = 'es' + wrapper = Wrapper() + expect(wrapper.vm.createdAt).toBe('hace 3 días') + + wrapper.vm.$i18n.locale = 'nl' + wrapper = Wrapper() + expect(wrapper.vm.createdAt).toBe('3 dagen geleden') + }) + + describe('createdAt == null', () => { + beforeEach(() => { + propsData = { + item: { + id: 7590, + foreign: false, + publicKey: 'eaf6a426b24fd54f8fbae11c17700fc595080ca25159579c63d38dbc64284ba7', + url: 'http://localhost/api/2_0', + lastAnnouncedAt: createdDate, + verifiedAt: null, + lastErrorAt: null, + createdAt: null, + updatedAt: null, + }, + } + wrapper = Wrapper() + }) + + it('computes empty string', async () => { + expect(wrapper.vm.createdAt).toBe('') + }) + }) + }) + }) + }) +}) diff --git a/admin/src/components/Fedaration/FederationVisualizeItem.vue b/admin/src/components/Fedaration/FederationVisualizeItem.vue new file mode 100644 index 000000000..faace7da1 --- /dev/null +++ b/admin/src/components/Fedaration/FederationVisualizeItem.vue @@ -0,0 +1,63 @@ + + diff --git a/admin/src/components/NavBar.spec.js b/admin/src/components/NavBar.spec.js index 1927f258c..6a4a69959 100644 --- a/admin/src/components/NavBar.spec.js +++ b/admin/src/components/NavBar.spec.js @@ -62,8 +62,12 @@ describe('NavBar', () => { ) }) + it('has a link to /federation', () => { + expect(wrapper.findAll('.nav-item').at(3).find('a').attributes('href')).toBe('/federation') + }) + it('has a link to /statistic', () => { - expect(wrapper.findAll('.nav-item').at(3).find('a').attributes('href')).toBe('/statistic') + expect(wrapper.findAll('.nav-item').at(4).find('a').attributes('href')).toBe('/statistic') }) }) @@ -72,7 +76,7 @@ describe('NavBar', () => { beforeEach(async () => { delete window.location window.location = '' - await wrapper.findAll('.nav-item').at(4).find('a').trigger('click') + await wrapper.findAll('.nav-item').at(5).find('a').trigger('click') }) afterEach(() => { @@ -97,7 +101,7 @@ describe('NavBar', () => { window.location = { assign: windowLocationMock, } - await wrapper.findAll('.nav-item').at(5).find('a').trigger('click') + await wrapper.findAll('.nav-item').at(6).find('a').trigger('click') }) afterEach(() => { diff --git a/admin/src/components/NavBar.vue b/admin/src/components/NavBar.vue index dae4bba91..2efeda048 100644 --- a/admin/src/components/NavBar.vue +++ b/admin/src/components/NavBar.vue @@ -1,6 +1,6 @@