Merge branch 'master' into refactor_arithmetic_transactionlist_adminarea

This commit is contained in:
Ulf Gebhardt 2022-03-04 14:43:39 +01:00 committed by GitHub
commit 91fda4c097
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 234 additions and 218 deletions

View File

@ -36,6 +36,7 @@
"graphql": "^15.6.1",
"identity-obj-proxy": "^3.0.0",
"jest": "26.6.3",
"portal-vue": "^2.1.7",
"regenerator-runtime": "^0.13.9",
"stats-webpack-plugin": "^0.7.0",
"vue": "^2.6.11",
@ -43,7 +44,6 @@
"vue-i18n": "^8.26.5",
"vue-jest": "^3.0.7",
"vue-router": "^3.5.3",
"vue-toasted": "^1.1.28",
"vuex": "^3.6.2",
"vuex-persistedstate": "^4.1.0"
},

View File

@ -1,21 +1,17 @@
import { mount } from '@vue/test-utils'
import ConfirmRegisterMailFormular from './ConfirmRegisterMailFormular.vue'
import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup'
const localVue = global.localVue
const apolloMutateMock = jest.fn().mockResolvedValue()
const toastSuccessMock = jest.fn()
const toastErrorMock = jest.fn()
const mocks = {
$t: jest.fn((t) => t),
$apollo: {
mutate: apolloMutateMock,
},
$toasted: {
success: toastSuccessMock,
error: toastErrorMock,
},
}
const propsData = {
@ -54,7 +50,7 @@ describe('ConfirmRegisterMailFormular', () => {
})
it('toasts a success message', () => {
expect(toastSuccessMock).toBeCalledWith('unregister_mail.success')
expect(toastSuccessSpy).toBeCalledWith('unregister_mail.success')
})
})
@ -66,7 +62,7 @@ describe('ConfirmRegisterMailFormular', () => {
})
it('toasts an error message', () => {
expect(toastErrorMock).toBeCalledWith('unregister_mail.error')
expect(toastErrorSpy).toBeCalledWith('unregister_mail.error')
})
})
})

View File

@ -48,10 +48,10 @@ export default {
},
})
.then(() => {
this.$toasted.success(this.$t('unregister_mail.success', { email: this.email }))
this.toastSuccess(this.$t('unregister_mail.success', { email: this.email }))
})
.catch((error) => {
this.$toasted.error(this.$t('unregister_mail.error', { message: error.message }))
this.toastError(this.$t('unregister_mail.error', { message: error.message }))
})
},
},

View File

