Merge branch 'query-transaction-link' into Elweyn/issue1558

This commit is contained in:
elweyn 2022-03-09 16:23:08 +01:00
commit 6ce7a59956
32 changed files with 368 additions and 116 deletions

View File

@ -18,6 +18,8 @@ export enum RIGHTS {
SET_PASSWORD = 'SET_PASSWORD',
UPDATE_USER_INFOS = 'UPDATE_USER_INFOS',
HAS_ELOPAGE = 'HAS_ELOPAGE',
CREATE_TRANSACTION_LINK = 'CREATE_TRANSACTION_LINK',
QUERY_TRANSACTION_LINK = 'QUERY_TRANSACTION_LINK',
// Admin
SEARCH_USERS = 'SEARCH_USERS',
CREATE_PENDING_CREATION = 'CREATE_PENDING_CREATION',

View File

@ -18,6 +18,7 @@ export const ROLE_USER = new Role('user', [
RIGHTS.LOGOUT,
RIGHTS.UPDATE_USER_INFOS,
RIGHTS.HAS_ELOPAGE,
RIGHTS.CREATE_TRANSACTION_LINK,
])
export const ROLE_ADMIN = new Role('admin', Object.values(RIGHTS)) // all rights

View File

@ -10,7 +10,7 @@ Decimal.set({
})
const constants = {
DB_VERSION: '0029-clean_transaction_table',
DB_VERSION: '0030-transaction_link',
DECAY_START_TIME: new Date('2021-05-13 17:46:31'), // GMT+0
}

View File

@ -0,0 +1,14 @@
import { ArgsType, Field } from 'type-graphql'
import Decimal from 'decimal.js-light'
@ArgsType()
export default class TransactionLinkArgs {
@Field(() => Decimal)
amount: Decimal
@Field(() => String)
memo: string
@Field(() => Boolean, { nullable: true })
showEmail?: boolean
}

View File

@ -0,0 +1,50 @@
import { ObjectType, Field } from 'type-graphql'
import Decimal from 'decimal.js-light'
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
import { User } from './User'
@ObjectType()
export class TransactionLink {
constructor(transactionLink: dbTransactionLink, user: User) {
this.id = transactionLink.id
this.user = user
this.amount = transactionLink.amount
this.memo = transactionLink.memo
this.code = transactionLink.code
this.createdAt = transactionLink.createdAt
this.validUntil = transactionLink.validUntil
this.showEmail = transactionLink.showEmail
this.redeemedAt = null
this.redeemedBy = null
}
@Field(() => Number)
id: number
@Field(() => User)
user: User
@Field(() => Decimal)
amount: Decimal
@Field(() => String)
memo: string
@Field(() => String)
code: string
@Field(() => Date)
createdAt: Date
@Field(() => Date)
validUntil: Date
@Field(() => Boolean)
showEmail: boolean
@Field(() => Date, { nullable: true })
redeemedAt: Date | null
@Field(() => User, { nullable: true })
redeemedBy: User | null
}

View File

@ -0,0 +1,14 @@
import { transactionLinkCode } from './TransactionLinkResolver'
describe('transactionLinkCode', () => {
const date = new Date()
it('returns a string of length 96', () => {
expect(transactionLinkCode(date)).toHaveLength(96)
})
it('returns a string that ends with the hex value of date', () => {
const regexp = new RegExp(date.getTime().toString(16) + '$')
expect(transactionLinkCode(date)).toEqual(expect.stringMatching(regexp))
})
})

View File

@ -0,0 +1,74 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Resolver, Args, Authorized, Ctx, Mutation, Query, Arg } from 'type-graphql'
import { getCustomRepository } from '@dbTools/typeorm'
import { TransactionLink } from '@model/TransactionLink'
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
import TransactionLinkArgs from '@arg/TransactionLinkArgs'
import { UserRepository } from '@repository/User'
import { calculateBalance } from '@/util/validate'
import { RIGHTS } from '@/auth/RIGHTS'
import { randomBytes } from 'crypto'
import { User } from '@model/User'
// TODO: do not export, test it inside the resolver
export const transactionLinkCode = (date: Date): string => {
const time = date.getTime().toString(16)
return (
randomBytes(48)
.toString('hex')
.substring(0, 96 - time.length) + time
)
}
const transactionLinkExpireDate = (date: Date): Date => {
// valid for 14 days
return new Date(date.setDate(date.getDate() + 14))
}
@Resolver()
export class TransactionLinkResolver {
@Authorized([RIGHTS.CREATE_TRANSACTION_LINK])
@Mutation(() => TransactionLink)
async createTransactionLink(
@Args() { amount, memo, showEmail = false }: TransactionLinkArgs,
@Ctx() context: any,
): Promise<TransactionLink> {
const userRepository = getCustomRepository(UserRepository)
const user = await userRepository.findByPubkeyHex(context.pubKey)
// validate amount
// TODO taken from transaction resolver, duplicate code
const createdDate = new Date()
const sendBalance = await calculateBalance(user.id, amount.mul(-1), createdDate)
if (!sendBalance) {
throw new Error("user hasn't enough GDD or amount is < 0")
}
// TODO!!!! Test balance for pending transaction links
const transactionLink = dbTransactionLink.create()
transactionLink.userId = user.id
transactionLink.amount = amount
transactionLink.memo = memo
transactionLink.code = transactionLinkCode(createdDate)
transactionLink.createdAt = createdDate
transactionLink.validUntil = transactionLinkExpireDate(createdDate)
transactionLink.showEmail = showEmail
await dbTransactionLink.save(transactionLink).catch((error) => {
throw error
})
return new TransactionLink(transactionLink, new User(user))
}
@Authorized([RIGHTS.QUERY_TRANSACTION_LINK])
@Query(() => TransactionLink)
async queryTransactionLink(@Arg('code') code: string): Promise<TransactionLink> {
const transactionLink = await dbTransactionLink.findOneOrFail({ code })
const userRepository = getCustomRepository(UserRepository)
const user = await userRepository.findOneOrFail({ id: transactionLink.userId })
return new TransactionLink(transactionLink, new User(user))
}
}

