Merge branch 'master' into create-message-table

This commit is contained in:
Hannes Heine 2022-08-19 12:17:45 +02:00 committed by GitHub
commit 159fb96502
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 882 additions and 85 deletions

View File

@ -26,5 +26,5 @@ module.exports = {
testMatch: ['**/?(*.)+(spec|test).js?(x)'],
// snapshotSerializers: ['jest-serializer-vue'],
transformIgnorePatterns: ['<rootDir>/node_modules/(?!vee-validate/dist/rules)'],
// testEnvironment: 'jest-environment-jsdom-sixteen', // not needed anymore since jest@26, see: https://www.npmjs.com/package/jest-environment-jsdom-sixteen
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
}

View File

@ -39,6 +39,7 @@
"identity-obj-proxy": "^3.0.0",
"jest": "26.6.3",
"jest-canvas-mock": "^2.3.1",
"jest-environment-jsdom-sixteen": "^2.0.0",
"portal-vue": "^2.1.7",
"qrcanvas-vue": "2.1.1",
"regenerator-runtime": "^0.13.9",

View File

@ -0,0 +1,39 @@
import { mount } from '@vue/test-utils'
import CommunityStatistic from './CommunityStatistic'
const localVue = global.localVue
const mocks = {
$t: jest.fn((t) => t),
$n: jest.fn((n) => n),
}
const propsData = {
value: {
totalUsers: '123',
activeUsers: '100',
deletedUsers: '5',
totalGradidoCreated: '2500',
totalGradidoDecayed: '200',
totalGradidoAvailable: '500',
totalGradidoUnbookedDecayed: '111',
},
}
describe('CommunityStatistic', () => {
let wrapper
const Wrapper = () => {
return mount(CommunityStatistic, { localVue, mocks, propsData })
}
describe('mount', () => {
beforeEach(() => {
wrapper = Wrapper()
})
it('renders the Div Element ".community-statistic"', () => {
expect(wrapper.find('div.community-statistic').exists()).toBe(true)
})
})
})

View File

@ -0,0 +1,59 @@
<template>
<div class="community-statistic">
<div>
<b-jumbotron bg-variant="info" text-variant="white" border-variant="dark">
<template #header>{{ $t('statistic.name') }}</template>
<hr class="my-4" />
<div>
{{ $t('statistic.totalUsers') }}{{ $t('math.colon') }}
<b>{{ value.totalUsers }}</b>
</div>
<div>
{{ $t('statistic.activeUsers') }}{{ $t('math.colon') }}
<b>{{ value.activeUsers }}</b>
</div>
<div>
{{ $t('statistic.deletedUsers') }}{{ $t('math.colon') }}
<b>{{ value.deletedUsers }}</b>
</div>
<div>
{{ $t('statistic.totalGradidoCreated') }}{{ $t('math.colon') }}
<b>{{ $n(value.totalGradidoCreated, 'decimal') }} {{ $t('GDD') }}</b>
<small class="ml-5">{{ value.totalGradidoCreated }}</small>
</div>
<div>
{{ $t('statistic.totalGradidoDecayed') }}{{ $t('math.colon') }}
<b>{{ $n(value.totalGradidoDecayed, 'decimal') }} {{ $t('GDD') }}</b>
<small class="ml-5">{{ value.totalGradidoDecayed }}</small>
</div>
<div>
{{ $t('statistic.totalGradidoAvailable') }}{{ $t('math.colon') }}
<b>{{ $n(value.totalGradidoAvailable, 'decimal') }} {{ $t('GDD') }}</b>
<small class="ml-5">{{ value.totalGradidoAvailable }}</small>
</div>
<div>
{{ $t('statistic.totalGradidoUnbookedDecayed') }}{{ $t('math.colon') }}
<b>{{ $n(value.totalGradidoUnbookedDecayed, 'decimal') }} {{ $t('GDD') }}</b>
<small class="ml-5">{{ value.totalGradidoUnbookedDecayed }}</small>
</div>
</b-jumbotron>
</div>
</div>
</template>
<script>
import CONFIG from '@/config'
export default {
name: 'CommunityStatistic',
props: {
value: { type: Object },
},
data() {
return {
CONFIG,
}
},
}
</script>

View File

@ -0,0 +1,29 @@
import { mount } from '@vue/test-utils'
import ContentFooter from './ContentFooter'
const localVue = global.localVue
const mocks = {
$t: jest.fn((t) => t),
$i18n: {
locale: jest.fn(() => 'en'),
},
}
describe('ContentFooter', () => {
let wrapper
const Wrapper = () => {
return mount(ContentFooter, { localVue, mocks })
}
describe('mount', () => {
beforeEach(() => {
wrapper = Wrapper()
})
it('renders the div element ".content-footer"', () => {
expect(wrapper.find('div.content-footer').exists()).toBe(true)
})
})
})

View File

@ -1,5 +1,5 @@
<template>
<div>
<div class="content-footer">
<hr />
<b-row align-v="center" class="mt-4 justify-content-lg-between">
<b-col>

View File