@ -2,6 +2,7 @@ import { mount } from '@vue/test-utils'
import CreationFormular from './CreationFormular.vue'
import { createPendingCreation } from '../graphql/createPendingCreation'
import { createPendingCreations } from '../graphql/createPendingCreations'
import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup'
const localVue = global.localVue
@ -11,8 +12,6 @@ const apolloMutateMock = jest.fn().mockResolvedValue({
},
})
const stateCommitMock = jest.fn()
const toastedErrorMock = jest.fn()
const toastedSuccessMock = jest.fn()
const mocks = {
$t: jest.fn((t, options) => (options ? [t, options] : t)),
@ -32,10 +31,6 @@ const mocks = {
},
},
},
$toasted: {
error: toastedErrorMock,
success: toastedSuccessMock,
},
}
const propsData = {
@ -140,7 +135,7 @@ describe('CreationFormular', () => {
})
it('toasts a success message', () => {
expect(toastedSuccessMock).toBeCalledWith([
expect(toastSuccessSpy).toBeCalledWith([
'creation_form.toasted',
{ email: 'benjamin@bluemchen.de', value: '90' },
])
@ -162,7 +157,7 @@ describe('CreationFormular', () => {
})
it('toasts an error message', () => {
expect(toastedErrorMock).toBeCalledWith('Ouch!')
expect(toastErrorSpy).toBeCalledWith('Ouch!')
})
})
@ -292,7 +287,7 @@ describe('CreationFormular', () => {
})
it('toast success message', () => {
expect(toastedSuccessMock).toBeCalled()
expect(toastSuccessSpy).toBeCalled()
})
it('store commit openCreationPlus', () => {
@ -426,13 +421,14 @@ describe('CreationFormular', () => {
expect(stateCommitMock).toBeCalledWith('openCreationsPlus', 0)
})
it('toasts two errors', () => {
expect(toastedErrorMock).toBeCalledWith(
'Could not created PendingCreation for bob@baumeister.de',
)
expect(toastedErrorMock).toBeCalledWith(
'Could not created PendingCreation for bibi@bloxberg.de',
)
it('emits remove all bookmarks', () => {
expect(wrapper.emitted('remove-all-bookmark')).toBeTruthy()
})
it('emits toast failed creations with two emails', () => {
expect(wrapper.emitted('toast-failed-creations')).toEqual([
[['bob@baumeister.de', 'bibi@bloxberg.de']],
])
})
})
@ -454,7 +450,7 @@ describe('CreationFormular', () => {
})
it('toasts an error message', () => {
expect(toastedErrorMock).toBeCalledWith('Oh no!')
expect(toastErrorSpy).toBeCalledWith('Oh no!')
})
})
})

View File

@ -166,20 +166,21 @@ export default {
fetchPolicy: 'no-cache',
})
.then((result) => {
const failedCreations = []
this.$store.commit(
'openCreationsPlus',
result.data.createPendingCreations.successfulCreation.length,
)
if (result.data.createPendingCreations.failedCreation.length > 0) {
result.data.createPendingCreations.failedCreation.forEach((failed) => {
// TODO: Please localize this error message
this.$toasted.error('Could not created PendingCreation for ' + failed)
result.data.createPendingCreations.failedCreation.forEach((email) => {
failedCreations.push(email)
})
}
this.$emit('remove-all-bookmark')
this.$emit('toast-failed-creations', failedCreations)
})
.catch((error) => {
this.$toasted.error(error.message)
this.toastError(error.message)
})
} else if (this.type === 'singleCreation') {
submitObj = {
@ -196,19 +197,19 @@ export default {
})
.then((result) => {
this.$emit('update-user-data', this.item, result.data.createPendingCreation)
this.$toasted.success(
this.$store.commit('openCreationsPlus', 1)
this.toastSuccess(
this.$t('creation_form.toasted', {
value: this.value,
email: this.item.email,
}),
)
this.$store.commit('openCreationsPlus', 1)
// what is this? Tests says that this.text is not reseted
this.$refs.creationForm.reset()
this.value = 0
})
.catch((error) => {
this.$toasted.error(error.message)
this.toastError(error.message)
this.$refs.creationForm.reset()
this.value = 0
})

View File

@ -1,5 +1,6 @@
import { mount } from '@vue/test-utils'
import CreationTransactionListFormular from './CreationTransactionListFormular.vue'
import { toastErrorSpy } from '../../test/testSetup'
const localVue = global.localVue
@ -50,17 +51,12 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
},
})
const toastedErrorMock = jest.fn()
const mocks = {
$d: jest.fn((t) => t),
$t: jest.fn((t) => t),
$apollo: {
query: apolloQueryMock,
},
$toasted: {
error: toastedErrorMock,
},
}
const propsData = {
@ -109,7 +105,7 @@ describe('CreationTransactionListFormular', () => {
})
it('toast error', () => {
expect(toastedErrorMock).toBeCalledWith('OUCH!')
expect(toastErrorSpy).toBeCalledWith('OUCH!')
})
})
})

View File

@ -63,7 +63,7 @@ export default {
this.items = result.data.transactionList.transactions.filter((t) => t.type === 'creation')
})
.catch((error) => {
this.$toasted.error(error.message)
this.toastError(error.message)
})
},
},

View File