View File

@ -0,0 +1,58 @@
import Decimal from 'decimal.js-light'
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer'
@Entity('transaction_links')
export class TransactionLink extends BaseEntity {
@PrimaryGeneratedColumn('increment', { unsigned: true })
id: number
@Column({ unsigned: true, nullable: false })
userId: number
@Column({
type: 'decimal',
precision: 40,
scale: 20,
nullable: false,
transformer: DecimalTransformer,
})
amount: Decimal
@Column({ length: 255, nullable: false, collation: 'utf8mb4_unicode_ci' })
memo: string
@Column({ length: 96, nullable: false, collation: 'utf8mb4_unicode_ci' })
code: string
@Column({
type: 'datetime',
default: () => 'CURRENT_TIMESTAMP',
nullable: false,
})
createdAt: Date
@Column({
type: 'datetime',
default: () => 'CURRENT_TIMESTAMP',
nullable: false,
})
validUntil: Date
@Column({
type: 'boolean',
default: () => false,
nullable: false,
})
showEmail: boolean
@Column({
type: 'datetime',
default: () => 'CURRENT_TIMESTAMP',
nullable: true,
})
redeemedAt?: Date | null
@Column({ type: 'int', unsigned: true, nullable: true })
redeemedBy?: number | null
}

View File

@ -0,0 +1 @@
export { TransactionLink } from './0030-transaction_link/TransactionLink'

View File

@ -3,6 +3,7 @@ import { LoginEmailOptIn } from './LoginEmailOptIn'
import { Migration } from './Migration'
import { ServerUser } from './ServerUser'
import { Transaction } from './Transaction'
import { TransactionLink } from './TransactionLink'
import { User } from './User'
import { UserSetting } from './UserSetting'
import { AdminPendingCreation } from './AdminPendingCreation'
@ -14,6 +15,7 @@ export const entities = [
Migration,
ServerUser,
Transaction,
TransactionLink,
User,
UserSetting,
]

