diff --git a/admin/jest.config.js b/admin/jest.config.js index 253c905de..a0c8ae412 100644 --- a/admin/jest.config.js +++ b/admin/jest.config.js @@ -9,7 +9,7 @@ module.exports = { ], coverageThreshold: { global: { - lines: 96, + lines: 95, }, }, moduleFileExtensions: [ @@ -31,6 +31,9 @@ module.exports = { setupFiles: ['/test/testSetup.js', 'jest-canvas-mock'], testMatch: ['**/?(*.)+(spec|test).js?(x)'], // snapshotSerializers: ['jest-serializer-vue'], - transformIgnorePatterns: ['/node_modules/(?!vee-validate/dist/rules)'], + transformIgnorePatterns: [ + '/node_modules/(?!vee-validate/dist/rules)', + '/node_modules/(?!@babel)', + ], testEnvironment: 'jest-environment-jsdom-sixteen', // why this is still needed? should not be needed anymore since jest@26, see: https://www.npmjs.com/package/jest-environment-jsdom-sixteen } diff --git a/admin/package.json b/admin/package.json index 3f34c82c4..b1a2bd044 100644 --- a/admin/package.json +++ b/admin/package.json @@ -16,6 +16,7 @@ "lint": "eslint --max-warnings=0 --ext .js,.vue,.json .", "stylelint": "stylelint --max-warnings=0 '**/*.{scss,vue}'", "test": "cross-env TZ=UTC jest", + "test:debug": "node --inspect-brk node_modules/.bin/vue-cli-service test:unit --no-cache --watch --runInBand", "locales": "scripts/sort.sh" }, "dependencies": { diff --git a/admin/src/components/Fedaration/FederationVisualizeItem.vue b/admin/src/components/Fedaration/FederationVisualizeItem.vue deleted file mode 100644 index a947387f4..000000000 --- a/admin/src/components/Fedaration/FederationVisualizeItem.vue +++ /dev/null @@ -1,63 +0,0 @@ - - diff --git a/admin/src/components/Fedaration/FederationVisualizeItem.spec.js b/admin/src/components/Federation/CommunityVisualizeItem.spec.js similarity index 53% rename from admin/src/components/Fedaration/FederationVisualizeItem.spec.js rename to admin/src/components/Federation/CommunityVisualizeItem.spec.js index 6058cc6f4..2c5065ba0 100644 --- a/admin/src/components/Fedaration/FederationVisualizeItem.spec.js +++ b/admin/src/components/Federation/CommunityVisualizeItem.spec.js @@ -1,36 +1,83 @@ import { mount } from '@vue/test-utils' -import FederationVisualizeItem from './FederationVisualizeItem.vue' +import Vuex from 'vuex' +import CommunityVisualizeItem from './CommunityVisualizeItem.vue' const localVue = global.localVue +localVue.use(Vuex) const today = new Date() const createdDate = new Date() createdDate.setDate(createdDate.getDate() - 3) +// Mock für den Vuex-Store +const store = new Vuex.Store({ + state: { + moderator: { + roles: ['ADMIN'], + }, + }, +}) + let propsData = { item: { - id: 7590, + id: 1, foreign: false, - publicKey: 'eaf6a426b24fd54f8fbae11c17700fc595080ca25159579c63d38dbc64284ba7', - url: 'http://localhost/api/2_0', - lastAnnouncedAt: createdDate, - verifiedAt: today, - lastErrorAt: null, + url: 'http://localhost/api/', + publicKey: '4007170edd8d33fb009cd99ee4e87f214e7cd21b668d45540a064deb42e243c2', + communityUuid: '5ab0befd-b150-4f31-a631-7f3637e47b21', + authenticatedAt: null, + name: 'Gradido Test', + description: 'Gradido Community zum testen', + gmsApiKey: '', + creationDate: createdDate, createdAt: createdDate, - updatedAt: null, + updatedAt: createdDate, + federatedCommunities: [ + { + id: 2046, + apiVersion: '2_0', + endPoint: 'http://localhost/api/', + lastAnnouncedAt: createdDate, + verifiedAt: today, + lastErrorAt: null, + createdAt: createdDate, + updatedAt: null, + }, + { + id: 2045, + apiVersion: '1_1', + endPoint: 'http://localhost/api/', + lastAnnouncedAt: null, + verifiedAt: null, + lastErrorAt: null, + createdAt: '2024-01-16T10:08:21.550Z', + updatedAt: null, + }, + { + id: 2044, + apiVersion: '1_0', + endPoint: 'http://localhost/api/', + lastAnnouncedAt: null, + verifiedAt: null, + lastErrorAt: null, + createdAt: '2024-01-16T10:08:21.544Z', + updatedAt: null, + }, + ], }, } const mocks = { + $t: (key) => key, $i18n: { locale: 'en', }, } -describe('FederationVisualizeItem', () => { +describe('CommunityVisualizeItem', () => { let wrapper const Wrapper = () => { - return mount(FederationVisualizeItem, { localVue, mocks, propsData }) + return mount(CommunityVisualizeItem, { localVue, mocks, propsData, store }) } describe('mount', () => { @@ -39,19 +86,35 @@ describe('FederationVisualizeItem', () => { }) it('renders the component', () => { - expect(wrapper.find('div.federation-visualize-item').exists()).toBe(true) + expect(wrapper.exists()).toBe(true) + expect(wrapper.find('div.community-visualize-item').exists()).toBe(true) + expect(wrapper.find('.details').exists()).toBe(false) + }) + + it('toggles details on click', async () => { + // Click the row to toggle details + await wrapper.find('.row').trigger('click') + + // Assert that details are now open + expect(wrapper.find('.details').exists()).toBe(true) + + // Click the row again to toggle details back + await wrapper.find('.row').trigger('click') + + // Assert that details are now closed + expect(wrapper.find('.details').exists()).toBe(false) }) describe('rendering item properties', () => { it('has the url', () => { - expect(wrapper.find('.row > div:nth-child(2) > div').text()).toBe( - 'http://localhost/api/2_0', + expect(wrapper.find('.row > div:nth-child(2) > div > a').text()).toBe( + 'http://localhost/api/', ) }) it('has the public key', () => { expect(wrapper.find('.row > div:nth-child(2) > small').text()).toContain( - 'eaf6a426b24fd54f8fbae11c17700fc595080ca25159579c63d38dbc64284ba7'.substring(0, 26), + '4007170edd8d33fb009cd99ee4e87f214e7cd21b668d45540a064deb42e243c2'.substring(0, 26), ) }) @@ -65,33 +128,6 @@ describe('FederationVisualizeItem', () => { }) }) - 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)', () => { @@ -155,6 +191,30 @@ describe('FederationVisualizeItem', () => { expect(wrapper.vm.createdAt).toBe('3 dagen geleden') }) + describe('not verified item', () => { + beforeEach(() => { + propsData = { + item: { + id: 7590, + foreign: false, + publicKey: 'eaf6a426b24fd54f8fbae11c17700fc595080ca25159579c63d38dbc64284ba7', + url: 'http://localhost/api/', + 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('createdAt == null', () => { beforeEach(() => { propsData = { @@ -163,9 +223,9 @@ describe('FederationVisualizeItem', () => { foreign: false, publicKey: 'eaf6a426b24fd54f8fbae11c17700fc595080ca25159579c63d38dbc64284ba7', url: 'http://localhost/api/2_0', - lastAnnouncedAt: createdDate, - verifiedAt: null, - lastErrorAt: null, + communityUuid: '5ab0befd-b150-4f31-a631-7f3637e47b21', + authenticatedAt: null, + creationDate: null, createdAt: null, updatedAt: null, }, diff --git a/admin/src/components/Federation/CommunityVisualizeItem.vue b/admin/src/components/Federation/CommunityVisualizeItem.vue new file mode 100644 index 000000000..24e6b3712 --- /dev/null +++ b/admin/src/components/Federation/CommunityVisualizeItem.vue @@ -0,0 +1,160 @@ + + diff --git a/admin/src/components/Federation/FederationVisualizeItem.vue b/admin/src/components/Federation/FederationVisualizeItem.vue new file mode 100644 index 000000000..9172f9a94 --- /dev/null +++ b/admin/src/components/Federation/FederationVisualizeItem.vue @@ -0,0 +1,47 @@ + + diff --git a/admin/src/components/input/EditableLabel.spec.js b/admin/src/components/input/EditableLabel.spec.js new file mode 100644 index 000000000..0b2462b30 --- /dev/null +++ b/admin/src/components/input/EditableLabel.spec.js @@ -0,0 +1,83 @@ +// Test written by ChatGPT 3.5 +import { mount } from '@vue/test-utils' +import EditableLabel from './EditableLabel.vue' + +const localVue = global.localVue +const value = 'test label value' + +describe('EditableLabel', () => { + let wrapper + + const createWrapper = (propsData) => { + return mount(EditableLabel, { + localVue, + propsData, + }) + } + + it('renders the label when not editing', () => { + wrapper = createWrapper({ value, allowEdit: true }) + + expect(wrapper.find('label').text()).toBe(value) + }) + + it('renders the input when editing', async () => { + wrapper = createWrapper({ value, allowEdit: true }) + + await wrapper.find('button').trigger('click') + + expect(wrapper.find('input').exists()).toBe(true) + }) + + it('emits save event when clicking save button', async () => { + wrapper = createWrapper({ value, allowEdit: true }) + + await wrapper.find('button').trigger('click') + await wrapper.setData({ inputValue: 'New Value' }) + await wrapper.find('button').trigger('click') + + expect(wrapper.emitted().save).toBeTruthy() + expect(wrapper.emitted().save[0][0]).toBe('New Value') + }) + + it('disables save button when value is not changed', async () => { + wrapper = createWrapper({ value, allowEdit: true }) + + await wrapper.find('button').trigger('click') + + expect(wrapper.find('button').attributes('disabled')).toBe('disabled') + }) + + it('enables save button when value is changed', async () => { + wrapper = createWrapper({ value, allowEdit: true }) + + await wrapper.find('button').trigger('click') + await wrapper.setData({ inputValue: 'New Value' }) + + expect(wrapper.find('button').attributes('disabled')).toBeFalsy() + }) + + it('updates originalValue when saving changes', async () => { + wrapper = createWrapper({ value, allowEdit: true }) + + await wrapper.find('button').trigger('click') + await wrapper.setData({ inputValue: 'New Value' }) + await wrapper.find('button').trigger('click') + + expect(wrapper.vm.originalValue).toBe('New Value') + }) + + it('changes variant to success when editing', async () => { + wrapper = createWrapper({ value, allowEdit: true }) + + await wrapper.find('button').trigger('click') + + expect(wrapper.vm.variant).toBe('success') + }) + + it('changes variant to prime when not editing', async () => { + wrapper = createWrapper({ value, allowEdit: true }) + + expect(wrapper.vm.variant).toBe('prime') + }) +}) diff --git a/admin/src/components/input/EditableLabel.vue b/admin/src/components/input/EditableLabel.vue new file mode 100644 index 000000000..674fcd3e7 --- /dev/null +++ b/admin/src/components/input/EditableLabel.vue @@ -0,0 +1,64 @@ + + + diff --git a/admin/src/graphql/allCommunities.js b/admin/src/graphql/allCommunities.js new file mode 100644 index 000000000..6379086c7 --- /dev/null +++ b/admin/src/graphql/allCommunities.js @@ -0,0 +1,29 @@ +import gql from 'graphql-tag' + +export const allCommunities = gql` + query { + allCommunities { + foreign + url + publicKey + uuid + authenticatedAt + name + description + gmsApiKey + creationDate + createdAt + updatedAt + federatedCommunities { + id + apiVersion + endPoint + lastAnnouncedAt + verifiedAt + lastErrorAt + createdAt + updatedAt + } + } + } +` diff --git a/admin/src/graphql/getCommunities.js b/admin/src/graphql/getCommunities.js deleted file mode 100644 index ccf894f6b..000000000 --- a/admin/src/graphql/getCommunities.js +++ /dev/null @@ -1,17 +0,0 @@ -import gql from 'graphql-tag' - -export const getCommunities = gql` - query { - getCommunities { - id - foreign - publicKey - url - lastAnnouncedAt - verifiedAt - lastErrorAt - createdAt - updatedAt - } - } -` diff --git a/admin/src/graphql/updateHomeCommunity.js b/admin/src/graphql/updateHomeCommunity.js new file mode 100644 index 000000000..a43d6edd2 --- /dev/null +++ b/admin/src/graphql/updateHomeCommunity.js @@ -0,0 +1,9 @@ +import gql from 'graphql-tag' + +export const updateHomeCommunity = gql` + mutation ($uuid: String!, $gmsApiKey: String!) { + updateHomeCommunity(uuid: $uuid, gmsApiKey: $gmsApiKey) { + id + } + } +` diff --git a/admin/src/locales/de.json b/admin/src/locales/de.json index 7cc0affac..9f808f70c 100644 --- a/admin/src/locales/de.json +++ b/admin/src/locales/de.json @@ -73,11 +73,20 @@ "error": "Fehler", "expired": "abgelaufen", "federation": { + "apiVersion": "API Version", + "authenticatedAt": "Verifiziert am:", + "communityUuid": "Community UUID:", "createdAt": "Erstellt am", + "gmsApiKey": "GMS API Key:", + "toast_gmsApiKeyUpdated": "Der GMS Api Key wurde erfolgreich aktualisiert!", "gradidoInstances": "Gradido Instanzen", "lastAnnouncedAt": "letzte Bekanntgabe", + "lastErrorAt": "Letzer Fehler am", + "name": "Name", + "publicKey": "PublicKey:", "url": "Url", - "verified": "Verifiziert" + "verified": "Verifiziert", + "verifiedAt": "Verifiziert am" }, "firstname": "Vorname", "footer": { diff --git a/admin/src/locales/en.json b/admin/src/locales/en.json index 084e2104f..b1f020921 100644 --- a/admin/src/locales/en.json +++ b/admin/src/locales/en.json @@ -73,11 +73,20 @@ "error": "Error", "expired": "expired", "federation": { + "apiVersion": "API Version", + "authenticatedAt": "verified at:", + "communityUuid": "Community UUID:", "createdAt": "Created At ", + "gmsApiKey": "GMS API Key:", + "toast_gmsApiKeyUpdated": "The GMS Api Key has been successfully updated!", "gradidoInstances": "Gradido Instances", "lastAnnouncedAt": "Last Announced", + "lastErrorAt": "last error at", + "name": "Name", + "publicKey": "PublicKey:", "url": "Url", - "verified": "Verified" + "verified": "Verified", + "verifiedAt": "Verified at" }, "firstname": "Firstname", "footer": { diff --git a/admin/src/pages/FederationVisualize.spec.js b/admin/src/pages/FederationVisualize.spec.js index 67d6dffde..a089ef8fe 100644 --- a/admin/src/pages/FederationVisualize.spec.js +++ b/admin/src/pages/FederationVisualize.spec.js @@ -2,7 +2,7 @@ import { mount } from '@vue/test-utils' import FederationVisualize from './FederationVisualize' import VueApollo from 'vue-apollo' import { createMockClient } from 'mock-apollo-client' -import { getCommunities } from '@/graphql/getCommunities' +import { allCommunities } from '@/graphql/allCommunities' import { toastErrorSpy } from '../../test/testSetup' const mockClient = createMockClient() @@ -25,42 +25,54 @@ const mocks = { const defaultData = () => { return { - getCommunities: [ + allCommunities: [ { - id: 1776, - foreign: true, - publicKey: 'c7ca9e742421bb167b8666cb78f90b40c665b8f35db8f001988d44dbb3ce8527', - url: 'http://localhost/api/2_0', - lastAnnouncedAt: '2023-04-07T12:27:24.037Z', - verifiedAt: null, - lastErrorAt: null, - createdAt: '2023-04-07T11:45:06.254Z', - updatedAt: null, - __typename: 'Community', - }, - { - id: 1775, - foreign: true, - publicKey: 'c7ca9e742421bb167b8666cb78f90b40c665b8f35db8f001988d44dbb3ce8527', - url: 'http://localhost/api/1_1', - lastAnnouncedAt: '2023-04-07T12:27:24.023Z', - verifiedAt: null, - lastErrorAt: null, - createdAt: '2023-04-07T11:45:06.234Z', - updatedAt: null, - __typename: 'Community', - }, - { - id: 1774, - foreign: true, - publicKey: 'c7ca9e742421bb167b8666cb78f90b40c665b8f35db8f001988d44dbb3ce8527', - url: 'http://localhost/api/1_0', - lastAnnouncedAt: '2023-04-07T12:27:24.009Z', - verifiedAt: null, - lastErrorAt: null, - createdAt: '2023-04-07T11:45:06.218Z', - updatedAt: null, - __typename: 'Community', + id: 1, + foreign: false, + url: 'http://localhost/api/', + publicKey: '4007170edd8d33fb009cd99ee4e87f214e7cd21b668d45540a064deb42e243c2', + communityUuid: '5ab0befd-b150-4f31-a631-7f3637e47b21', + authenticatedAt: null, + name: 'Gradido Test', + description: 'Gradido Community zum testen', + gmsApiKey: '', + creationDate: '2024-01-09T15:56:40.592Z', + createdAt: '2024-01-09T15:56:40.595Z', + updatedAt: '2024-01-16T11:17:15.000Z', + federatedCommunities: [ + { + id: 2046, + apiVersion: '2_0', + endPoint: 'http://localhost/api/', + lastAnnouncedAt: null, + verifiedAt: null, + lastErrorAt: null, + createdAt: '2024-01-16T10:08:21.544Z', + updatedAt: null, + }, + { + id: 2045, + apiVersion: '1_1', + endPoint: 'http://localhost/api/', + lastAnnouncedAt: null, + verifiedAt: null, + lastErrorAt: null, + createdAt: '2024-01-16T10:08:21.550Z', + updatedAt: null, + __typename: 'FederatedCommunity', + }, + { + id: 2044, + apiVersion: '1_0', + endPoint: 'http://localhost/api/', + lastAnnouncedAt: null, + verifiedAt: null, + lastErrorAt: null, + createdAt: '2024-01-16T10:08:21.544Z', + updatedAt: null, + __typename: 'FederatedCommunity', + }, + ], }, ], } @@ -68,11 +80,11 @@ const defaultData = () => { describe('FederationVisualize', () => { let wrapper - const getCommunitiesMock = jest.fn() + const allCommunitiesMock = jest.fn() mockClient.setRequestHandler( - getCommunities, - getCommunitiesMock + allCommunities, + allCommunitiesMock .mockRejectedValueOnce({ message: 'Ouch!' }) .mockResolvedValue({ data: defaultData() }), ) @@ -95,7 +107,7 @@ describe('FederationVisualize', () => { describe('sever success', () => { it('sends query to Apollo when created', () => { - expect(getCommunitiesMock).toBeCalled() + expect(allCommunitiesMock).toBeCalled() }) it('has a DIV element with the class "federation-visualize"', () => { @@ -106,8 +118,8 @@ describe('FederationVisualize', () => { expect(wrapper.find('[data-test="federation-communities-refresh-btn"]').exists()).toBe(true) }) - it('renders 3 community list items', () => { - expect(wrapper.findAll('.list-group-item').length).toBe(3) + it('renders 1 community list item', () => { + expect(wrapper.findAll('.list-group-item').length).toBe(1) }) describe('cklicking the refresh button', () => { @@ -117,7 +129,7 @@ describe('FederationVisualize', () => { }) it('calls the API', async () => { - expect(getCommunitiesMock).toBeCalled() + expect(allCommunitiesMock).toBeCalled() }) }) }) diff --git a/admin/src/pages/FederationVisualize.vue b/admin/src/pages/FederationVisualize.vue index 383cf064d..5736b1a0e 100644 --- a/admin/src/pages/FederationVisualize.vue +++ b/admin/src/pages/FederationVisualize.vue @@ -7,7 +7,7 @@ icon="arrow-clockwise" font-scale="2" :animation="animation" - @click="$apollo.queries.GetCommunities.refresh()" + @click="$apollo.queries.allCommunities.refresh()" data-test="federation-communities-refresh-btn" > @@ -16,28 +16,29 @@ {{ $t('federation.verified') }} {{ $t('federation.url') }} + {{ $t('federation.name') }} {{ $t('federation.lastAnnouncedAt') }} {{ $t('federation.createdAt') }} - +