@ -2,6 +2,7 @@ import { mount } from '@vue/test-utils'
import DeletedUserFormular from './DeletedUserFormular.vue'
import { deleteUser } from '../graphql/deleteUser'
import { unDeleteUser } from '../graphql/unDeleteUser'
import { toastErrorSpy } from '../../test/testSetup'
const localVue = global.localVue
@ -13,9 +14,6 @@ const apolloMutateMock = jest.fn().mockResolvedValue({
},
})
const toastedErrorMock = jest.fn()
const toastedSuccessMock = jest.fn()
const mocks = {
$t: jest.fn((t) => t),
$apollo: {
@ -29,10 +27,6 @@ const mocks = {
},
},
},
$toasted: {
error: toastedErrorMock,
success: toastedSuccessMock,
},
}
const propsData = {
@ -118,10 +112,6 @@ describe('DeletedUserFormular', () => {
)
})
it('toasts a success message', () => {
expect(toastedSuccessMock).toBeCalledWith('user_deleted')
})
it('emits update deleted At', () => {
expect(wrapper.emitted('updateDeletedAt')).toEqual(
expect.arrayContaining([
@ -147,7 +137,7 @@ describe('DeletedUserFormular', () => {
})
it('toasts an error message', () => {
expect(toastedErrorMock).toBeCalledWith('Oh no!')
expect(toastErrorSpy).toBeCalledWith('Oh no!')
})
})
@ -215,10 +205,6 @@ describe('DeletedUserFormular', () => {
)
})
it('toasts a success message', () => {
expect(toastedSuccessMock).toBeCalledWith('user_recovered')
})
it('emits update deleted At', () => {
expect(wrapper.emitted('updateDeletedAt')).toEqual(
expect.arrayContaining([
@ -244,7 +230,7 @@ describe('DeletedUserFormular', () => {
})
it('toasts an error message', () => {
expect(toastedErrorMock).toBeCalledWith('Oh no!')
expect(toastErrorSpy).toBeCalledWith('Oh no!')
})
})

View File

@ -45,7 +45,6 @@ export default {
},
})
.then((result) => {
this.$toasted.success(this.$t('user_deleted'))
this.$emit('updateDeletedAt', {
userId: this.item.userId,
deletedAt: result.data.deleteUser,
@ -53,7 +52,7 @@ export default {
this.checked = false
})
.catch((error) => {
this.$toasted.error(error.message)
this.toastError(error.message)
})
},
unDeleteUser() {
@ -65,7 +64,7 @@ export default {
},
})
.then((result) => {
this.$toasted.success(this.$t('user_recovered'))
this.toastSuccess(this.$t('user_recovered'))
this.$emit('updateDeletedAt', {
userId: this.item.userId,
deletedAt: result.data.unDeleteUser,
@ -73,7 +72,7 @@ export default {
this.checked = false
})
.catch((error) => {
this.$toasted.error(error.message)
this.toastError(error.message)
})
},
},

View File

@ -1,5 +1,6 @@
import { mount } from '@vue/test-utils'
import EditCreationFormular from './EditCreationFormular.vue'
import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup'
const localVue = global.localVue
@ -16,8 +17,6 @@ const apolloMutateMock = jest.fn().mockResolvedValue({
})
const stateCommitMock = jest.fn()
const toastedErrorMock = jest.fn()
const toastedSuccessMock = jest.fn()
const mocks = {
$t: jest.fn((t) => t),
@ -37,10 +36,6 @@ const mocks = {
},
commit: stateCommitMock,
},
$toasted: {
error: toastedErrorMock,
success: toastedSuccessMock,
},
}
const now = new Date(Date.now())
@ -142,7 +137,7 @@ describe('EditCreationFormular', () => {
})
it('toasts a success message', () => {
expect(toastedSuccessMock).toBeCalledWith('creation_form.toasted_update')
expect(toastSuccessSpy).toBeCalledWith('creation_form.toasted_update')
})
})
@ -155,7 +150,7 @@ describe('EditCreationFormular', () => {
})
it('toasts an error message', () => {
expect(toastedErrorMock).toBeCalledWith('Oh no!')
expect(toastErrorSpy).toBeCalledWith('Oh no!')
})
})
})

View File

