mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'query-transaction-link' into Elweyn/issue1558
This commit is contained in:
commit
6ce7a59956
@ -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',
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
14
backend/src/graphql/arg/TransactionLinkArgs.ts
Normal file
14
backend/src/graphql/arg/TransactionLinkArgs.ts
Normal 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
|
||||
}
|
||||
50
backend/src/graphql/model/TransactionLink.ts
Normal file
50
backend/src/graphql/model/TransactionLink.ts
Normal 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
|
||||
}
|
||||
14
backend/src/graphql/resolver/TransactionLinkResolver.test.ts
Normal file
14
backend/src/graphql/resolver/TransactionLinkResolver.test.ts
Normal 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))
|
||||
})
|
||||
})
|
||||
74
backend/src/graphql/resolver/TransactionLinkResolver.ts
Normal file
74
backend/src/graphql/resolver/TransactionLinkResolver.ts
Normal 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))
|
||||
}
|
||||
}
|
||||
58
database/entity/0030-transaction_link/TransactionLink.ts
Normal file
58
database/entity/0030-transaction_link/TransactionLink.ts
Normal 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
|
||||
}
|
||||
1
database/entity/TransactionLink.ts
Normal file
1
database/entity/TransactionLink.ts
Normal file
@ -0,0 +1 @@
|
||||
export { TransactionLink } from './0030-transaction_link/TransactionLink'
|
||||
@ -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,
|
||||
]
|
||||
|
||||
26
database/migrations/0030-transaction_link.ts
Normal file
26
database/migrations/0030-transaction_link.ts
Normal 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\`;`)
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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(
|
||||
'+',
|
||||
)
|
||||
|
||||
@ -21,7 +21,6 @@ const mocks = {
|
||||
isAdmin: true,
|
||||
},
|
||||
},
|
||||
$n: jest.fn((n) => n),
|
||||
}
|
||||
|
||||
describe('Navbar', () => {
|
||||
|
||||
@ -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 }},
|
||||
|
||||
@ -7,7 +7,6 @@ const mocks = {
|
||||
$i18n: {
|
||||
locale: 'en',
|
||||
},
|
||||
$n: jest.fn((n) => n),
|
||||
$t: jest.fn((t) => t),
|
||||
$d: jest.fn((d) => d),
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -7,7 +7,6 @@ const mocks = {
|
||||
$i18n: {
|
||||
locale: 'en',
|
||||
},
|
||||
$n: jest.fn((n) => n),
|
||||
$t: jest.fn((t) => t),
|
||||
$d: jest.fn((d) => d),
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -7,7 +7,6 @@ const mocks = {
|
||||
$i18n: {
|
||||
locale: 'en',
|
||||
},
|
||||
$n: jest.fn((n) => n),
|
||||
$t: jest.fn((t) => t),
|
||||
$d: jest.fn((d) => d),
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -7,7 +7,6 @@ const mocks = {
|
||||
$i18n: {
|
||||
locale: 'en',
|
||||
},
|
||||
$n: jest.fn((n) => n),
|
||||
$t: jest.fn((t) => t),
|
||||
$d: jest.fn((d) => d),
|
||||
}
|
||||
|
||||
@ -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"
|
||||
|
||||
18
frontend/src/filters/amount.js
Normal file
18
frontend/src/filters/amount.js
Normal 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'
|
||||
}
|
||||
37
frontend/src/filters/amount.test.js
Normal file
37
frontend/src/filters/amount.test.js
Normal 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')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user