View File

@ -0,0 +1,26 @@
/* MIGRATION TO CREATE TRANSACTION_LINK TABLE */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/no-explicit-any */
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(`
CREATE TABLE \`transaction_links\` (
\`id\` int UNSIGNED NOT NULL AUTO_INCREMENT,
\`userId\` int UNSIGNED NOT NULL,
\`amount\` DECIMAL(40,20) NOT NULL,
\`memo\` varchar(255) NOT NULL,
\`code\` varchar(96) NOT NULL,
\`createdAt\` datetime NOT NULL,
\`validUntil\` datetime NOT NULL,
\`showEmail\` boolean NOT NULL DEFAULT false,
\`redeemedAt\` datetime,
\`redeemedBy\` int UNSIGNED,
PRIMARY KEY (\`id\`)
) ENGINE = InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
`)
}
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(`DROP TABLE \`transaction_links\`;`)
}

View File

@ -13,9 +13,9 @@
</b-col>
<b-col cols="6">
<div>
{{ $n(Number(balance) - Number(decay.decay), 'decimal') }}
GDD {{ $n(Number(decay.decay) * -1, 'decimal') }} GDD =
<b>{{ $n(Number(balance), 'decimal') }} GDD</b>
{{ (Number(balance) - Number(decay.decay)) | GDD }}
{{ decay.decay | GDD }} =
<b>{{ balance | GDD }}</b>
</div>
</b-col>
</b-row>

View File

@ -17,12 +17,8 @@
<!-- Decay-->
<b-row>
<b-col cols="6" class="text-right">
<div>{{ $t('decay.decay') }}</div>
</b-col>
<b-col cols="6">
<div> {{ $n(decay.decay * -1, 'decimal') }}</div>
</b-col>
<b-col cols="6" class="text-right">{{ $t('decay.decay') }}</b-col>
<b-col cols="6">{{ decay.decay | GDD }}</b-col>
</b-row>
<hr class="mt-2 mb-2" />
<b-row>
@ -32,39 +28,19 @@
</b-row>
<!-- Type-->
<b-row>
<b-col cols="6" class="text-right">
<div v-if="typeId === 'SEND'">{{ $t('decay.sent') }}</div>
<div v-if="typeId === 'RECEIVE'">{{ $t('decay.received') }}</div>
</b-col>
<b-col cols="6">
<div v-if="typeId === 'SEND'"> {{ $n(amount * -1, 'decimal') }}</div>
<div v-if="typeId === 'RECEIVE'">{{ $n(amount, 'decimal') }}</div>
</b-col>
<b-col cols="6" class="text-right">{{ $t(`decay.${typeId.toLowerCase()}`) }}</b-col>
<b-col cols="6">{{ amount | GDD }}</b-col>
</b-row>
<!-- Decay-->
<b-row>
<b-col cols="6" class="text-right">
<div>{{ $t('decay.decay') }}</div>
</b-col>
<b-col cols="6">
<div> {{ $n(decay.decay * -1, 'decimal') }}</div>
</b-col>
<b-col cols="6" class="text-right">{{ $t('decay.decay') }}</b-col>
<b-col cols="6">{{ decay.decay | GDD }}</b-col>
</b-row>
<!-- Total-->
<b-row>
<b-col cols="6" class="text-right">
<div>{{ $t('decay.total') }}</div>
</b-col>
<b-col cols="6" class="text-right">{{ $t('decay.total') }}</b-col>
<b-col cols="6">
<div v-if="typeId === 'SEND'">
<b> {{ $n((Number(amount) + Number(decay.decay)) * -1, 'decimal') }}</b>
</div>
<div v-if="typeId === 'RECEIVE'">
<b>{{ $n(Number(amount) + Number(decay.decay), 'decimal') }}</b>
</div>
<div v-if="typeId === 'CREATION'">
<b>{{ $n(Number(amount) + Number(decay.decay), 'decimal') }}</b>
</div>
<b>{{ (Number(amount) + Number(decay.decay)) | GDD }}</b>
</b-col>
</b-row>
</div>