@ -132,7 +132,7 @@ export default {
moderator: Number(result.data.updatePendingCreation.moderator),
row: this.row,
})
this.$toasted.success(
this.toastSuccess(
this.$t('creation_form.toasted_update', {
value: this.value,
email: this.item.email,
@ -144,7 +144,7 @@ export default {
this.value = 0
})
.catch((error) => {
this.$toasted.error(error.message)
this.toastError(error.message)
// das creation Formular reseten
this.$refs.updateCreationForm.reset()
// Den geschöpften Wert auf o setzen

View File

@ -73,10 +73,6 @@ const mocks = {
},
},
},
$toasted: {
error: jest.fn(),
success: jest.fn(),
},
}
describe('SearchUserTable', () => {

View File

@ -5,6 +5,7 @@
"confirmed": "bestätigt",
"creation": "Schöpfung",
"creation_form": {
"creation_failed": "Ausstehende Schöpfung für {email} konnte nicht erzeugt werden.",
"creation_for": "Aktives Grundeinkommen für",
"enter_text": "Text eintragen",
"form": "Schöpfungsformular",
@ -28,6 +29,7 @@
"delete_user": "Nutzer löschen",
"details": "Details",
"edit": "Bearbeiten",
"error": "Fehler",
"e_mail": "E-Mail",
"firstname": "Vorname",
"gradido_admin_footer": "Gradido Akademie Adminkonsole",
@ -68,6 +70,7 @@
"remove_all": "alle Nutzer entfernen",
"save": "Speichern",
"status": "Status",
"success": "Erfolg",
"text": "Text",
"transaction": "Transaktion",
"transactionlist": {

View File

@ -5,6 +5,7 @@
"confirmed": "confirmed",
"creation": "Creation",
"creation_form": {
"creation_failed": "Could not create pending creation for {email}",
"creation_for": "Active Basic Income for",
"enter_text": "Enter text",
"form": "Creation form",
@ -28,6 +29,7 @@
"delete_user": "Delete user",
"details": "Details",
"edit": "Edit",
"error": "Error",
"e_mail": "E-mail",
"firstname": "Firstname",
"gradido_admin_footer": "Gradido Academy Admin Console",
@ -68,6 +70,7 @@
"remove_all": "Remove all users",
"save": "Speichern",
"status": "Status",
"success": "Success",
"text": "Text",
"transaction": "Transaction",
"transactionlist": {

View File

@ -13,31 +13,24 @@ import i18n from './i18n'
import VueApollo from 'vue-apollo'
import PortalVue from 'portal-vue'
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import Toasted from 'vue-toasted'
import { toasters } from './mixins/toaster'
import { apolloProvider } from './plugins/apolloProvider'
Vue.use(PortalVue)
Vue.use(BootstrapVue)
Vue.use(IconsPlugin)
Vue.use(VueApollo)
Vue.use(Toasted, {
position: 'top-center',
duration: 5000,
fullWidth: true,
action: {
text: 'x',
onClick: (e, toastObject) => {
toastObject.goAway(0)
},
},
})
Vue.mixin(toasters)
addNavigationGuards(router, store, apolloProvider.defaultClient, i18n)

View File

@ -0,0 +1,30 @@
export const toasters = {
methods: {
toastSuccess(message) {
this.toast(message, {
title: this.$t('success'),
variant: 'success',
})
},
toastError(message) {
this.toast(message, {
title: this.$t('error'),
variant: 'danger',
})
},
toast(message, options) {
// for unit tests, check that replace is present
if (message.replace) message = message.replace(/^GraphQL error: /, '')
this.$bvToast.toast(message, {
autoHideDelay: 5000,
appendToast: true,
solid: true,
toaster: 'b-toaster-top-right',
headerClass: 'gdd-toaster-title',
bodyClass: 'gdd-toaster-body',
toastClass: 'gdd-toaster',
...options,
})
},
},
}

View File

@ -1,5 +1,6 @@
import { mount } from '@vue/test-utils'
import Creation from './Creation.vue'
import { toastErrorSpy } from '../../test/testSetup'
const localVue = global.localVue
@ -29,18 +30,14 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
},
})
const toastErrorMock = jest.fn()
const storeCommitMock = jest.fn()
const mocks = {
$t: jest.fn((t) => t),
$t: jest.fn((t, options) => (options ? [t, options] : t)),
$d: jest.fn((d) => d),
$apollo: {
query: apolloQueryMock,
},
$toasted: {
error: toastErrorMock,
},
$store: {
commit: storeCommitMock,
state: {
@ -236,6 +233,25 @@ describe('Creation', () => {
})
})
describe('failed creations', () => {
beforeEach(async () => {
await wrapper
.findComponent({ name: 'CreationFormular' })
.vm.$emit('toast-failed-creations', ['bibi@bloxberg.de', 'benjamin@bluemchen.de'])
})
it('toasts two error messages', () => {
expect(toastErrorSpy).toBeCalledWith([
'creation_form.creation_failed',
{ email: 'bibi@bloxberg.de' },
])
expect(toastErrorSpy).toBeCalledWith([
'creation_form.creation_failed',
{ email: 'benjamin@bluemchen.de' },
])
})
})
describe('watchers', () => {
beforeEach(() => {
jest.clearAllMocks()
@ -298,7 +314,7 @@ describe('Creation', () => {
})
it('toasts an error message', () => {
expect(toastErrorMock).toBeCalledWith('Ouch')
expect(toastErrorSpy).toBeCalledWith('Ouch')
})
})
})

View File

@ -56,6 +56,7 @@
:creation="creation"
:items="itemsMassCreation"
@remove-all-bookmark="removeAllBookmarks"
@toast-failed-creations="toastFailedCreations"
/>
</b-col>
</b-row>
@ -118,7 +119,7 @@ export default {
}
})
.catch((error) => {
this.$toasted.error(error.message)
this.toastError(error.message)
})
},
pushItem(selectedItem) {
@ -144,6 +145,11 @@ export default {
this.$store.commit('setUserSelectedInMassCreation', [])
this.getUsers()
},
toastFailedCreations(failedCreations) {
failedCreations.forEach((email) =>
this.toastError(this.$t('creation_form.creation_failed', { email })),
)
},
},
computed: {
Searchfields() {

View File

@ -2,12 +2,11 @@ import { mount } from '@vue/test-utils'
import CreationConfirm from './CreationConfirm.vue'
import { deletePendingCreation } from '../graphql/deletePendingCreation'
import { confirmPendingCreation } from '../graphql/confirmPendingCreation'
import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup'
const localVue = global.localVue
const storeCommitMock = jest.fn()
const toastedErrorMock = jest.fn()
const toastedSuccessMock = jest.fn()
const apolloQueryMock = jest.fn().mockResolvedValue({
data: {
getPendingCreations: [
@ -47,10 +46,6 @@ const mocks = {
query: apolloQueryMock,
mutate: apolloMutateMock,
},
$toasted: {
error: toastedErrorMock,
success: toastedSuccessMock,
},
}
describe('CreationConfirm', () => {
@ -101,7 +96,7 @@ describe('CreationConfirm', () => {
})
it('toasts a success message', () => {
expect(toastedSuccessMock).toBeCalledWith('creation_form.toasted_delete')
expect(toastSuccessSpy).toBeCalledWith('creation_form.toasted_delete')
})
})
@ -112,7 +107,7 @@ describe('CreationConfirm', () => {
})
it('toasts an error message', () => {
expect(toastedErrorMock).toBeCalledWith('Ouchhh!')
expect(toastErrorSpy).toBeCalledWith('Ouchhh!')
})
})
@ -158,7 +153,7 @@ describe('CreationConfirm', () => {
})
it('toasts a success message', () => {
expect(toastedSuccessMock).toBeCalledWith('creation_form.toasted_created')
expect(toastSuccessSpy).toBeCalledWith('creation_form.toasted_created')
})
it('has 1 item left in the table', () => {
@ -173,7 +168,7 @@ describe('CreationConfirm', () => {
})
it('toasts an error message', () => {
expect(toastedErrorMock).toBeCalledWith('Ouchhh!')
expect(toastErrorSpy).toBeCalledWith('Ouchhh!')
})
})
})
@ -189,7 +184,7 @@ describe('CreationConfirm', () => {
})
it('toast an error message', () => {
expect(toastedErrorMock).toBeCalledWith('Ouch!')
expect(toastErrorSpy).toBeCalledWith('Ouch!')
})
})
})

