diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..5a60c21e2 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,12 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node-terminal", + "name": "Admin: test", + "request": "launch", + "command": "yarn run test", + "cwd": "${workspaceFolder}/admin" + } + ] +} \ No newline at end of file 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 e34136e4b..3c1e666e8 100644 --- a/admin/package.json +++ b/admin/package.json @@ -15,6 +15,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/Federation/CommunityVisualizeItem.spec.js b/admin/src/components/Federation/CommunityVisualizeItem.spec.js index 6058cc6f4..2c5065ba0 100644 --- a/admin/src/components/Federation/CommunityVisualizeItem.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 index c99f38ec5..4a5d9e67e 100644 --- a/admin/src/components/Federation/CommunityVisualizeItem.vue +++ b/admin/src/components/Federation/CommunityVisualizeItem.vue @@ -12,7 +12,7 @@ {{ lastAnnouncedAt }} {{ createdAt }} - + @@ -87,6 +87,9 @@ export default { }, computed: { verified() { + if (!this.item.federatedCommunities || this.item.federatedCommunities.length === 0) { + return false + } return ( this.item.federatedCommunities.filter( (federatedCommunity) => @@ -101,6 +104,7 @@ export default { return this.verified ? 'success' : 'danger' }, lastAnnouncedAt() { + if (!this.item.federatedCommunities || this.item.federatedCommunities.length === 0) return '' const minDate = new Date(0) const lastAnnouncedAt = this.item.federatedCommunities.reduce( (lastAnnouncedAt, federateCommunity) => { diff --git a/admin/src/components/Federation/FederationVisualizeItem.vue b/admin/src/components/Federation/FederationVisualizeItem.vue index cbfe53ef0..9172f9a94 100644 --- a/admin/src/components/Federation/FederationVisualizeItem.vue +++ b/admin/src/components/Federation/FederationVisualizeItem.vue @@ -35,7 +35,11 @@ export default { methods: { distanceDate(dateString) { return dateString - ? formatDistanceToNow(new Date(dateString), { locale: locales[this.$i18n.locale] }) + ? formatDistanceToNow(new Date(dateString), { + includeSecond: true, + addSuffix: true, + locale: locales[this.$i18n.locale], + }) : '' }, }, 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/locales/de.json b/admin/src/locales/de.json index 0fb43e48f..9f808f70c 100644 --- a/admin/src/locales/de.json +++ b/admin/src/locales/de.json @@ -69,7 +69,6 @@ "deleted_user": "Alle gelöschten Nutzer", "deny": "Ablehnen", "e_mail": "E-Mail", - "edit": "Bearbeiten", "enabled": "aktiviert", "error": "Fehler", "expired": "abgelaufen", diff --git a/admin/src/locales/en.json b/admin/src/locales/en.json index b2073a1e2..b1f020921 100644 --- a/admin/src/locales/en.json +++ b/admin/src/locales/en.json @@ -69,7 +69,6 @@ "deleted_user": "All deleted user", "deny": "Reject", "e_mail": "E-mail", - "edit": "Edit", "enabled": "enabled", "error": "Error", "expired": "expired", 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/backend/src/graphql/resolver/util/communities.ts b/backend/src/graphql/resolver/util/communities.ts index b8f23b50e..e85357991 100644 --- a/backend/src/graphql/resolver/util/communities.ts +++ b/backend/src/graphql/resolver/util/communities.ts @@ -3,7 +3,6 @@ import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCom import { Paginated } from '@arg/Paginated' -import { Order } from '@/graphql/enum/Order' import { LogError } from '@/server/LogError' import { Connection } from '@/typeorm/connection'