View File

@ -25,14 +25,7 @@
<div>{{ $t('decay.past_time') }}</div>
</b-col>
<b-col cols="6">
<span v-if="duration">
<span v-if="duration.years > 0">{{ duration.years }} {{ $t('decay.year') }},</span>
<span v-if="duration.months > 0">{{ duration.months }} {{ $t('decay.months') }},</span>
<span v-if="duration.days > 0">{{ duration.days }} {{ $t('decay.days') }},</span>
<span v-if="duration.hours > 0">{{ duration.hours }} {{ $t('decay.hours') }},</span>
<span v-if="duration.minutes > 0">{{ duration.minutes }} {{ $t('decay.minutes') }},</span>
<span v-if="duration.seconds > 0">{{ duration.seconds }} {{ $t('decay.seconds') }}</span>
</span>
<span v-if="duration">{{ durationText }}</span>
</b-col>
</b-row>
@ -41,9 +34,7 @@
<b-col cols="6" class="text-right">
<div>{{ $t('decay.decay') }}</div>
</b-col>
<b-col cols="6">
<div> {{ $n(decay.decay * -1, 'decimal') }}</div>
</b-col>
<b-col cols="6">{{ decay.decay | GDD }}</b-col>
</b-row>
<hr class="mt-2 mb-2" />
<b-row>
@ -53,23 +44,13 @@
</b-row>
<!-- Type-->
<b-row>
<b-col cols="6" class="text-right">
<div v-if="typeId === 'SEND'">{{ $t('decay.sent') }}</div>
<div v-if="typeId === 'RECEIVE'">{{ $t('decay.received') }}</div>
</b-col>
<b-col cols="6">
<div v-if="typeId === 'SEND'"> {{ $n(amount * -1, 'decimal') }}</div>
<div v-if="typeId === 'RECEIVE'">{{ $n(amount, 'decimal') }}</div>
</b-col>
<b-col cols="6" class="text-right">{{ $t(`decay.${typeId.toLowerCase()}`) }}</b-col>
<b-col cols="6">{{ amount | GDD }}</b-col>
</b-row>
<!-- Decay-->
<b-row>
<b-col cols="6" class="text-right">
<div>{{ $t('decay.decay') }}</div>
</b-col>
<b-col cols="6">
<div> {{ $n(decay.decay * -1, 'decimal') }}</div>
</b-col>
<b-col cols="6" class="text-right">{{ $t('decay.decay') }}</b-col>
<b-col cols="6">{{ decay.decay | GDD }}</b-col>
</b-row>
<!-- Total-->
<b-row>
@ -77,15 +58,7 @@
<div>{{ $t('decay.total') }}</div>
</b-col>
<b-col cols="6">
<div v-if="typeId === 'SEND'">
<b> {{ $n((Number(amount) + Number(decay.decay)) * -1, 'decimal') }}</b>
</div>
<div v-if="typeId === 'RECEIVE'">
<b>{{ $n(Number(amount) + Number(decay.decay), 'decimal') }}</b>
</div>
<div v-if="typeId === 'CREATION'">
<b>{{ $n(Number(amount) + Number(decay.decay), 'decimal') }}</b>
</div>
<b>{{ (Number(amount) + Number(decay.decay)) | GDD }}</b>
</b-col>
</b-row>
</div>
@ -104,6 +77,17 @@ export default {
duration() {
return this.$moment.duration(new Date(this.decay.end) - new Date(this.decay.start))._data
},
durationText() {
const order = ['years', 'months', 'days', 'hours', 'minutes', 'seconds']
const result = []
order.forEach((timeSpan) => {
if (this.duration[timeSpan] > 0) {
const locale = this.$t(`decay.${timeSpan}`)
result.push(`${this.duration[timeSpan]} ${locale}`)
}
})
return result.join(', ')
},
},
}
</script>

View File

@ -1,6 +1,6 @@
<template>
<div class="decayinformation-short">
<span> {{ decay ? $n(Number(decay.decay) * -1, 'decimal') : '' }}</span>
<span v-if="decay.decay">{{ decay.decay | amount }}</span>
</div>
</template>
<script>