View File

@ -43,10 +43,10 @@ export default {
})
.then((result) => {
this.updatePendingCreations(item.id)
this.$toasted.success(this.$t('creation_form.toasted_delete'))
this.toastSuccess(this.$t('creation_form.toasted_delete'))
})
.catch((error) => {
this.$toasted.error(error.message)
this.toastError(error.message)
})
},
confirmCreation() {
@ -60,11 +60,11 @@ export default {
.then((result) => {
this.overlay = false
this.updatePendingCreations(this.item.id)
this.$toasted.success(this.$t('creation_form.toasted_created'))
this.toastSuccess(this.$t('creation_form.toasted_created'))
})
.catch((error) => {
this.overlay = false
this.$toasted.error(error.message)
this.toastError(error.message)
})
},
getPendingCreations() {
@ -79,7 +79,7 @@ export default {
this.$store.commit('setOpenCreations', result.data.getPendingCreations.length)
})
.catch((error) => {
this.$toasted.error(error.message)
this.toastError(error.message)
})
},
updatePendingCreations(id) {

View File

@ -1,5 +1,6 @@
import { mount } from '@vue/test-utils'
import UserSearch from './UserSearch.vue'
import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup'
const localVue = global.localVue
@ -15,6 +16,7 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
email: 'bibi@bloxberg.de',
creation: [200, 400, 600],
emailChecked: true,
deletedAt: null,
},
{
userId: 2,
@ -23,6 +25,7 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
email: 'benjamin@bluemchen.de',
creation: [1000, 1000, 1000],
emailChecked: true,
deletedAt: null,
},
{
userId: 3,
@ -31,6 +34,7 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
email: 'peter@lustig.de',
creation: [0, 0, 0],
emailChecked: true,
deletedAt: null,
},
{
userId: 4,
@ -39,23 +43,19 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
email: 'new@user.ch',
creation: [1000, 1000, 1000],
emailChecked: false,
deletedAt: null,
},
],
},
},
})
const toastErrorMock = jest.fn()
const mocks = {
$t: jest.fn((t) => t),
$d: jest.fn((d) => String(d)),
$apollo: {
query: apolloQueryMock,
},
$toasted: {
error: toastErrorMock,
},
}
describe('UserSearch', () => {
@ -187,6 +187,21 @@ describe('UserSearch', () => {
})
})
describe('delete user', () => {
const now = new Date()
beforeEach(async () => {
wrapper.findComponent({ name: 'SearchUserTable' }).vm.$emit('updateDeletedAt', 4, now)
})
it('marks the user as deleted', () => {
expect(wrapper.vm.searchResult.find((obj) => obj.userId === 4).deletedAt).toEqual(now)
})
it('toasts a success message', () => {
expect(toastSuccessSpy).toBeCalledWith('user_deleted')
})
})
describe('apollo returns error', () => {
beforeEach(() => {
apolloQueryMock.mockRejectedValue({
@ -196,7 +211,7 @@ describe('UserSearch', () => {
})
it('toasts an error message', () => {
expect(toastErrorMock).toBeCalledWith('Ouch')
expect(toastErrorSpy).toBeCalledWith('Ouch')
})
})
})

View File

@ -94,11 +94,12 @@ export default {
this.searchResult = result.data.searchUsers.userList
})
.catch((error) => {
this.$toasted.error(error.message)
this.toastError(error.message)
})
},
updateDeletedAt(userId, deletedAt) {
this.searchResult.find((obj) => obj.userId === userId).deletedAt = deletedAt
this.toastSuccess(this.$t('user_deleted'))
},
},
watch: {

View File

@ -5,11 +5,18 @@ import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
// without this async calls are not working
import 'regenerator-runtime'
import { toasters } from '../src/mixins/toaster'
export const toastErrorSpy = jest.spyOn(toasters.methods, 'toastError')
export const toastSuccessSpy = jest.spyOn(toasters.methods, 'toastSuccess')
global.localVue = createLocalVue()
global.localVue.use(BootstrapVue)
global.localVue.use(IconsPlugin)
global.localVue.mixin(toasters)
// throw errors for vue warnings to force the programmers to take care about warnings
Vue.config.warnHandler = (w) => {
throw new Error(w)

View File

@ -12512,11 +12512,6 @@ vue-template-es2015-compiler@^1.6.0, vue-template-es2015-compiler@^1.9.0:
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
vue-toasted@^1.1.28:
version "1.1.28"
resolved "https://registry.yarnpkg.com/vue-toasted/-/vue-toasted-1.1.28.tgz#dbabb83acc89f7a9e8765815e491d79f0dc65c26"
integrity sha512-UUzr5LX51UbbiROSGZ49GOgSzFxaMHK6L00JV8fir/CYNJCpIIvNZ5YmS4Qc8Y2+Z/4VVYRpeQL2UO0G800Raw==
vue@^2.6.11:
version "2.6.14"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.14.tgz#e51aa5250250d569a3fbad3a8a5a687d6036e235"

View File

@ -17,6 +17,6 @@ export class Balance {
@Field(() => Decimal)
decay: Decimal
@Field(() => String)
decayDate: string
@Field(() => Date)
decayDate: Date
}

View File

@ -1,6 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field } from 'type-graphql'
import { ObjectType, Field, Int } from 'type-graphql'
import Decimal from 'decimal.js-light'
@ObjectType()
@ -31,6 +29,6 @@ export class Decay {
@Field(() => Date, { nullable: true })
end: Date | null
@Field(() => Number, { nullable: true })
@Field(() => Int, { nullable: true })
duration: number | null
}

View File

@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field } from 'type-graphql'
import { Decay } from './Decay'
import { Transaction as dbTransaction } from '@entity/Transaction'
@ -25,7 +23,7 @@ export class Transaction {
transaction.decay,
transaction.decayStart,
transaction.balanceDate,
(transaction.balanceDate.getTime() - transaction.decayStart.getTime()) / 1000,
Math.round((transaction.balanceDate.getTime() - transaction.decayStart.getTime()) / 1000),
)
}
this.memo = transaction.memo