@ -1,5 +1,6 @@
import { mount } from '@vue/test-utils'
import ContributionLinkForm from './ContributionLinkForm.vue'
import { toastErrorSpy } from '../../test/testSetup'
const localVue = global.localVue
@ -8,9 +9,13 @@ global.alert = jest.fn()
const propsData = {
contributionLinkData: {},
}
const apolloMutateMock = jest.fn().mockResolvedValue()
const mocks = {
$t: jest.fn((t) => t),
$apollo: {
mutate: apolloMutateMock,
},
}
// const mockAPIcall = jest.fn()
@ -99,4 +104,16 @@ describe('ContributionLinkForm', () => {
// })
// })
})
describe('send createContributionLink with error', () => {
beforeEach(() => {
apolloMutateMock.mockRejectedValue({ message: 'OUCH!' })
wrapper = Wrapper()
wrapper.vm.onSubmit()
})
it('toasts an error message', () => {
expect(toastErrorSpy).toBeCalledWith('contributionLink.noStartDate')
})
})
})

View File

@ -73,6 +73,7 @@
"hide_details": "Details verbergen",
"lastname": "Nachname",
"math": {
"colon": ":",
"exclaim": "!",
"pipe": "|",
"plus": "+"
@ -105,6 +106,16 @@
"removeNotSelf": "Als Admin/Moderator kannst du dich nicht selber löschen.",
"remove_all": "alle Nutzer entfernen",
"save": "Speichern",
"statistic": {
"activeUsers": "Aktive Mitglieder",
"deletedUsers": "Gelöschte Mitglieder",
"name": "Statistik",
"totalGradidoAvailable": "GDD insgesamt im Umlauf",
"totalGradidoCreated": "GDD insgesamt geschöpft",
"totalGradidoDecayed": "GDD insgesamt verfallen",
"totalGradidoUnbookedDecayed": "Ungebuchter GDD Verfall",
"totalUsers": "Mitglieder"
},
"status": "Status",
"success": "Erfolg",
"text": "Text",

View File

@ -73,6 +73,7 @@
"hide_details": "Hide details",
"lastname": "Lastname",
"math": {
"colon": ":",
"exclaim": "!",
"pipe": "|",
"plus": "+"
@ -105,6 +106,16 @@
"removeNotSelf": "As an admin/moderator, you cannot delete yourself.",
"remove_all": "Remove all users",
"save": "Speichern",
"statistic": {
"activeUsers": "Active members",
"deletedUsers": "Deleted members",
"name": "Statistic",
"totalGradidoAvailable": "Total GDD in circulation",
"totalGradidoCreated": "Total created GDD",
"totalGradidoDecayed": "Total GDD decay",
"totalGradidoUnbookedDecayed": "Unbooked GDD decay",
"totalUsers": "Members"
},
"status": "Status",
"success": "Success",
"text": "Text",

View File

@ -1,28 +1,83 @@
import { mount } from '@vue/test-utils'
import Overview from './Overview.vue'
import { listContributionLinks } from '@/graphql/listContributionLinks.js'
import { communityStatistics } from '@/graphql/communityStatistics.js'
import { listUnconfirmedContributions } from '@/graphql/listUnconfirmedContributions.js'
const localVue = global.localVue
const apolloQueryMock = jest.fn().mockResolvedValue({
data: {
listUnconfirmedContributions: [
{
pending: true,
const apolloQueryMock = jest
.fn()
.mockResolvedValueOnce({
data: {
listUnconfirmedContributions: [
{
pending: true,
},
{
pending: true,
},
{
pending: true,
},
],
},
})
.mockResolvedValueOnce({
data: {
communityStatistics: {
totalUsers: 3113,
activeUsers: 1057,
deletedUsers: 35,
totalGradidoCreated: '4083774.05000000000000000000',
totalGradidoDecayed: '-1062639.13634129622923372197',
totalGradidoAvailable: '2513565.869444365732411569',
totalGradidoUnbookedDecayed: '-500474.6738366222166261272',
},
{
pending: true,
},
})
.mockResolvedValueOnce({
data: {
listContributionLinks: {
links: [
{
id: 1,
name: 'Meditation',
memo: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut l',
amount: '200',
validFrom: '2022-04-01',
validTo: '2022-08-01',
cycle: 'täglich',
maxPerCycle: '3',
maxAmountPerMonth: 0,
link: 'https://localhost/redeem/CL-1a2345678',
},
],
count: 1,
},
{
pending: true,
},
],
},
})
},
})
.mockResolvedValue({
data: {
listUnconfirmedContributions: [
{
pending: true,
},
{
pending: true,
},
{
pending: true,
},
],
},
})
const storeCommitMock = jest.fn()
const mocks = {
$t: jest.fn((t) => t),
$n: jest.fn((n) => n),
$apollo: {
query: apolloQueryMock,
},
@ -47,10 +102,30 @@ describe('Overview', () => {
})
it('calls listUnconfirmedContributions', () => {
expect(apolloQueryMock).toBeCalled()
expect(apolloQueryMock).toBeCalledWith(
expect.objectContaining({
query: listUnconfirmedContributions,
}),
)
})
it('commts three pending creations to store', () => {
it('calls communityStatistics', () => {
expect(apolloQueryMock).toBeCalledWith(
expect.objectContaining({
query: communityStatistics,
}),
)
})
it('calls listContributionLinks', () => {
expect(apolloQueryMock).toBeCalledWith(
expect.objectContaining({
query: listContributionLinks,
}),
)
})
it('commits three pending creations to store', () => {
expect(storeCommitMock).toBeCalledWith('setOpenCreations', 3)
})

View File

@ -29,26 +29,39 @@
</b-card-text>
</b-card>
<contribution-link :items="items" :count="count" />
<community-statistic class="mt-5" v-model="statistics" />
</div>
</template>
<script>
import { listContributionLinks } from '@/graphql/listContributionLinks.js'
import { communityStatistics } from '@/graphql/communityStatistics.js'
import ContributionLink from '../components/ContributionLink.vue'
import { listUnconfirmedContributions } from '../graphql/listUnconfirmedContributions'
import CommunityStatistic from '../components/CommunityStatistic.vue'
import { listUnconfirmedContributions } from '@/graphql/listUnconfirmedContributions.js'
export default {
name: 'overview',
components: {
ContributionLink,
CommunityStatistic,
},
data() {
return {
items: [],
count: 0,
statistics: {
totalUsers: null,
activeUsers: null,
deletedUsers: null,
totalGradidoCreated: null,
totalGradidoDecayed: null,
totalGradidoAvailable: null,
totalGradidoUnbookedDecayed: null,
},
}
},
methods: {
async getPendingCreations() {
getPendingCreations() {
this.$apollo
.query({
query: listUnconfirmedContributions,
@ -58,7 +71,7 @@ export default {
this.$store.commit('setOpenCreations', result.data.listUnconfirmedContributions.length)
})
},
async getContributionLinks() {
getContributionLinks() {
this.$apollo
.query({
query: listContributionLinks,
@ -72,9 +85,31 @@ export default {
this.toastError('listContributionLinks has no result, use default data')
})
},
getCommunityStatistics() {
this.$apollo
.query({
query: communityStatistics,
fetchPolicy: 'network-only',
})
.then((result) => {
this.statistics.totalUsers = result.data.communityStatistics.totalUsers
this.statistics.activeUsers = result.data.communityStatistics.activeUsers
this.statistics.deletedUsers = result.data.communityStatistics.deletedUsers
this.statistics.totalGradidoCreated = result.data.communityStatistics.totalGradidoCreated
this.statistics.totalGradidoDecayed = result.data.communityStatistics.totalGradidoDecayed
this.statistics.totalGradidoAvailable =
result.data.communityStatistics.totalGradidoAvailable
this.statistics.totalGradidoUnbookedDecayed =
result.data.communityStatistics.totalGradidoUnbookedDecayed
})
.catch(() => {
this.toastError('communityStatistics has no result, use default data')
})
},
},
created() {
this.getPendingCreations()
this.getCommunityStatistics()
this.getContributionLinks()
},
}

View File

@ -1282,6 +1282,17 @@
jest-message-util "^24.9.0"
jest-mock "^24.9.0"
"@jest/fake-timers@^25.1.0":
version "25.5.0"
resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-25.5.0.tgz#46352e00533c024c90c2bc2ad9f2959f7f114185"
integrity sha512-9y2+uGnESw/oyOI3eww9yaxdZyHq7XvprfP/eeoCsjqKYts2yRlsHS/SgjPDV8FyMfn2nbMy8YzUk6nyvdLOpQ==
dependencies:
"@jest/types" "^25.5.0"
jest-message-util "^25.5.0"
jest-mock "^25.5.0"
jest-util "^25.5.0"
lolex "^5.0.0"
"@jest/fake-timers@^26.6.2":
version "26.6.2"
resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad"
@ -1493,6 +1504,16 @@
"@types/istanbul-reports" "^1.1.1"
"@types/yargs" "^13.0.0"
"@jest/types@^25.5.0":
version "25.5.0"
resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d"
integrity sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==
dependencies:
"@types/istanbul-lib-coverage" "^2.0.0"
"@types/istanbul-reports" "^1.1.1"
"@types/yargs" "^15.0.0"
chalk "^3.0.0"
"@jest/types@^26.6.2":
version "26.6.2"
resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e"
@ -4109,6 +4130,14 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chalk@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chalk@^4.0.0, chalk@^4.1.0:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
@ -7983,6 +8012,16 @@ jest-environment-jsdom-fifteen@^1.0.2:
jest-util "^24.0.0"
jsdom "^15.2.1"
jest-environment-jsdom-sixteen@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/jest-environment-jsdom-sixteen/-/jest-environment-jsdom-sixteen-2.0.0.tgz#0f8c12663ccd9836d248574decffc575bfb091e1"
integrity sha512-BF+8P67aEJcd78TQzwSb9P4a73cArOWb5KgqI8eU6cHRWDIJdDRE8XTeZAmOuDSDhKpuEXjKkXwWB3GOJvqHJQ==
dependencies:
"@jest/fake-timers" "^25.1.0"
jest-mock "^25.1.0"
jest-util "^25.1.0"
jsdom "^16.2.1"
jest-environment-jsdom@^24.9.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz#4b0806c7fc94f95edb369a69cc2778eec2b7375b"
@ -8197,6 +8236,20 @@ jest-message-util@^24.9.0:
slash "^2.0.0"
stack-utils "^1.0.1"
jest-message-util@^25.5.0:
version "25.5.0"
resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-25.5.0.tgz#ea11d93204cc7ae97456e1d8716251185b8880ea"
integrity sha512-ezddz3YCT/LT0SKAmylVyWWIGYoKHOFOFXx3/nA4m794lfVUskMcwhip6vTgdVrOtYdjeQeis2ypzes9mZb4EA==
dependencies:
"@babel/code-frame" "^7.0.0"
"@jest/types" "^25.5.0"
"@types/stack-utils" "^1.0.1"
chalk "^3.0.0"
graceful-fs "^4.2.4"
micromatch "^4.0.2"
slash "^3.0.0"
stack-utils "^1.0.1"
jest-message-util@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07"
@ -8219,6 +8272,13 @@ jest-mock@^24.0.0, jest-mock@^24.9.0:
dependencies:
"@jest/types" "^24.9.0"
jest-mock@^25.1.0, jest-mock@^25.5.0:
version "25.5.0"
resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-25.5.0.tgz#a91a54dabd14e37ecd61665d6b6e06360a55387a"
integrity sha512-eXWuTV8mKzp/ovHc5+3USJMYsTBhyQ+5A1Mak35dey/RG8GlM4YWVylZuGgVXinaW6tpvk/RSecmF37FKUlpXA==
dependencies:
"@jest/types" "^25.5.0"
jest-mock@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302"
@ -8495,6 +8555,17 @@ jest-util@^24.0.0, jest-util@^24.9.0:
slash "^2.0.0"
source-map "^0.6.0"
jest-util@^25.1.0, jest-util@^25.5.0:
version "25.5.0"
resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-25.5.0.tgz#31c63b5d6e901274d264a4fec849230aa3fa35b0"
integrity sha512-KVlX+WWg1zUTB9ktvhsg2PXZVdkI1NBevOJSkTKYAyXyH4QSvh+Lay/e/v+bmaFfrkfx43xD8QTfgobzlEXdIA==
dependencies:
"@jest/types" "^25.5.0"
chalk "^3.0.0"
graceful-fs "^4.2.4"
is-ci "^2.0.0"
make-dir "^3.0.0"
jest-util@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1"
@ -8741,7 +8812,7 @@ jsdom@^15.2.1:
ws "^7.0.0"
xml-name-validator "^3.0.0"
jsdom@^16.4.0:
jsdom@^16.2.1, jsdom@^16.4.0:
version "16.7.0"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710"
integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==
@ -9096,6 +9167,13 @@ loglevel@^1.6.8:
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197"
integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==
lolex@^5.0.0:
version "5.1.2"
resolved "https://registry.yarnpkg.com/lolex/-/lolex-5.1.2.tgz#953694d098ce7c07bc5ed6d0e42bc6c0c6d5a367"
integrity sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==
dependencies:
"@sinonjs/commons" "^1.7.0"
loose-envify@^1.0.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"

View File

@ -47,7 +47,7 @@ describe('GddSend confirm', () => {
})
})
describe('has total balance equal 0', () => {
describe('has totalBalance under 0', () => {
beforeEach(async () => {
await wrapper.setProps({
balance: 0,

View File

@ -48,53 +48,57 @@ describe('Navbar', () => {
expect(wrapper.find('.navbar-toggler').exists()).toBeTruthy()
})
it('has twelve b-nav-item in the navbar', () => {
expect(wrapper.findAll('.nav-item')).toHaveLength(12)
it('has thirteen b-nav-item in the navbar', () => {
expect(wrapper.findAll('.nav-item')).toHaveLength(13)
})
it('has first nav-item "amount GDD" in navbar', () => {
it('has nav-item "amount GDD" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(1).text()).toEqual('1234 GDD')
})
it('has first nav-item "navigation.overview" in navbar', () => {
it('has nav-item "navigation.overview" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(3).text()).toEqual('navigation.overview')
})
it('has first nav-item "navigation.send" in navbar', () => {
it('has nav-item "navigation.send" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(4).text()).toEqual('navigation.send')
})
it('has first nav-item "navigation.transactions" in navbar', () => {
it('has nav-item "navigation.transactions" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(5).text()).toEqual('navigation.transactions')
})
it('has first nav-item "gdt.gdt" in navbar', () => {
it('has nav-item "gdt.gdt" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(6).text()).toEqual('gdt.gdt')
})
it('has first nav-item "navigation.community" in navbar', () => {
it('has nav-item "navigation.community" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(7).text()).toEqual('navigation.community')
})
it('has first nav-item "navigation.profile" in navbar', () => {
it('has nav-item "navigation.profile" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(8).text()).toEqual('navigation.profile')
})
it('has nav-item "navigation.info" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(9).text()).toEqual('navigation.info')
})
})
describe('navigation Navbar (user has an elopage account)', () => {
it('has a link to the members area', () => {
expect(wrapper.findAll('.nav-item').at(9).text()).toContain('navigation.members_area')
expect(wrapper.findAll('.nav-item').at(9).find('a').attributes('href')).toBe(
expect(wrapper.findAll('.nav-item').at(10).text()).toContain('navigation.members_area')
expect(wrapper.findAll('.nav-item').at(10).find('a').attributes('href')).toBe(
'https://elopage.com',
)
})
it('has first nav-item "navigation.admin_area" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(10).text()).toEqual('navigation.admin_area')
it('has nav-item "navigation.admin_area" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(11).text()).toEqual('navigation.admin_area')
})
it('has first nav-item "navigation.logout" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(11).text()).toEqual('navigation.logout')
it('has nav-item "navigation.logout" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(12).text()).toEqual('navigation.logout')
})
})
@ -104,12 +108,12 @@ describe('Navbar', () => {
wrapper = Wrapper()
})
it('has first nav-item "navigation.admin_area" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(9).text()).toEqual('navigation.admin_area')
it('has nav-item "navigation.admin_area" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(10).text()).toEqual('navigation.admin_area')
})
it('has first nav-item "navigation.logout" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(10).text()).toEqual('navigation.logout')
it('has nav-item "navigation.logout" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(11).text()).toEqual('navigation.logout')
})
})
})

View File

@ -64,6 +64,10 @@
<b-icon icon="gear" aria-hidden="true"></b-icon>
{{ $t('navigation.profile') }}
</b-nav-item>
<b-nav-item to="/information" class="mb-3">
<b-icon icon="info-circle" aria-hidden="true"></b-icon>
{{ $t('navigation.info') }}
</b-nav-item>
<br />
<b-nav-item v-if="$store.state.hasElopage" :href="elopageUri" class="mb-3" target="_blank">
<b-icon icon="link45deg" aria-hidden="true"></b-icon>

View File

@ -32,48 +32,62 @@ describe('Sidebar', () => {
expect(wrapper.find('div#component-sidebar').exists()).toBeTruthy()
})
describe('navigation Navbar (general elements)', () => {
it('has first nav-item "navigation.overview" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(0).text()).toEqual('navigation.overview')
describe('navigation Navbar', () => {
it('has ten b-nav-item in the navbar', () => {
expect(wrapper.findAll('.nav-item')).toHaveLength(10)
})
it('has first nav-item "navigation.send" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(1).text()).toEqual('navigation.send')
describe('navigation Navbar (general elements)', () => {
it('has nav-item "navigation.overview" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(0).text()).toEqual('navigation.overview')
})
it('has nav-item "navigation.send" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(1).text()).toEqual('navigation.send')
})
it('has nav-item "gdt.gdt" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(3).text()).toEqual('gdt.gdt')
})
it('has nav-item "navigation.community" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(4).text()).toContain('navigation.community')
})
it('has nav-item "navigation.profile" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(5).text()).toEqual('navigation.profile')
})
it('has nav-item "navigation.info" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(6).text()).toEqual('navigation.info')
})
})
it('has first nav-item "navigation.transactions" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(2).text()).toEqual('navigation.transactions')
describe('navigation Navbar (user has an elopage account)', () => {
it('has ten b-nav-item in the navbar', () => {
expect(wrapper.findAll('.nav-item')).toHaveLength(10)
})
it('has a link to the members area', () => {
expect(wrapper.findAll('.nav-item').at(7).text()).toEqual('navigation.members_area')
expect(wrapper.findAll('.nav-item').at(7).find('a').attributes('href')).toBe('#')
})
it('has nav-item "navigation.admin_area" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(8).text()).toEqual('navigation.admin_area')
})
it('has nav-item "navigation.logout" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(9).text()).toEqual('navigation.logout')
})
})
it('has first nav-item "gdt.gdt" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(3).text()).toEqual('gdt.gdt')
it('has nav-item "navigation.admin_area" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(8).text()).toEqual('navigation.admin_area')
})
it('has first nav-item "navigation.community" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(4).text()).toContain('navigation.community')
})
it('has first nav-item "navigation.profile" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(5).text()).toEqual('navigation.profile')
})
})
describe('navigation Navbar (user has an elopage account)', () => {
it('has eight b-nav-item in the navbar', () => {
expect(wrapper.findAll('.nav-item')).toHaveLength(9)
})
it('has a link to the members area', () => {
expect(wrapper.findAll('.nav-item').at(6).text()).toEqual('navigation.members_area')
expect(wrapper.findAll('.nav-item').at(6).find('a').attributes('href')).toBe('#')
})
it('has first nav-item "navigation.admin_area" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(7).text()).toEqual('navigation.admin_area')
})
it('has first nav-item "navigation.logout" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(8).text()).toEqual('navigation.logout')
it('has nav-item "navigation.logout" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(9).text()).toEqual('navigation.logout')
})
})
@ -83,16 +97,16 @@ describe('Sidebar', () => {
wrapper = Wrapper()
})
it('has seven b-nav-item in the navbar', () => {
expect(wrapper.findAll('.nav-item')).toHaveLength(8)
it('has nine b-nav-item in the navbar', () => {
expect(wrapper.findAll('.nav-item')).toHaveLength(9)
})
it('has first nav-item "navigation.admin_area" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(6).text()).toEqual('navigation.admin_area')
it('has nav-item "navigation.admin_area" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(7).text()).toEqual('navigation.admin_area')
})
it('has first nav-item "navigation.logout" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(7).text()).toEqual('navigation.logout')
it('has nav-item "navigation.logout" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(8).text()).toEqual('navigation.logout')
})
})
})

View File

@ -28,6 +28,10 @@
<b-icon icon="gear" aria-hidden="true"></b-icon>
{{ $t('navigation.profile') }}
</b-nav-item>
<b-nav-item to="/information" class="mb-3">
<b-icon icon="info-circle" aria-hidden="true"></b-icon>
{{ $t('navigation.info') }}
</b-nav-item>
</b-nav>
<hr />
<b-nav vertical class="w-100">

View File

@ -163,6 +163,26 @@ export const listTransactionLinks = gql`
}
`
export const listContributionLinks = gql`
query($currentPage: Int = 1, $pageSize: Int = 25, $order: Order = DESC) {
listContributionLinks(currentPage: $currentPage, pageSize: $pageSize, order: $order) {
links {
id
amount
name
memo
createdAt
validFrom
validTo
maxAmountPerMonth
cycle
maxPerCycle
}
count
}
}
`
export const listContributions = gql`
query(
$currentPage: Int = 1
@ -209,3 +229,29 @@ export const listAllContributions = gql`
}
}
`
export const communityStatistics = gql`
query {
communityStatistics {
totalUsers
activeUsers
deletedUsers
totalGradidoCreated
totalGradidoDecayed
totalGradidoAvailable
totalGradidoUnbookedDecayed
}
}
`
export const searchAdminUsers = gql`
query {
searchAdminUsers {
userCount
userList {
firstName
lastName
}
}
}
`

View File

@ -26,8 +26,13 @@
"community": "Gemeinschaft",
"continue-to-registration": "Weiter zur Registrierung",
"current-community": "Aktuelle Gemeinschaft",
"members": "Mitglieder",
"moderators": "Moderatoren",
"myContributions": "Meine Beiträge zum Gemeinwohl",
"openContributionLinks": "öffentliche Beitrags-Linkliste",
"openContributionLinkText": "Folgende {count} automatische Schöpfungen werden zur Zeit durch die Gemeinschaft „{name}“ bereitgestellt.",
"other-communities": "Weitere Gemeinschaften",
"statistic": "Statistik",
"submitContribution": "Beitrag einreichen",
"switch-to-this-community": "zu dieser Gemeinschaft wechseln"
},
@ -59,7 +64,8 @@
"yourActivity": "Bitte trage eine Tätigkeit ein!"
},
"contribution-link": {
"thanksYouWith": "dankt dir mit"
"thanksYouWith": "dankt dir mit",
"unique": "(einmalig)"
},
"decay": {
"before_startblock_transaction": "Diese Transaktion beinhaltet keine Vergänglichkeit.",
@ -224,6 +230,7 @@
"navigation": {
"admin_area": "Adminbereich",
"community": "Gemeinschaft",
"info": "Information",
"logout": "Abmelden",
"members_area": "Mitgliederbereich",
"overview": "Übersicht",
@ -298,6 +305,14 @@
"uppercase": "Großbuchstabe erforderlich."
}
},
"statistic": {
"activeUsers": "Aktive Mitglieder",
"deletedUsers": "Gelöschte Mitglieder",
"totalGradidoAvailable": "GDD insgesamt im Umlauf",
"totalGradidoCreated": "GDD insgesamt geschöpft",
"totalGradidoDecayed": "GDD insgesamt verfallen",
"totalGradidoUnbookedDecayed": "Gesamter ungebuchter GDD Verfall"
},
"success": "Erfolg",
"time": {
"days": "Tage",

View File

@ -26,8 +26,13 @@
"community": "Community",
"continue-to-registration": "Continue to registration",
"current-community": "Current community",
"members": "Members",
"moderators": "Moderators",
"myContributions": "My contributions to the common good",
"openContributionLinks": "open Contribution links list",
"openContributionLinkText": "The following {count} automatic creations are currently provided by the \"{name}\" community.",
"other-communities": "Other communities",
"statistic": "Statistics",
"submitContribution": "Submit contribution",
"switch-to-this-community": "Switch to this community"
},
@ -59,7 +64,8 @@
"yourActivity": "Please enter an activity!"
},
"contribution-link": {
"thanksYouWith": "thanks you with"
"thanksYouWith": "thanks you with",
"unique": "(unique)"
},
"decay": {
"before_startblock_transaction": "This transaction does not include decay.",
@ -224,6 +230,7 @@
"navigation": {
"admin_area": "Admin Area",
"community": "Community",
"info": "Information",
"logout": "Logout",
"members_area": "Members area",
"overview": "Overview",
@ -298,6 +305,14 @@
"uppercase": "One uppercase letter required."
}
},
"statistic": {
"activeUsers": "Active members",
"deletedUsers": "Deleted members",
"totalGradidoAvailable": "Total GDD in circulation",
"totalGradidoCreated": "Total created GDD",
"totalGradidoDecayed": "Total GDD decay",
"totalGradidoUnbookedDecayed": "Total unbooked GDD decay"
},
"success": "Success",
"time": {
"days": "Days",

View File

@ -26,8 +26,13 @@
"community": "Comunidad",
"continue-to-registration": "Continuar con el registro",
"current-community": "Comunidad actual",
"members": "Miembros",
"moderators": "Moderadores",
"myContributions": "Mis contribuciones al bien común",
"openContributionLinks": "lista de enlaces de contribuciones públicas",
"openContributionLinkText": "La comunidad \"{name}\" proporciona actualmente las siguientes {count} creaciones automáticas.",
"other-communities": "Otras comunidades",
"statistic": "Estadísticas",
"submitContribution": "Aportar una contribución",
"switch-to-this-community": "cambiar a esta comunidad"
},
@ -59,7 +64,8 @@
"yourActivity": "¡Por favor, introduce una actividad!"
},
"contribution-link": {
"thanksYouWith": "agradecidos con"
"thanksYouWith": "te agradece con",
"unique": "(único)"
},
"decay": {
"before_startblock_transaction": "Esta transacción no implica disminución en su valor.",
@ -224,6 +230,7 @@
"navigation": {
"admin_area": "Área de administración",
"community": "Comunidad",
"info": "Información",
"logout": "Salir",
"members_area": "Área de afiliados",
"overview": "Resumen",
@ -298,6 +305,14 @@
"uppercase": "Letra mayúscula requerida."
}
},
"statistic": {
"activeUsers": "miembros activos",
"deletedUsers": "miembros eliminados",
"totalGradidoAvailable": "GDD total en circulación",
"totalGradidoCreated": "GDD total creado",
"totalGradidoDecayed": "GDD total decaído",
"totalGradidoUnbookedDecayed": "GDD no contabilizado decaído"
},
"success": "Lo lograste",
"time": {
"days": "Días",

View File

@ -0,0 +1,130 @@
import { mount } from '@vue/test-utils'
import InfoStatistic from './InfoStatistic'
import { toastErrorSpy } from '../../test/testSetup'
import { listContributionLinks, communityStatistics, searchAdminUsers } from '@/graphql/queries'
const localVue = global.localVue
const apolloQueryMock = jest
.fn()
.mockResolvedValueOnce({
data: {
listContributionLinks: {
count: 2,
links: [
{
id: 1,
amount: 200,
name: 'Dokumenta 2017',
memo: 'Vielen Dank für deinen Besuch bei der Dokumenta 2017',
cycle: 'ONCE',
},
{
id: 2,
amount: 200,
name: 'Dokumenta 2022',
memo: 'Vielen Dank für deinen Besuch bei der Dokumenta 2022',
cycle: 'ONCE',
},
],
},
},
})
.mockResolvedValueOnce({
data: {
searchAdminUsers: {
userCount: 2,
userList: [
{ firstName: 'Peter', lastName: 'Lustig' },
{ firstName: 'Super', lastName: 'Admin' },
],
},
},
})
.mockResolvedValueOnce({
data: {
communityStatistics: {
totalUsers: 3113,
activeUsers: 1057,
deletedUsers: 35,
totalGradidoCreated: '4083774.05000000000000000000',
totalGradidoDecayed: '-1062639.13634129622923372197',
totalGradidoAvailable: '2513565.869444365732411569',
totalGradidoUnbookedDecayed: '-500474.6738366222166261272',
},
},
})
.mockResolvedValue('default')
describe('InfoStatistic', () => {
let wrapper
const mocks = {
$i18n: {
locale: 'en',
},
$t: jest.fn((t) => t),
$apollo: {
query: apolloQueryMock,
},
}
const Wrapper = () => {
return mount(InfoStatistic, { localVue, mocks })
}
describe('mount', () => {
beforeEach(() => {
wrapper = Wrapper()
})
it('renders the info page', () => {
expect(wrapper.find('div.info-statistic').exists()).toBe(true)
})
it('calls listContributionLinks', () => {
expect(apolloQueryMock).toBeCalledWith(
expect.objectContaining({
query: listContributionLinks,
fetchPolicy: 'network-only',
}),
)
})
it('calls searchAdminUsers', () => {
expect(apolloQueryMock).toBeCalledWith(
expect.objectContaining({
query: searchAdminUsers,
fetchPolicy: 'network-only',
}),
)
})
it('calls getCommunityStatistics', () => {
expect(apolloQueryMock).toBeCalledWith(
expect.objectContaining({
query: communityStatistics,
fetchPolicy: 'network-only',
}),
)
})
describe('error apolloQueryMock', () => {
beforeEach(async () => {
jest.clearAllMocks()
apolloQueryMock.mockRejectedValue({
message: 'uups',
})
wrapper = Wrapper()
})
it('toasts three error messages', () => {
expect(toastErrorSpy).toBeCalledWith(
'listContributionLinks has no result, use default data',
)
expect(toastErrorSpy).toBeCalledWith('searchAdminUsers has no result, use default data')
expect(toastErrorSpy).toBeCalledWith('communityStatistics has no result, use default data')
})
})
})
})

View File

@ -0,0 +1,166 @@
<template>
<div class="info-statistic">
<b-container>
<div class="h3">{{ $t('community.community') }}</div>
<div class="h1">{{ CONFIG.COMMUNITY_NAME }}</div>
<div>
{{ CONFIG.COMMUNITY_DESCRIPTION }}
</div>
<div>
{{ CONFIG.COMMUNITY_URL }}
</div>
</b-container>
<hr />
<b-container>
<div class="h3">{{ $t('community.openContributionLinks') }}</div>
<small>
{{
$t('community.openContributionLinkText', {
name: CONFIG.COMMUNITY_NAME,
count,
})
}}
</small>
<ul>
<li v-for="item in itemsContributionLinks" v-bind:key="item.id">
<div>{{ item.name }}</div>
<div>{{ item.memo }}</div>
<div>
{{ item.amount | GDD }}
<span v-if="item.cycle === 'ONCE'">{{ $t('contribution-link.unique') }}</span>
</div>
</li>
</ul>
</b-container>
<hr />
<b-container>
<div class="h3">{{ $t('community.moderators') }}</div>
<ul>
<li v-for="item in itemsAdminUser" v-bind:key="item.id">
{{ item.firstName }} {{ item.lastName }}
</li>
</ul>
<b-link href="mailto: abc@example.com">{{ supportMail }}</b-link>
</b-container>
<hr />
<b-container>
<div class="h3">{{ $t('community.statistic') }}</div>
<div>
<div>
{{ $t('community.members') }}
<span class="h4">{{ totalUsers }}</span>
</div>
<div>
{{ $t('statistic.activeUsers') }}
<span class="h4">{{ activeUsers }}</span>
</div>
<div>
{{ $t('statistic.deletedUsers') }}
<span class="h4">{{ deletedUsers }}</span>
</div>
<div>
{{ $t('statistic.totalGradidoCreated') }}
<span class="h4">{{ totalGradidoCreated | GDD }}</span>
</div>
<div>
{{ $t('statistic.totalGradidoDecayed') }}
<span class="h4">{{ totalGradidoDecayed | GDD }}</span>
</div>
<div>
{{ $t('statistic.totalGradidoAvailable') }}
<span class="h4">{{ totalGradidoAvailable | GDD }}</span>
</div>
<div>
{{ $t('statistic.totalGradidoUnbookedDecayed') }}
<span class="h4">{{ totalGradidoUnbookedDecayed | GDD }}</span>
</div>
</div>
</b-container>
</div>
</template>
<script>
import CONFIG from '@/config'
import { listContributionLinks, communityStatistics, searchAdminUsers } from '@/graphql/queries'
export default {
name: 'InfoStatistic',
data() {
return {
CONFIG,
count: null,
countAdminUser: null,
itemsContributionLinks: [],
itemsAdminUser: [],
supportMail: 'support@supportemail.de',
membersCount: '1203',
totalUsers: null,
activeUsers: null,
deletedUsers: null,
totalGradidoCreated: null,
totalGradidoDecayed: null,
totalGradidoAvailable: null,
totalGradidoUnbookedDecayed: null,
}
},
methods: {
getContributionLinks() {
this.$apollo
.query({
query: listContributionLinks,
fetchPolicy: 'network-only',
})
.then((result) => {
this.count = result.data.listContributionLinks.count
this.itemsContributionLinks = result.data.listContributionLinks.links
})
.catch(() => {
this.toastError('listContributionLinks has no result, use default data')
})
},
getAdminUsers() {
this.$apollo
.query({
query: searchAdminUsers,
fetchPolicy: 'network-only',
})
.then((result) => {
this.countAdminUser = result.data.searchAdminUsers.userCount
this.itemsAdminUser = result.data.searchAdminUsers.userList
})
.catch(() => {
this.toastError('searchAdminUsers has no result, use default data')
})
},
getCommunityStatistics() {
this.$apollo
.query({
query: communityStatistics,
fetchPolicy: 'network-only',
})
.then((result) => {
this.totalUsers = result.data.communityStatistics.totalUsers
this.activeUsers = result.data.communityStatistics.activeUsers
this.deletedUsers = result.data.communityStatistics.deletedUsers
this.totalGradidoCreated = result.data.communityStatistics.totalGradidoCreated
this.totalGradidoDecayed = result.data.communityStatistics.totalGradidoDecayed
this.totalGradidoAvailable = result.data.communityStatistics.totalGradidoAvailable
this.totalGradidoUnbookedDecayed =
result.data.communityStatistics.totalGradidoUnbookedDecayed
})
.catch(() => {
this.toastError('communityStatistics has no result, use default data')
})
},
updateTransactions(pagination) {
this.$emit('update-transactions', pagination)
},
},
created() {
this.getContributionLinks()
this.getAdminUsers()
this.getCommunityStatistics()
this.updateTransactions(0)
},
}
</script>

View File

@ -50,7 +50,7 @@ describe('router', () => {
})
it('has sixteen routes defined', () => {
expect(routes).toHaveLength(18)
expect(routes).toHaveLength(19)
})
describe('overview', () => {
@ -108,6 +108,28 @@ describe('router', () => {
})
})
describe('community', () => {
it('requires authorization', () => {
expect(routes.find((r) => r.path === '/community').meta.requiresAuth).toBeTruthy()
})
it('loads the "Community" page', async () => {
const component = await routes.find((r) => r.path === '/community').component()
expect(component.default.name).toBe('Community')
})
})
describe('info', () => {
it('requires authorization', () => {
expect(routes.find((r) => r.path === '/information').meta.requiresAuth).toBeTruthy()
})
it('loads the "InfoStatistic" page', async () => {
const component = await routes.find((r) => r.path === '/information').component()
expect(component.default.name).toBe('InfoStatistic')
})
})
describe('gdt', () => {
it('requires authorization', () => {
expect(routes.find((r) => r.path === '/gdt').meta.requiresAuth).toBeTruthy()

View File

@ -54,6 +54,13 @@ const routes = [
requiresAuth: true,
},
},
{
path: '/information',
component: () => import('@/pages/InfoStatistic.vue'),
meta: {
requiresAuth: true,
},
},
{
path: '/login/:code?',
component: () => import('@/pages/Login.vue'),