View File

@ -88,6 +88,7 @@ describe('GddTransactionList', () => {
describe('with transactions', () => {
beforeEach(async () => {
const decayStartBlock = new Date(2001, 8, 9)
await wrapper.setProps({
transactions: [
{
@ -103,9 +104,7 @@ describe('GddTransactionList', () => {
start: '2022-02-28T13:55:47.000Z',
end: '2022-03-03T08:54:54.020Z',
duration: 241147.02,
__typename: 'Decay',
},
__typename: 'Transaction',
},
{
id: 9,
@ -117,16 +116,13 @@ describe('GddTransactionList', () => {
linkedUser: {
firstName: 'Bibi',
lastName: 'Bloxberg',
__typename: 'User',
},
decay: {
decay: '-0.2038314055482643084',
start: '2022-02-25T07:29:26.000Z',
end: '2022-02-28T13:55:47.000Z',
duration: 282381,
__typename: 'Decay',
},
__typename: 'Transaction',
},
{
id: 8,
@ -138,16 +134,13 @@ describe('GddTransactionList', () => {
linkedUser: {
firstName: 'Gradido',
lastName: 'Akademie',
__typename: 'User',
},
decay: {
decay: '-0.03517768386652623868',
start: '2022-02-23T10:55:30.000Z',
end: '2022-02-25T07:29:26.000Z',
duration: 160436,
__typename: 'Decay',
},
__typename: 'Transaction',
},
{
id: 6,
@ -159,24 +152,22 @@ describe('GddTransactionList', () => {
linkedUser: {
firstName: 'Bibi',
lastName: 'Bloxberg',
__typename: 'User',
},
decay: {
decay: '0',
start: null,
end: null,
duration: null,
__typename: 'Decay',
},
__typename: 'Transaction',
},
],
count: 12,
decayStartBlock,
})
})
it('renders 4 transactions', () => {
expect(wrapper.findAll('div.list-group-item')).toHaveLength(3)
expect(wrapper.findAll('div.list-group-item')).toHaveLength(4)
})
describe('decay transactions', () => {
@ -238,11 +229,12 @@ describe('GddTransactionList', () => {
expect(transaction.findAll('svg').at(1).classes()).toContain('text-danger')
})
// it('has a minus operator', () => {
// expect(transaction.findAll('.gdd-transaction-list-item-operator').at(0).text()).toContain(
// '-',
// )
// })
// operators are renderd by GDD filter
it.skip('has a minus operator', () => {
expect(transaction.findAll('.gdd-transaction-list-item-operator').at(0).text()).toContain(
'-',
)
})
it('shows the amount of transaction', () => {
expect(transaction.findAll('.gdd-transaction-list-item-amount').at(0).text()).toContain(
@ -270,7 +262,7 @@ describe('GddTransactionList', () => {
it('shows the decay calculation', () => {
expect(transaction.findAll('div.gdd-transaction-list-item-decay').at(0).text()).toContain(
' 0.20383140554826432',
' 0.2038314055482643084',
)
})
})
@ -293,7 +285,7 @@ describe('GddTransactionList', () => {
it('has a bi-gift icon', () => {
expect(transaction.findAll('svg').at(1).classes()).toEqual([
'bi-arrow-right-circle',
'bi-gift',
'gradido-global-color-accent',
'm-mb-1',
'font2em',
@ -308,7 +300,8 @@ describe('GddTransactionList', () => {
)
})
it('has a plus operator', () => {
// operators are renderd by GDD filter
it.skip('has a plus operator', () => {
expect(transaction.findAll('.gdd-transaction-list-item-operator').at(0).text()).toContain(
'+',
)
@ -316,19 +309,19 @@ describe('GddTransactionList', () => {
it('shows the amount of transaction', () => {
expect(transaction.findAll('.gdd-transaction-list-item-amount').at(0).text()).toContain(
'10',
'1000',
)
})
it('shows the name of the receiver', () => {
expect(transaction.findAll('.gdd-transaction-list-item-name').at(0).text()).toContain(
'Bibi Bloxberg',
'Gradido Akademie',
)
})
it('shows the date of the transaction', () => {
expect(transaction.findAll('.gdd-transaction-list-item-date').at(0).text()).toContain(
'Wed Feb 23 2022 10:55:30 GMT+0000 (Coordinated Universal Time)',
'Fri Feb 25 2022 07:29:26 GMT+0000 (Coordinated Universal Time)',
)
})
})
@ -336,7 +329,7 @@ describe('GddTransactionList', () => {
describe('receive transactions', () => {
let transaction
beforeEach(() => {
transaction = wrapper.findAll('div.list-group-item').at(2)
transaction = wrapper.findAll('div.list-group-item').at(3)
})
it('has a bi-caret-down-square icon', () => {
@ -363,7 +356,8 @@ describe('GddTransactionList', () => {
])
})
it('has a plus operator', () => {
// operators are renderd by GDD filter
it.skip('has a plus operator', () => {
expect(transaction.findAll('.gdd-transaction-list-item-operator').at(0).text()).toContain(
'+',
)

View File

@ -21,7 +21,6 @@ const mocks = {
isAdmin: true,
},
},
$n: jest.fn((n) => n),
}
describe('Navbar', () => {

View File

@ -10,7 +10,7 @@
</div>
<b-navbar-nav class="ml-auto" is-nav>
<b-nav-item>{{ pending ? '—' : $n(balance, 'decimal') }} GDD</b-nav-item>
<b-nav-item>{{ pending ? '—' : balance | amount }} GDD</b-nav-item>
<b-nav-item to="/profile" right class="d-none d-sm-none d-md-none d-lg-flex shadow-lg">
<small>
{{ $store.state.firstName }} {{ $store.state.lastName }},

View File

@ -7,7 +7,6 @@ const mocks = {
$i18n: {
locale: 'en',
},
$n: jest.fn((n) => n),
$t: jest.fn((t) => t),
$d: jest.fn((d) => d),
}

View File

@ -23,9 +23,8 @@
<b-row>
<b-col cols="5">
<div class="text-right">
<span class="gdd-transaction-list-item-operator">+</span>
<span class="gdd-transaction-list-item-amount">
{{ $n(amount, 'decimal') }}
{{ amount | GDD }}
</span>
</div>
</b-col>

View File

@ -7,7 +7,6 @@ const mocks = {
$i18n: {
locale: 'en',
},
$n: jest.fn((n) => n),
$t: jest.fn((t) => t),
$d: jest.fn((d) => d),
}

View File

@ -23,9 +23,8 @@
<b-row>
<b-col cols="5">
<div class="text-right">
<span class="gdd-transaction-list-item-operator"></span>
<span class="gdd-transaction-list-item-amount">
{{ $n(Number(amount) * -1, 'decimal') }}
{{ amount | GDD }}
</span>
</div>
</b-col>

View File

@ -7,7 +7,6 @@ const mocks = {
$i18n: {
locale: 'en',
},
$n: jest.fn((n) => n),
$t: jest.fn((t) => t),
$d: jest.fn((d) => d),
}

View File

@ -26,9 +26,8 @@
<b-row>
<b-col cols="5">
<div class="text-right">
<span class="gdd-transaction-list-item-operator">+</span>
<span class="gdd-transaction-list-item-amount">
{{ $n(amount, 'decimal') }}
{{ amount | GDD }}
</span>
</div>
</b-col>

View File

@ -7,7 +7,6 @@ const mocks = {
$i18n: {
locale: 'en',
},
$n: jest.fn((n) => n),
$t: jest.fn((t) => t),
$d: jest.fn((d) => d),
}

View File

@ -23,9 +23,8 @@
<b-row>
<b-col cols="5">
<div class="text-right">
<span class="gdd-transaction-list-item-operator"></span>
<span class="gdd-transaction-list-item-amount">
{{ $n(Number(amount) * -1, 'decimal') }}
{{ amount | GDD }}
</span>
</div>
</b-col>
@ -79,7 +78,7 @@
<b-collapse class="pb-4 pt-5" v-model="visible">
<decay-information-before-startblock v-if="decay.start === null" />
<decay-information-decay-startblock
v-else-if="true"
v-else-if="isStartBlock"
:amount="amount"
:decay="decay"
:typeId="typeId"

View File

@ -0,0 +1,18 @@
let i18n
export const loadFilters = (_i18n) => {
i18n = _i18n
return { amount, GDD }
}
const amount = (value) => {
if (!value && value !== 0) return ''
return i18n.n(value.toString(), 'decimal').replace('-', ' ')
}
const GDD = (value) => {
value = amount(value)
if (value === '') return ''
if (!value.match(/^ /)) value = '+ ' + value
return value + ' GDD'
}

View File

@ -0,0 +1,37 @@
import { loadFilters } from './amount'
const i18nMock = {
n: jest.fn((n) => n),
}
const { amount, GDD } = loadFilters(i18nMock)
describe('amount filters', () => {
describe('amount', () => {
it('returns empty string when called with null', () => {
expect(amount(null)).toBe('')
})
it('returns 0 when called with 0', () => {
expect(amount(0)).toBe('0')
})
it('returns a leading proper minus sign when called with negative value', () => {
expect(amount(-1)).toBe(' 1')
})
})
describe('GDD', () => {
it('returns empty string when called with null', () => {
expect(GDD(null)).toBe('')
})
it('returns "+ 0 GDD" when called with 0', () => {
expect(GDD(0)).toBe('+ 0 GDD')
})
it('returns a leading proper minus sign when called with negative value', () => {
expect(GDD(-1)).toBe(' 1 GDD')
})
})
})

View File

@ -37,9 +37,9 @@
"months": "Monate",
"noDecay": "Keine Vergänglichkeit",
"past_time": "Vergangene Zeit",
"received": "Empfangen",
"receive": "Empfangen",
"seconds": "Sekunden",
"sent": "Gesendet",
"send": "Gesendet",
"since_introduction": "seit Einführung der Vergänglichkeit",
"Starting_block_decay": "Startblock Vergänglichkeit",
"toCommunity": "An die Gemeinschaft",

View File

@ -37,9 +37,9 @@
"months": "Months",
"noDecay": "No Decay",
"past_time": "Time passed",
"received": "Received",
"receive": "Received",
"seconds": "Seconds",
"sent": "Sent",
"send": "Sent",
"since_introduction": "Since the introduction of Decay",
"Starting_block_decay": "Starting Block Decay",
"toCommunity": "To the community",

View File

@ -4,6 +4,7 @@ import App from './App.vue'
import i18n from './i18n.js'
import { loadAllRules } from './validation-rules'
import { toasters } from './mixins/toaster'
import { loadFilters } from './filters/amount'
import 'regenerator-runtime'
@ -20,6 +21,9 @@ Vue.use(DashboardPlugin)
Vue.config.productionTip = false
Vue.mixin(toasters)
const filters = loadFilters(i18n)
Vue.filter('amount', filters.amount)
Vue.filter('GDD', filters.GDD)
loadAllRules(i18n)

View File

@ -17,7 +17,9 @@ import { focus } from 'vue-focus'
import { loadAllRules } from '../src/validation-rules'
import { toasters } from '../src/mixins/toaster'
import { loadFilters } from '@/filters/amount'
import { toasters } from '@/mixins/toaster'
export const toastErrorSpy = jest.spyOn(toasters.methods, 'toastError')
export const toastSuccessSpy = jest.spyOn(toasters.methods, 'toastSuccess')
@ -53,6 +55,10 @@ global.localVue.directive('focus', focus)
global.localVue.mixin(toasters)
const filters = loadFilters(i18nMock)
global.localVue.filter('amount', filters.amount)
global.localVue.filter('GDD', filters.GDD)
// Filter the warnings for portal vue
// https://github.com/BeniRupp/bug_portal-vue-target-already-exists
const consoleWarn = global.console.warn