View File

@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field } from 'type-graphql'
import CONFIG from '../../config'
import Decimal from 'decimal.js-light'

View File

@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field } from 'type-graphql'
import { KlickTipp } from './KlickTipp'
import { User as dbUser } from '@entity/User'

View File

@ -307,13 +307,13 @@ export class AdminResolver {
const receivedCallDate = new Date()
const transactionRepository = getCustomRepository(TransactionRepository)
const lastUserTransaction = await transactionRepository.findLastForUser(pendingCreation.userId)
const lastTransaction = await transactionRepository.findLastForUser(pendingCreation.userId)
let newBalance = new Decimal(0)
if (lastUserTransaction) {
if (lastTransaction) {
newBalance = calculateDecay(
lastUserTransaction.balance,
lastUserTransaction.balanceDate,
lastTransaction.balance,
lastTransaction.balanceDate,
receivedCallDate,
).balance
}

View File

@ -30,7 +30,8 @@ import { RIGHTS } from '../../auth/RIGHTS'
import { User } from '../model/User'
import { communityUser } from '../../util/communityUser'
import { virtualDecayTransaction } from '../../util/virtualDecayTransaction'
import Decimal from '../scalar/Decimal'
import Decimal from 'decimal.js-light'
import { calculateDecay } from '../../util/decay'
@Resolver()
export class TransactionResolver {
@ -47,6 +48,7 @@ export class TransactionResolver {
}: Paginated,
@Ctx() context: any,
): Promise<TransactionList> {
const now = new Date()
// find user
const userRepository = getCustomRepository(UserRepository)
// TODO: separate those usecases - this is a security issue
@ -60,65 +62,6 @@ export class TransactionResolver {
{ order: { balanceDate: 'DESC' } },
)
if (!lastTransaction) {
// TODO Have proper return type here
throw new Error('User has no transactions')
}
// find transactions
const limit = currentPage === 1 && order === Order.DESC ? pageSize - 1 : pageSize
const offset =
currentPage === 1 ? 0 : (currentPage - 1) * pageSize - (order === Order.DESC ? 1 : 0)
const transactionRepository = getCustomRepository(TransactionRepository)
const [userTransactions, userTransactionsCount] = await transactionRepository.findByUserPaged(
user.id,
limit,
offset,
order,
onlyCreations,
)
// find involved users
let involvedUserIds: number[] = []
userTransactions.forEach((transaction: dbTransaction) => {
involvedUserIds.push(transaction.userId)
if (transaction.linkedUserId) {
involvedUserIds.push(transaction.linkedUserId)
}
})
// remove duplicates
involvedUserIds = involvedUserIds.filter((value, index, self) => self.indexOf(value) === index)
// We need to show the name for deleted users for old transactions
const involvedDbUsers = await dbUser
.createQueryBuilder()
.withDeleted()
.where('id IN (:...userIds)', { userIds: involvedUserIds })
.getMany()
const involvedUsers = involvedDbUsers.map((u) => new User(u))
const self = new User(user)
const transactions: Transaction[] = []
// decay transaction
if (currentPage === 1 && order === Order.DESC) {
const now = new Date()
transactions.push(
virtualDecayTransaction(lastTransaction.balance, lastTransaction.balanceDate, now, self),
)
}
// transactions
for (let i = 0; i < userTransactions.length; i++) {
const userTransaction = userTransactions[i]
let linkedUser = null
if (userTransaction.typeId === TypeId.CREATION) {
linkedUser = communityUser
} else {
linkedUser = involvedUsers.find((u) => u.id === userTransaction.linkedUserId)
}
transactions.push(new Transaction(userTransaction, self, linkedUser))
}
// get GDT
let balanceGDT = null
try {
@ -134,9 +77,59 @@ export class TransactionResolver {
console.log('Could not query GDT Server', err)
}
if (!lastTransaction) {
return new TransactionList(new Decimal(0), [], 0, balanceGDT)
}
// find transactions
// first page can contain 26 due to virtual decay transaction
const offset = (currentPage - 1) * pageSize
const transactionRepository = getCustomRepository(TransactionRepository)
const [userTransactions, userTransactionsCount] = await transactionRepository.findByUserPaged(
user.id,
pageSize,
offset,
order,
onlyCreations,
)
// find involved users; I am involved
const involvedUserIds: number[] = [user.id]
userTransactions.forEach((transaction: dbTransaction) => {
if (transaction.linkedUserId && !involvedUserIds.includes(transaction.linkedUserId)) {
involvedUserIds.push(transaction.linkedUserId)
}
})
// We need to show the name for deleted users for old transactions
const involvedDbUsers = await dbUser
.createQueryBuilder()
.withDeleted()
.where('id IN (:...userIds)', { userIds: involvedUserIds })
.getMany()
const involvedUsers = involvedDbUsers.map((u) => new User(u))
const self = new User(user)
const transactions: Transaction[] = []
// decay transaction
if (currentPage === 1 && order === Order.DESC) {
transactions.push(
virtualDecayTransaction(lastTransaction.balance, lastTransaction.balanceDate, now, self),
)
}
// transactions
userTransactions.forEach((userTransaction) => {
const linkedUser =
userTransaction.typeId === TransactionTypeId.CREATION
? communityUser
: involvedUsers.find((u) => u.id === userTransaction.linkedUserId)
transactions.push(new Transaction(userTransaction, self, linkedUser))
})
// Construct Result
return new TransactionList(
lastTransaction.balance,
calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, now).balance,
transactions,
userTransactionsCount,
balanceGDT,
@ -184,7 +177,7 @@ export class TransactionResolver {
transactionSend.memo = memo
transactionSend.userId = senderUser.id
transactionSend.linkedUserId = recipientUser.id
transactionSend.amount = amount
transactionSend.amount = amount.mul(-1)
transactionSend.balance = sendBalance.balance
transactionSend.balanceDate = receivedCallDate
transactionSend.decay = sendBalance.decay.decay

View File

@ -7,36 +7,35 @@ describe('utils/decay', () => {
it('has base 0.99999997802044727', () => {
const amount = new Decimal(1.0)
const seconds = 1
expect(decayFormula(amount, seconds)).toBe(0.99999997802044727)
})
// Not sure if the following skiped tests make sence!?
it('has negative decay?', async () => {
const amount = new Decimal(1.0)
const seconds = 1
expect(decayFormula(amount, seconds)).toBe(-0.99999997802044727)
// TODO: toString() was required, we could not compare two decimals
expect(decayFormula(amount, seconds).toString()).toBe('0.999999978035040489732012')
})
it('has correct backward calculation', async () => {
const amount = new Decimal(1.0)
const seconds = -1
expect(decayFormula(amount, seconds)).toBe(1.0000000219795533)
expect(decayFormula(amount, seconds).toString()).toBe('1.000000021964959992727444')
})
// not possible, nodejs hasn't enough accuracy
it('has correct forward calculation', async () => {
const amount = new Decimal(1.0).div(0.99999997802044727)
// we get pretty close, but not exact here, skipping
it.skip('has correct forward calculation', async () => {
const amount = new Decimal(1.0).div(
new Decimal('0.99999997803504048973201202316767079413460520837376'),
)
const seconds = 1
expect(decayFormula(amount, seconds)).toBe(1.0)
expect(decayFormula(amount, seconds).toString()).toBe('1.0')
})
})
it.skip('has base 0.99999997802044727', async () => {
it('has base 0.99999997802044727', async () => {
const now = new Date()
now.setSeconds(1)
const oneSecondAgo = new Date(now.getTime())
oneSecondAgo.setSeconds(0)
expect(calculateDecay(new Decimal(1.0), oneSecondAgo, now)).toBe(0.99999997802044727)
expect(calculateDecay(new Decimal(1.0), oneSecondAgo, now).balance.toString()).toBe(
'0.999999978035040489732012',
)
})
it('returns input amount when from and to is the same', async () => {
const now = new Date()
expect(calculateDecay(new Decimal(100.0), now, now).balance).toBe(100.0)
expect(calculateDecay(new Decimal(100.0), now, now).balance.toString()).toBe('100')
})
})

View File

@ -2,6 +2,7 @@ import Faker from 'faker'
import { define } from 'typeorm-seeding'
import { Transaction } from '../../entity/Transaction'
import { TransactionContext } from '../interface/TransactionContext'
import Decimal from 'decimal.js-light'
define(Transaction, (faker: typeof Faker, context?: TransactionContext) => {
if (!context) {
@ -12,6 +13,8 @@ define(Transaction, (faker: typeof Faker, context?: TransactionContext) => {
transaction.typeId = context.typeId // || 2
transaction.userId = context.userId
transaction.amount = context.amount
transaction.balance = context.balance
transaction.decay = new Decimal(0) // context.decay
transaction.memo = context.memo
transaction.creationDate = context.creationDate || new Date()
// transaction.sendReceiverPublicKey = context.sendReceiverPublicKey || null