Merge branch 'master' into update-typeorm

This commit is contained in:
Moriz Wahl 2023-06-29 12:28:05 +02:00 committed by GitHub
commit 3ed9adfc73
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
70 changed files with 1741 additions and 454 deletions

View File

@ -73,8 +73,8 @@ describe('ContributionMessagesFormular', () => {
)
})
it('emitted "update-state" with data', async () => {
expect(wrapper.emitted('update-state')).toEqual(
it('emitted "update-status" with data', async () => {
expect(wrapper.emitted('update-status')).toEqual(
expect.arrayContaining([expect.arrayContaining([42])]),
)
})

View File

@ -54,7 +54,7 @@ export default {
})
.then((result) => {
this.$emit('get-list-contribution-messages', this.contributionId)
this.$emit('update-state', this.contributionId)
this.$emit('update-status', this.contributionId)
this.form.text = ''
this.toastSuccess(this.$t('message.request'))
this.loading = false

View File

@ -10,7 +10,7 @@ describe('ContributionMessagesList', () => {
const propsData = {
contributionId: 42,
contributionState: 'PENDING',
contributionStatus: 'PENDING',
}
const mocks = {

View File

@ -6,11 +6,11 @@
</div>
</b-container>
<div v-if="contributionState === 'PENDING' || contributionState === 'IN_PROGRESS'">
<div v-if="contributionStatus === 'PENDING' || contributionStatus === 'IN_PROGRESS'">
<contribution-messages-formular
:contributionId="contributionId"
@get-list-contribution-messages="getListContributionMessages"
@update-state="updateState"
@update-status="updateStatus"
/>
</div>
</div>
@ -31,7 +31,7 @@ export default {
type: Number,
required: true,
},
contributionState: {
contributionStatus: {
type: String,
required: true,
},
@ -58,8 +58,8 @@ export default {
this.toastError(error.message)
})
},
updateState(id) {
this.$emit('update-state', id)
updateStatus(id) {
this.$emit('update-status', id)
},
},
created() {

View File

@ -18,7 +18,7 @@ describe('ContributionMessagesListItem', () => {
describe('if message author has moderator role', () => {
const propsData = {
contributionId: 42,
state: 'PENDING',
status: 'PENDING',
message: {
id: 111,
message: 'Lorem ipsum?',
@ -79,7 +79,7 @@ describe('ContributionMessagesListItem', () => {
describe('if message author does not have moderator role', () => {
const propsData = {
contributionId: 42,
state: 'PENDING',
status: 'PENDING',
message: {
id: 113,
message: 'Asda sdad ad asdasd, das Ass das Das. ',

View File

@ -28,7 +28,7 @@ const defaultData = () => {
memo: 'Danke für alles',
date: new Date(),
moderator: 1,
state: 'PENDING',
status: 'PENDING',
creation: [500, 500, 500],
messagesCount: 0,
deniedBy: null,
@ -50,7 +50,7 @@ const defaultData = () => {
memo: 'Gut Ergattert',
date: new Date(),
moderator: 1,
state: 'PENDING',
status: 'PENDING',
creation: [500, 500, 500],
messagesCount: 0,
deniedBy: null,

View File

@ -34,8 +34,8 @@
{{ $t('help.transactionlist.confirmed') }}
</div>
<div>
{{ $t('transactionlist.state') }} {{ $t('math.equals') }}
{{ $t('help.transactionlist.state') }}
{{ $t('transactionlist.status') }} {{ $t('math.equals') }}
{{ $t('help.transactionlist.status') }}
</div>
</b-collapse>
</div>
@ -78,8 +78,8 @@ export default {
},
},
{
key: 'state',
label: this.$t('transactionlist.state'),
key: 'status',
label: this.$t('transactionlist.status'),
},
{
key: 'amount',

View File

@ -131,13 +131,13 @@ describe('OpenCreationsTable', () => {
})
})
describe('call updateState', () => {
describe('call updateStatus', () => {
beforeEach(() => {
wrapper.vm.updateState(4)
wrapper.vm.updateStatus(4)
})
it('emits update-state', () => {
expect(wrapper.vm.$root.$emit('update-state', 4)).toBeTruthy()
it('emits update-status', () => {
expect(wrapper.vm.$root.$emit('update-status', 4)).toBeTruthy()
})
})
})

View File

@ -9,8 +9,8 @@
stacked="md"
:tbody-tr-class="rowClass"
>
<template #cell(state)="row">
<b-icon :icon="getStatusIcon(row.item.state)"></b-icon>
<template #cell(status)="row">
<b-icon :icon="getStatusIcon(row.item.status)"></b-icon>
</template>
<template #cell(bookmark)="row">
<div v-if="!myself(row.item)">
@ -39,12 +39,12 @@
<b-button v-else @click="rowToggleDetails(row, 0)">
<b-icon icon="chat-dots"></b-icon>
<b-icon
v-if="row.item.state === 'PENDING' && row.item.messagesCount > 0"
v-if="row.item.status === 'PENDING' && row.item.messagesCount > 0"
icon="exclamation-circle-fill"
variant="warning"
></b-icon>
<b-icon
v-if="row.item.state === 'IN_PROGRESS' && row.item.messagesCount > 0"
v-if="row.item.status === 'IN_PROGRESS' && row.item.messagesCount > 0"
icon="question-diamond"
variant="warning"
class="pl-1"
@ -102,8 +102,8 @@
<div v-else>
<contribution-messages-list
:contributionId="row.item.id"
:contributionState="row.item.state"
@update-state="updateState"
:contributionStatus="row.item.status"
@update-status="updateStatus"
/>
</div>
</template>
@ -154,15 +154,21 @@ export default {
},
rowClass(item, type) {
if (!item || type !== 'row') return
if (item.state === 'CONFIRMED') return 'table-success'
if (item.state === 'DENIED') return 'table-warning'
if (item.state === 'DELETED') return 'table-danger'
if (item.state === 'IN_PROGRESS') return 'table-primary'
if (item.state === 'PENDING') return 'table-primary'
if (item.status === 'CONFIRMED') return 'table-success'
if (item.status === 'DENIED') return 'table-warning'
if (item.status === 'DELETED') return 'table-danger'
if (item.status === 'IN_PROGRESS') return 'table-primary'
if (item.status === 'PENDING') return 'table-primary'
},
updateState(id) {
this.$emit('update-state', id)
updateStatus(id) {
this.$emit('update-status', id)
},
},
}
</script>
<style>
.btn-warning {
background-color: #e1a908;
border-color: #e1a908;
}
</style>

View File

@ -26,7 +26,7 @@ export const adminListContributions = gql`
contributionDate
confirmedAt
confirmedBy
state
status
messagesCount
deniedAt
deniedBy

View File

@ -94,7 +94,7 @@
"transactionlist": {
"confirmed": "Wann wurde es von einem Moderator / Admin bestätigt.",
"periods": "Für welchen Zeitraum wurde vom Mitglied eingereicht.",
"state": "[PENDING = eingereicht, DELETED = gelöscht, IN_PROGRESS = im Dialog mit Moderator, DENIED = abgelehnt, CONFIRMED = bestätigt]",
"status": "[PENDING = eingereicht, DELETED = gelöscht, IN_PROGRESS = im Dialog mit Moderator, DENIED = abgelehnt, CONFIRMED = bestätigt]",
"submitted": "Wann wurde es vom Mitglied eingereicht"
}
},
@ -184,7 +184,7 @@
"confirmed": "Bestätigt",
"memo": "Nachricht",
"period": "Zeitraum",
"state": "Status",
"status": "Status",
"submitted": "Eingereicht",
"title": "Alle geschöpften Transaktionen für den Nutzer"
},

View File

@ -94,7 +94,7 @@
"transactionlist": {
"confirmed": "When was it confirmed by a moderator / admin.",
"periods": "For what period was it submitted by the member.",
"state": "[PENDING = submitted, DELETED = deleted, IN_PROGRESS = in dialogue with moderator, DENIED = rejected, CONFIRMED = confirmed]",
"status": "[PENDING = submitted, DELETED = deleted, IN_PROGRESS = in dialogue with moderator, DENIED = rejected, CONFIRMED = confirmed]",
"submitted": "When was it submitted by the member"
}
},
@ -184,7 +184,7 @@
"confirmed": "Confirmed",
"memo": "Message",
"period": "Period",
"state": "State",
"status": "State",
"submitted": "Submitted",
"title": "All creation-transactions for the user"
},

View File

@ -51,7 +51,7 @@ const defaultData = () => {
memo: 'Danke für alles',
date: new Date(),
moderator: 1,
state: 'PENDING',
status: 'PENDING',
creation: [500, 500, 500],
messagesCount: 0,
deniedBy: null,
@ -73,7 +73,7 @@ const defaultData = () => {
memo: 'Gut Ergattert',
date: new Date(),
moderator: 1,
state: 'PENDING',
status: 'PENDING',
creation: [500, 500, 500],
messagesCount: 0,
deniedBy: null,
@ -451,12 +451,12 @@ describe('CreationConfirm', () => {
describe('update status', () => {
beforeEach(async () => {
await wrapper.findComponent({ name: 'OpenCreationsTable' }).vm.$emit('update-state', 2)
await wrapper.findComponent({ name: 'OpenCreationsTable' }).vm.$emit('update-status', 2)
})
it('updates the status', () => {
expect(wrapper.vm.items.find((obj) => obj.id === 2).messagesCount).toBe(1)
expect(wrapper.vm.items.find((obj) => obj.id === 2).state).toBe('IN_PROGRESS')
expect(wrapper.vm.items.find((obj) => obj.id === 2).status).toBe('IN_PROGRESS')
})
})

View File

@ -43,7 +43,7 @@
:items="items"
:fields="fields"
@show-overlay="showOverlay"
@update-state="updateStatus"
@update-status="updateStatus"
@update-contributions="$apollo.queries.ListAllContributions.refetch()"
/>
@ -187,7 +187,7 @@ export default {
},
updateStatus(id) {
this.items.find((obj) => obj.id === id).messagesCount++
this.items.find((obj) => obj.id === id).state = 'IN_PROGRESS'
this.items.find((obj) => obj.id === id).status = 'IN_PROGRESS'
},
formatDateOrDash(value) {
return value ? this.$d(new Date(value), 'short') : '—'
@ -331,7 +331,7 @@ export default {
],
[
// all contributions
{ key: 'state', label: this.$t('status') },
{ key: 'status', label: this.$t('status') },
{ key: 'firstName', label: this.$t('firstname') },
{ key: 'lastName', label: this.$t('lastname') },
{

View File

@ -43,7 +43,7 @@ const defaultData = () => {
memo: 'Danke für alles',
date: new Date(),
moderatorId: 1,
state: 'PENDING',
status: 'PENDING',
creation: [500, 500, 500],
messagesCount: 0,
deniedBy: null,
@ -65,7 +65,7 @@ const defaultData = () => {
memo: 'Gut Ergattert',
date: new Date(),
moderatorId: 1,
state: 'PENDING',
status: 'PENDING',
creation: [500, 500, 500],
messagesCount: 0,
deniedBy: null,

View File

@ -197,6 +197,9 @@ module.exports = {
{
files: ['*.test.ts'],
plugins: ['jest'],
env: {
jest: true,
},
rules: {
'jest/no-disabled-tests': 'error',
'jest/no-focused-tests': 'error',

View File

@ -53,4 +53,5 @@ export enum RIGHTS {
ADMIN_CREATE_CONTRIBUTION_MESSAGE = 'ADMIN_CREATE_CONTRIBUTION_MESSAGE',
DENY_CONTRIBUTION = 'DENY_CONTRIBUTION',
ADMIN_OPEN_CREATIONS = 'ADMIN_OPEN_CREATIONS',
ADMIN_LIST_ALL_CONTRIBUTION_MESSAGES = 'ADMIN_LIST_ALL_CONTRIBUTION_MESSAGES',
}

View File

@ -1,5 +1,7 @@
import { ArgsType, Field, Int, InputType } from 'type-graphql'
import { ContributionMessageType } from '@enum/ContributionMessageType'
@InputType()
@ArgsType()
export class ContributionMessageArgs {
@ -8,4 +10,7 @@ export class ContributionMessageArgs {
@Field(() => String)
message: string
@Field(() => ContributionMessageType, { defaultValue: ContributionMessageType.DIALOG })
messageType: ContributionMessageType
}

View File

@ -3,6 +3,7 @@ import { registerEnumType } from 'type-graphql'
export enum ContributionMessageType {
HISTORY = 'HISTORY',
DIALOG = 'DIALOG',
MODERATOR = 'MODERATOR', // messages for moderator communication, can only be seen by moderators
}
registerEnumType(ContributionMessageType, {

View File

@ -15,7 +15,7 @@ export class Contribution {
this.confirmedAt = contribution.confirmedAt
this.confirmedBy = contribution.confirmedBy
this.contributionDate = contribution.contributionDate
this.state = contribution.contributionStatus
this.status = contribution.contributionStatus
this.messagesCount = contribution.messages ? contribution.messages.length : 0
this.deniedAt = contribution.deniedAt
this.deniedBy = contribution.deniedBy
@ -68,7 +68,7 @@ export class Contribution {
messagesCount: number
@Field(() => String)
state: string
status: string
@Field(() => Int, { nullable: true })
moderatorId: number | null

View File

@ -1,25 +1,39 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field, Float, Int } from 'type-graphql'
import { GdtEntryType } from '@enum/GdtEntryType'
@ObjectType()
export class GdtEntry {
constructor(json: any) {
this.id = json.id
this.amount = json.amount
this.date = json.date
this.email = json.email
this.comment = json.comment
this.couponCode = json.coupon_code
this.gdtEntryType = json.gdt_entry_type_id
this.factor = json.factor
this.amount2 = json.amount2
this.factor2 = json.factor2
this.gdt = json.gdt
constructor({
id,
amount,
date,
email,
comment,
// eslint-disable-next-line camelcase
coupon_code,
// eslint-disable-next-line camelcase
gdt_entry_type_id,
factor,
amount2,
factor2,
gdt,
}: any) {
this.id = id
this.amount = amount
this.date = date
this.email = email
this.comment = comment
// eslint-disable-next-line camelcase
this.couponCode = coupon_code
// eslint-disable-next-line camelcase
this.gdtEntryType = gdt_entry_type_id
this.factor = factor
this.amount2 = amount2
this.factor2 = factor2
this.gdt = gdt
}
@Field(() => Int)

View File

@ -1,24 +1,19 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field, Int, Float } from 'type-graphql'
import { GdtEntry } from './GdtEntry'
@ObjectType()
export class GdtEntryList {
constructor(json: any) {
this.state = json.state
this.count = json.count
this.gdtEntries = json.gdtEntries ? json.gdtEntries.map((json: any) => new GdtEntry(json)) : []
this.gdtSum = json.gdtSum
this.timeUsed = json.timeUsed
constructor(status = '', count = 0, gdtEntries = [], gdtSum = 0, timeUsed = 0) {
this.status = status
this.count = count
this.gdtEntries = gdtEntries
this.gdtSum = gdtSum
this.timeUsed = timeUsed
}
@Field(() => String)
state: string
status: string
@Field(() => Int)
count: number

View File

@ -16,7 +16,7 @@ export class UnconfirmedContribution {
this.email = user ? user.emailContact.email : ''
this.moderator = contribution.moderatorId
this.creation = creations
this.state = contribution.contributionStatus
this.status = contribution.contributionStatus
this.messageCount = contribution.messages ? contribution.messages.length : 0
}
@ -51,7 +51,7 @@ export class UnconfirmedContribution {
creation: Decimal[]
@Field(() => String)
state: string
status: string
@Field(() => Int)
messageCount: number

View File

@ -20,7 +20,7 @@ import {
createContributionMessage,
login,
} from '@/seeds/graphql/mutations'
import { listContributionMessages } from '@/seeds/graphql/queries'
import { listContributionMessages, adminListContributionMessages } from '@/seeds/graphql/queries'
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
import { peterLustig } from '@/seeds/users/peter-lustig'
@ -217,6 +217,33 @@ describe('ContributionMessageResolver', () => {
)
})
})
describe('contribution message type MODERATOR', () => {
it('creates ContributionMessage', async () => {
await expect(
mutate({
mutation: adminCreateContributionMessage,
variables: {
contributionId: result.data.createContribution.id,
message: 'Internal moderator communication',
messageType: 'MODERATOR',
},
}),
).resolves.toEqual(
expect.objectContaining({
data: {
adminCreateContributionMessage: expect.objectContaining({
id: expect.any(Number),
message: 'Internal moderator communication',
type: 'MODERATOR',
userFirstName: 'Peter',
userLastName: 'Lustig',
}),
},
}),
)
})
})
})
})
@ -385,7 +412,7 @@ describe('ContributionMessageResolver', () => {
resetToken()
})
it('returns a list of contributionmessages', async () => {
it('returns a list of contributionmessages without type MODERATOR', async () => {
await expect(
mutate({
mutation: listContributionMessages,
@ -419,4 +446,96 @@ describe('ContributionMessageResolver', () => {
})
})
})
describe('adminListContributionMessages', () => {
describe('unauthenticated', () => {
it('returns an error', async () => {
await expect(
mutate({
mutation: adminListContributionMessages,
variables: { contributionId: 1 },
}),
).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('401 Unauthorized')],
}),
)
})
})
describe('authenticated as user', () => {
beforeAll(async () => {
await mutate({
mutation: login,
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
})
})
it('returns an error', async () => {
await expect(
mutate({
mutation: adminListContributionMessages,
variables: { contributionId: 1 },
}),
).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('401 Unauthorized')],
}),
)
})
})
describe('authenticated as admin', () => {
beforeAll(async () => {
await mutate({
mutation: login,
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
})
})
afterAll(() => {
resetToken()
})
it('returns a list of contributionmessages with type MODERATOR', async () => {
await expect(
mutate({
mutation: adminListContributionMessages,
variables: { contributionId: result.data.createContribution.id },
}),
).resolves.toEqual(
expect.objectContaining({
data: {
adminListContributionMessages: {
count: 3,
messages: expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
message: 'Admin Test',
type: 'DIALOG',
userFirstName: 'Peter',
userLastName: 'Lustig',
}),
expect.objectContaining({
id: expect.any(Number),
message: 'User Test',
type: 'DIALOG',
userFirstName: 'Bibi',
userLastName: 'Bloxberg',
}),
expect.objectContaining({
id: expect.any(Number),
message: 'Internal moderator communication',
type: 'MODERATOR',
userFirstName: 'Peter',
userLastName: 'Lustig',
}),
]),
},
},
}),
)
})
})
})
})

View File

@ -8,8 +8,8 @@ import { Arg, Args, Authorized, Ctx, Int, Mutation, Query, Resolver } from 'type
import { ContributionMessageArgs } from '@arg/ContributionMessageArgs'
import { Paginated } from '@arg/Paginated'
import { ContributionMessageType } from '@enum/ContributionMessageType'
import { ContributionStatus } from '@enum/ContributionStatus'
import { ContributionMessageType } from '@enum/MessageType'
import { Order } from '@enum/Order'
import { ContributionMessage, ContributionMessageListResult } from '@model/ContributionMessage'
@ -22,6 +22,8 @@ import {
import { Context, getUser } from '@/server/context'
import { LogError } from '@/server/LogError'
import { findContributionMessages } from './util/findContributionMessages'
@Resolver()
export class ContributionMessageResolver {
@Authorized([RIGHTS.CREATE_CONTRIBUTION_MESSAGE])
@ -82,16 +84,35 @@ export class ContributionMessageResolver {
@Args()
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
): Promise<ContributionMessageListResult> {
const [contributionMessages, count] = await getConnection()
.createQueryBuilder()
.select('cm')
.from(DbContributionMessage, 'cm')
.leftJoinAndSelect('cm.user', 'u')
.where({ contributionId })
.orderBy('cm.createdAt', order)
.limit(pageSize)
.offset((currentPage - 1) * pageSize)
.getManyAndCount()
const [contributionMessages, count] = await findContributionMessages({
contributionId,
currentPage,
pageSize,
order,
})
return {
count,
messages: contributionMessages.map(
(message) => new ContributionMessage(message, message.user),
),
}
}
@Authorized([RIGHTS.ADMIN_LIST_ALL_CONTRIBUTION_MESSAGES])
@Query(() => ContributionMessageListResult)
async adminListContributionMessages(
@Arg('contributionId', () => Int) contributionId: number,
@Args()
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
): Promise<ContributionMessageListResult> {
const [contributionMessages, count] = await findContributionMessages({
contributionId,
currentPage,
pageSize,
order,
showModeratorType: true,
})
return {
count,
@ -104,7 +125,7 @@ export class ContributionMessageResolver {
@Authorized([RIGHTS.ADMIN_CREATE_CONTRIBUTION_MESSAGE])
@Mutation(() => ContributionMessage)
async adminCreateContributionMessage(
@Args() { contributionId, message }: ContributionMessageArgs,
@Args() { contributionId, message, messageType }: ContributionMessageArgs,
@Ctx() context: Context,
): Promise<ContributionMessage> {
const moderator = getUser(context)
@ -133,7 +154,7 @@ export class ContributionMessageResolver {
contributionMessage.createdAt = new Date()
contributionMessage.message = message
contributionMessage.userId = moderator.id
contributionMessage.type = ContributionMessageType.DIALOG
contributionMessage.type = messageType
contributionMessage.isModerator = true
await queryRunner.manager.insert(DbContributionMessage, contributionMessage)

View File

@ -1092,29 +1092,29 @@ describe('ContributionResolver', () => {
contributionCount: 4,
contributionList: expect.arrayContaining([
expect.not.objectContaining({
state: 'CONFIRMED',
status: 'CONFIRMED',
}),
expect.objectContaining({
id: pendingContribution.data.createContribution.id,
state: 'PENDING',
status: 'PENDING',
memo: 'Test PENDING contribution update',
amount: '10',
}),
expect.objectContaining({
id: contributionToDeny.data.createContribution.id,
state: 'DENIED',
status: 'DENIED',
memo: 'Test contribution to deny',
amount: '100',
}),
expect.objectContaining({
id: contributionToDelete.data.createContribution.id,
state: 'DELETED',
status: 'DELETED',
memo: 'Test contribution to delete',
amount: '100',
}),
expect.objectContaining({
id: inProgressContribution.data.createContribution.id,
state: 'IN_PROGRESS',
status: 'IN_PROGRESS',
memo: 'Test IN_PROGRESS contribution',
amount: '100',
}),
@ -1223,47 +1223,47 @@ describe('ContributionResolver', () => {
contributionCount: 7,
contributionList: expect.arrayContaining([
expect.not.objectContaining({
state: 'DELETED',
status: 'DELETED',
}),
expect.objectContaining({
amount: '100',
state: 'CONFIRMED',
status: 'CONFIRMED',
id: contributionToConfirm.data.createContribution.id,
memo: 'Test contribution to confirm',
}),
expect.objectContaining({
id: pendingContribution.data.createContribution.id,
state: 'PENDING',
status: 'PENDING',
memo: 'Test PENDING contribution update',
amount: '10',
}),
expect.objectContaining({
id: contributionToDeny.data.createContribution.id,
state: 'DENIED',
status: 'DENIED',
memo: 'Test contribution to deny',
amount: '100',
}),
expect.objectContaining({
id: inProgressContribution.data.createContribution.id,
state: 'IN_PROGRESS',
status: 'IN_PROGRESS',
memo: 'Test IN_PROGRESS contribution',
amount: '100',
}),
expect.objectContaining({
id: bibiCreatedContribution.id,
state: 'CONFIRMED',
status: 'CONFIRMED',
memo: 'Herzlich Willkommen bei Gradido!',
amount: '1000',
}),
expect.objectContaining({
id: expect.any(Number),
state: 'CONFIRMED',
status: 'CONFIRMED',
memo: 'Whatever contribution',
amount: '166',
}),
expect.objectContaining({
id: expect.any(Number),
state: 'DENIED',
status: 'DENIED',
memo: 'Whatever contribution',
amount: '166',
}),
@ -1288,47 +1288,47 @@ describe('ContributionResolver', () => {
contributionCount: 7,
contributionList: expect.arrayContaining([
expect.not.objectContaining({
state: 'DELETED',
status: 'DELETED',
}),
expect.objectContaining({
amount: '100',
state: 'CONFIRMED',
status: 'CONFIRMED',
id: contributionToConfirm.data.createContribution.id,
memo: 'Test contribution to confirm',
}),
expect.objectContaining({
id: pendingContribution.data.createContribution.id,
state: 'PENDING',
status: 'PENDING',
memo: 'Test PENDING contribution update',
amount: '10',
}),
expect.objectContaining({
id: contributionToDeny.data.createContribution.id,
state: 'DENIED',
status: 'DENIED',
memo: 'Test contribution to deny',
amount: '100',
}),
expect.objectContaining({
id: inProgressContribution.data.createContribution.id,
state: 'IN_PROGRESS',
status: 'IN_PROGRESS',
memo: 'Test IN_PROGRESS contribution',
amount: '100',
}),
expect.objectContaining({
id: bibiCreatedContribution.id,
state: 'CONFIRMED',
status: 'CONFIRMED',
memo: 'Herzlich Willkommen bei Gradido!',
amount: '1000',
}),
expect.objectContaining({
id: expect.any(Number),
state: 'CONFIRMED',
status: 'CONFIRMED',
memo: 'Whatever contribution',
amount: '166',
}),
expect.objectContaining({
id: expect.any(Number),
state: 'DENIED',
status: 'DENIED',
memo: 'Whatever contribution',
amount: '166',
}),
@ -1353,47 +1353,47 @@ describe('ContributionResolver', () => {
contributionCount: 7,
contributionList: expect.arrayContaining([
expect.not.objectContaining({
state: 'DELETED',
status: 'DELETED',
}),
expect.objectContaining({
amount: '100',
state: 'CONFIRMED',
status: 'CONFIRMED',
id: contributionToConfirm.data.createContribution.id,
memo: 'Test contribution to confirm',
}),
expect.objectContaining({
id: pendingContribution.data.createContribution.id,
state: 'PENDING',
status: 'PENDING',
memo: 'Test PENDING contribution update',
amount: '10',
}),
expect.objectContaining({
id: contributionToDeny.data.createContribution.id,
state: 'DENIED',
status: 'DENIED',
memo: 'Test contribution to deny',
amount: '100',
}),
expect.objectContaining({
id: inProgressContribution.data.createContribution.id,
state: 'IN_PROGRESS',
status: 'IN_PROGRESS',
memo: 'Test IN_PROGRESS contribution',
amount: '100',
}),
expect.objectContaining({
id: bibiCreatedContribution.id,
state: 'CONFIRMED',
status: 'CONFIRMED',
memo: 'Herzlich Willkommen bei Gradido!',
amount: '1000',
}),
expect.objectContaining({
id: expect.any(Number),
state: 'CONFIRMED',
status: 'CONFIRMED',
memo: 'Whatever contribution',
amount: '166',
}),
expect.objectContaining({
id: expect.any(Number),
state: 'DENIED',
status: 'DENIED',
memo: 'Whatever contribution',
amount: '166',
}),
@ -1419,33 +1419,33 @@ describe('ContributionResolver', () => {
contributionList: expect.arrayContaining([
expect.objectContaining({
amount: '100',
state: 'CONFIRMED',
status: 'CONFIRMED',
id: contributionToConfirm.data.createContribution.id,
memo: 'Test contribution to confirm',
}),
expect.objectContaining({
id: bibiCreatedContribution.id,
state: 'CONFIRMED',
status: 'CONFIRMED',
memo: 'Herzlich Willkommen bei Gradido!',
amount: '1000',
}),
expect.objectContaining({
id: expect.any(Number),
state: 'CONFIRMED',
status: 'CONFIRMED',
memo: 'Whatever contribution',
amount: '166',
}),
expect.not.objectContaining({
state: 'PENDING',
status: 'PENDING',
}),
expect.not.objectContaining({
state: 'DENIED',
status: 'DENIED',
}),
expect.not.objectContaining({
state: 'DELETED',
status: 'DELETED',
}),
expect.not.objectContaining({
state: 'IN_PROGRESS',
status: 'IN_PROGRESS',
}),
]),
})
@ -1468,20 +1468,20 @@ describe('ContributionResolver', () => {
contributionCount: 1,
contributionList: expect.arrayContaining([
expect.not.objectContaining({
state: 'CONFIRMED',
status: 'CONFIRMED',
}),
expect.not.objectContaining({
state: 'DENIED',
status: 'DENIED',
}),
expect.not.objectContaining({
state: 'DELETED',
status: 'DELETED',
}),
expect.not.objectContaining({
state: 'IN_PROGRESS',
status: 'IN_PROGRESS',
}),
expect.objectContaining({
id: pendingContribution.data.createContribution.id,
state: 'PENDING',
status: 'PENDING',
memo: 'Test PENDING contribution update',
amount: '10',
}),
@ -1506,20 +1506,20 @@ describe('ContributionResolver', () => {
contributionCount: 1,
contributionList: expect.arrayContaining([
expect.not.objectContaining({
state: 'CONFIRMED',
status: 'CONFIRMED',
}),
expect.not.objectContaining({
state: 'PENDING',
status: 'PENDING',
}),
expect.not.objectContaining({
state: 'DENIED',
status: 'DENIED',
}),
expect.not.objectContaining({
state: 'DELETED',
status: 'DELETED',
}),
expect.objectContaining({
id: inProgressContribution.data.createContribution.id,
state: 'IN_PROGRESS',
status: 'IN_PROGRESS',
memo: 'Test IN_PROGRESS contribution',
amount: '100',
}),
@ -1545,27 +1545,27 @@ describe('ContributionResolver', () => {
contributionList: expect.arrayContaining([
expect.objectContaining({
id: contributionToDeny.data.createContribution.id,
state: 'DENIED',
status: 'DENIED',
memo: 'Test contribution to deny',
amount: '100',
}),
expect.objectContaining({
id: expect.any(Number),
state: 'DENIED',
status: 'DENIED',
memo: 'Whatever contribution',
amount: '166',
}),
expect.not.objectContaining({
state: 'CONFIRMED',
status: 'CONFIRMED',
}),
expect.not.objectContaining({
state: 'DELETED',
status: 'DELETED',
}),
expect.not.objectContaining({
state: 'IN_PROGRESS',
status: 'IN_PROGRESS',
}),
expect.not.objectContaining({
state: 'PENDING',
status: 'PENDING',
}),
]),
})
@ -1608,36 +1608,36 @@ describe('ContributionResolver', () => {
contributionList: expect.arrayContaining([
expect.objectContaining({
amount: '100',
state: 'CONFIRMED',
status: 'CONFIRMED',
id: contributionToConfirm.data.createContribution.id,
memo: 'Test contribution to confirm',
}),
expect.objectContaining({
id: pendingContribution.data.createContribution.id,
state: 'PENDING',
status: 'PENDING',
memo: 'Test PENDING contribution update',
amount: '10',
}),
expect.objectContaining({
id: bibiCreatedContribution.id,
state: 'CONFIRMED',
status: 'CONFIRMED',
memo: 'Herzlich Willkommen bei Gradido!',
amount: '1000',
}),
expect.objectContaining({
id: expect.any(Number),
state: 'CONFIRMED',
status: 'CONFIRMED',
memo: 'Whatever contribution',
amount: '166',
}),
expect.not.objectContaining({
state: 'DENIED',
status: 'DENIED',
}),
expect.not.objectContaining({
state: 'DELETED',
status: 'DELETED',
}),
expect.not.objectContaining({
state: 'IN_PROGRESS',
status: 'IN_PROGRESS',
}),
]),
})
@ -2691,7 +2691,7 @@ describe('ContributionResolver', () => {
lastName: 'Bloxberg',
memo: 'Herzlich Willkommen bei Gradido liebe Bibi!',
messagesCount: 0,
state: 'CONFIRMED',
status: 'CONFIRMED',
}),
expect.objectContaining({
amount: expect.decimalEqual(50),
@ -2700,7 +2700,7 @@ describe('ContributionResolver', () => {
lastName: 'Bloxberg',
memo: 'Herzlich Willkommen bei Gradido liebe Bibi!',
messagesCount: 0,
state: 'CONFIRMED',
status: 'CONFIRMED',
}),
expect.objectContaining({
amount: expect.decimalEqual(450),
@ -2709,7 +2709,7 @@ describe('ContributionResolver', () => {
lastName: 'Bloxberg',
memo: 'Herzlich Willkommen bei Gradido liebe Bibi!',
messagesCount: 0,
state: 'CONFIRMED',
status: 'CONFIRMED',
}),
expect.objectContaining({
amount: expect.decimalEqual(400),
@ -2718,7 +2718,7 @@ describe('ContributionResolver', () => {
lastName: 'Lustig',
memo: 'Herzlich Willkommen bei Gradido!',
messagesCount: 0,
state: 'PENDING',
status: 'PENDING',
}),
expect.objectContaining({
amount: expect.decimalEqual(100),
@ -2727,7 +2727,7 @@ describe('ContributionResolver', () => {
lastName: 'der Baumeister',
memo: 'Confirmed Contribution',
messagesCount: 0,
state: 'CONFIRMED',
status: 'CONFIRMED',
}),
expect.objectContaining({
amount: expect.decimalEqual(100),
@ -2736,7 +2736,7 @@ describe('ContributionResolver', () => {
lastName: 'Lustig',
memo: 'Test env contribution',
messagesCount: 0,
state: 'PENDING',
status: 'PENDING',
}),
expect.objectContaining({
amount: expect.decimalEqual(200),
@ -2745,7 +2745,7 @@ describe('ContributionResolver', () => {
lastName: 'Bloxberg',
memo: 'Aktives Grundeinkommen',
messagesCount: 0,
state: 'PENDING',
status: 'PENDING',
}),
expect.objectContaining({
amount: expect.decimalEqual(200),
@ -2754,7 +2754,7 @@ describe('ContributionResolver', () => {
lastName: 'Lustig',
memo: 'Das war leider zu Viel!',
messagesCount: 0,
state: 'DELETED',
status: 'DELETED',
}),
expect.objectContaining({
amount: expect.decimalEqual(166),
@ -2763,7 +2763,7 @@ describe('ContributionResolver', () => {
lastName: 'Hotzenplotz',
memo: 'Whatever contribution',
messagesCount: 0,
state: 'DENIED',
status: 'DENIED',
}),
expect.objectContaining({
amount: expect.decimalEqual(166),
@ -2772,7 +2772,7 @@ describe('ContributionResolver', () => {
lastName: 'Hotzenplotz',
memo: 'Whatever contribution',
messagesCount: 0,
state: 'DELETED',
status: 'DELETED',
}),
expect.objectContaining({
amount: expect.decimalEqual(166),
@ -2781,7 +2781,7 @@ describe('ContributionResolver', () => {
lastName: 'Hotzenplotz',
memo: 'Whatever contribution',
messagesCount: 0,
state: 'CONFIRMED',
status: 'CONFIRMED',
}),
expect.objectContaining({
amount: expect.decimalEqual(100),
@ -2790,7 +2790,7 @@ describe('ContributionResolver', () => {
lastName: 'Bloxberg',
memo: 'Test contribution to delete',
messagesCount: 0,
state: 'DELETED',
status: 'DELETED',
}),
expect.objectContaining({
amount: expect.decimalEqual(100),
@ -2799,7 +2799,7 @@ describe('ContributionResolver', () => {
lastName: 'Bloxberg',
memo: 'Test contribution to deny',
messagesCount: 0,
state: 'DENIED',
status: 'DENIED',
}),
expect.objectContaining({
amount: expect.decimalEqual(100),
@ -2808,7 +2808,7 @@ describe('ContributionResolver', () => {
lastName: 'Bloxberg',
memo: 'Test contribution to confirm',
messagesCount: 0,
state: 'CONFIRMED',
status: 'CONFIRMED',
}),
expect.objectContaining({
amount: expect.decimalEqual(100),
@ -2817,7 +2817,7 @@ describe('ContributionResolver', () => {
lastName: 'Bloxberg',
memo: 'Test IN_PROGRESS contribution',
messagesCount: 1,
state: 'IN_PROGRESS',
status: 'IN_PROGRESS',
}),
expect.objectContaining({
amount: expect.decimalEqual(10),
@ -2826,7 +2826,7 @@ describe('ContributionResolver', () => {
lastName: 'Bloxberg',
memo: 'Test PENDING contribution update',
messagesCount: 1,
state: 'PENDING',
status: 'PENDING',
}),
expect.objectContaining({
amount: expect.decimalEqual(1000),
@ -2835,7 +2835,7 @@ describe('ContributionResolver', () => {
lastName: 'Bloxberg',
memo: 'Herzlich Willkommen bei Gradido!',
messagesCount: 0,
state: 'CONFIRMED',
status: 'CONFIRMED',
}),
]),
})
@ -2864,7 +2864,7 @@ describe('ContributionResolver', () => {
lastName: 'Lustig',
memo: 'Herzlich Willkommen bei Gradido!',
messagesCount: 0,
state: 'PENDING',
status: 'PENDING',
}),
expect.objectContaining({
amount: '100',
@ -2873,19 +2873,19 @@ describe('ContributionResolver', () => {
lastName: 'Lustig',
memo: 'Test env contribution',
messagesCount: 0,
state: 'PENDING',
status: 'PENDING',
}),
expect.not.objectContaining({
state: 'DENIED',
status: 'DENIED',
}),
expect.not.objectContaining({
state: 'DELETED',
status: 'DELETED',
}),
expect.not.objectContaining({
state: 'CONFIRMED',
status: 'CONFIRMED',
}),
expect.not.objectContaining({
state: 'IN_PROGRESS',
status: 'IN_PROGRESS',
}),
]),
})

View File

@ -11,9 +11,9 @@ import { AdminCreateContributionArgs } from '@arg/AdminCreateContributionArgs'
import { AdminUpdateContributionArgs } from '@arg/AdminUpdateContributionArgs'
import { ContributionArgs } from '@arg/ContributionArgs'
import { Paginated } from '@arg/Paginated'
import { ContributionMessageType } from '@enum/ContributionMessageType'
import { ContributionStatus } from '@enum/ContributionStatus'
import { ContributionType } from '@enum/ContributionType'
import { ContributionMessageType } from '@enum/MessageType'
import { Order } from '@enum/Order'
import { TransactionTypeId } from '@enum/TransactionTypeId'
import { AdminUpdateContribution } from '@model/AdminUpdateContribution'

View File

@ -6,6 +6,7 @@ import { Resolver, Query, Args, Ctx, Authorized, Arg, Int, Float } from 'type-gr
import { Paginated } from '@arg/Paginated'
import { Order } from '@enum/Order'
import { GdtEntry } from '@model/GdtEntry'
import { GdtEntryList } from '@model/GdtEntryList'
import { apiGet, apiPost } from '@/apis/HttpRequest'
@ -31,9 +32,17 @@ export class GdtResolver {
`${CONFIG.GDT_API_URL}/GdtEntries/listPerEmailApi/${userEntity.emailContact.email}/${currentPage}/${pageSize}/${order}`,
)
if (!resultGDT.success) {
throw new LogError(resultGDT.data)
return new GdtEntryList()
}
return new GdtEntryList(resultGDT.data)
const { state, count, gdtEntries, gdtSum, timeUsed } = resultGDT.data
return new GdtEntryList(
state,
count,
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-explicit-any
gdtEntries ? gdtEntries.map((data: any) => new GdtEntry(data)) : [],
gdtSum,
timeUsed,
)
} catch (err) {
throw new LogError('GDT Server is not reachable')
}

View File

@ -352,7 +352,8 @@ export class UserResolver {
const user = await findUserByEmail(email).catch(() => {
logger.warn(`fail on find UserContact per ${email}`)
})
if (!user) {
if (!user || user.deletedAt) {
logger.warn(`no user found with ${email}`)
return true
}

View File

@ -0,0 +1,36 @@
import { In } from '@dbTools/typeorm'
import { ContributionMessage as DbContributionMessage } from '@entity/ContributionMessage'
import { ContributionMessageType } from '@enum/ContributionMessageType'
import { Order } from '@enum/Order'
interface FindContributionMessagesOptions {
contributionId: number
pageSize: number
currentPage: number
order: Order
showModeratorType?: boolean
}
export const findContributionMessages = async (
options: FindContributionMessagesOptions,
): Promise<[DbContributionMessage[], number]> => {
const { contributionId, pageSize, currentPage, order, showModeratorType } = options
const messageTypes = [ContributionMessageType.DIALOG, ContributionMessageType.HISTORY]
if (showModeratorType) messageTypes.push(ContributionMessageType.MODERATOR)
return DbContributionMessage.findAndCount({
where: {
contributionId,
type: In(messageTypes),
},
relations: ['user'],
order: {
createdAt: order,
},
skip: (currentPage - 1) * pageSize,
take: pageSize,
})
}

View File

@ -284,8 +284,12 @@ export const createContributionMessage = gql`
`
export const adminCreateContributionMessage = gql`
mutation ($contributionId: Int!, $message: String!) {
adminCreateContributionMessage(contributionId: $contributionId, message: $message) {
mutation ($contributionId: Int!, $message: String!, $messageType: ContributionMessageType) {
adminCreateContributionMessage(
contributionId: $contributionId
message: $message
messageType: $messageType
) {
id
message
createdAt

View File

@ -195,7 +195,7 @@ export const listContributions = gql`
confirmedAt
confirmedBy
deletedAt
state
status
messagesCount
deniedAt
deniedBy
@ -218,7 +218,7 @@ query ($currentPage: Int = 1, $pageSize: Int = 5, $order: Order = DESC, $statusF
confirmedAt
confirmedBy
contributionDate
state
status
messagesCount
deniedAt
deniedBy
@ -254,7 +254,7 @@ export const adminListContributions = gql`
confirmedAt
confirmedBy
contributionDate
state
status
messagesCount
deniedAt
deniedBy
@ -349,6 +349,29 @@ export const listContributionMessages = gql`
}
`
export const adminListContributionMessages = gql`
query ($contributionId: Int!, $pageSize: Int = 25, $currentPage: Int = 1, $order: Order = ASC) {
adminListContributionMessages(
contributionId: $contributionId
pageSize: $pageSize
currentPage: $currentPage
order: $order
) {
count
messages {
id
message
createdAt
updatedAt
type
userFirstName
userLastName
userId
}
}
}
`
export const user = gql`
query ($identifier: String!) {
user(identifier: $identifier) {

View File

@ -234,7 +234,8 @@ crontab -l
This show all existing entries of the crontab for user `gradido`
To install/add the cronjob for a daily backup at 3:00am please
To install/add the cronjob for a daily backup at 3:00am please,
To install/add the cronjob for a daily klicktipp export at 4:00am please,
Run:
@ -244,4 +245,5 @@ crontab -e
and insert the following line
```bash
0 3 * * * ~/gradido/deployment/bare_metal/backup.sh
0 4 * * * cd ~/gradido/backend/ && yarn klicktipp && cd
```

View File

@ -21,9 +21,7 @@
"dotenv": "10.0.0",
"log4js": "^6.7.1",
"nodemon": "^2.0.20",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.1.2",
"typescript": "^4.9.4",
"uuid": "^8.3.2"
},
"devDependencies": {
@ -46,7 +44,9 @@
"eslint-plugin-security": "^1.7.1",
"prettier": "^2.8.7",
"jest": "^27.2.4",
"ts-jest": "^27.0.5"
"ts-jest": "^27.0.5",
"ts-node": "^10.9.1",
"typescript": "^4.9.4"
},
"engines": {
"node": ">=14"

View File

@ -1 +1,2 @@
node_modules
playwright

View File

@ -2,13 +2,13 @@ module.exports = {
root: true,
env: {
node: true,
cypress: true,
},
parser: '@typescript-eslint/parser',
plugins: ['cypress', 'prettier', '@typescript-eslint' /*, 'jest' */],
extends: [
'standard',
'eslint:recommended',
'plugin:cypress/recommended',
'plugin:prettier/recommended',
'plugin:@typescript-eslint/recommended',
],

View File

@ -6,7 +6,7 @@ let emailLink: string
async function setupNodeEvents(
on: Cypress.PluginEvents,
config: Cypress.PluginConfigOptions
config: Cypress.PluginConfigOptions,
): Promise<Cypress.PluginConfigOptions> {
await addCucumberPreprocessorPlugin(on, config)
@ -14,7 +14,7 @@ async function setupNodeEvents(
'file:preprocessor',
browserify(config, {
typescript: require.resolve('typescript'),
})
}),
)
on('task', {
@ -41,7 +41,6 @@ export default defineConfig({
e2e: {
specPattern: '**/*.feature',
excludeSpecPattern: '*.js',
experimentalSessionAndOrigin: true,
baseUrl: 'http://localhost:3000',
chromeWebSecurity: false,
defaultCommandTimeout: 10000,
@ -49,10 +48,7 @@ export default defineConfig({
viewportHeight: 720,
viewportWidth: 1280,
video: false,
retries: {
runMode: 2,
openMode: 0,
},
retries: 0,
env: {
backendURL: 'http://localhost:4000',
mailserverURL: 'http://localhost:1080',

View File

@ -0,0 +1,38 @@
Feature: Send coins
As a user
I want to send and receive GDD
I want to see transaction details on overview and transactions pages
# Background:
# Given the following "users" are in the database:
# | email | password | name |
# | bob@baumeister.de | Aa12345_ | Bob Baumeister |
# | raeuber@hotzenplotz.de | Aa12345_ | Räuber Hotzenplotz |
Scenario: Send GDD to other user
Given the user is logged in as "bob@baumeister.de" "Aa12345_"
And the user navigates to page "/send"
When the user fills the send form with "<receiverEmail>" "<amount>" "<memoText>"
And the user submits the send form
Then the transaction details are presented for confirmation "<receiverEmail>" "<amount>" "<memoText>" "<senderBalance>" "<newSenderBalance>"
When the user submits the transaction by confirming
Then the "<receiverName>" and "<amount>" are displayed on the "send" page
When the user navigates to page "/transactions"
Then the "<receiverName>" and "<amount>" are displayed on the "transactions" page
Examples:
| receiverName | receiverEmail | amount | memoText | senderBalance | newSenderBalance |
| Räuber Hotzenplotz | raeuber@hotzenplotz.de | 120.50 | Some memo text | 515.11 | 394.61 |
Scenario: Receive GDD from other user
Given the user is logged in as "raeuber@hotzenplotz.de" "Aa12345_"
And the user receives the transaction e-mail about "<amount>" GDD from "<senderName>"
When the user opens the "transaction" link in the browser
Then the "<senderName>" and "120.50" are displayed on the "overview" page
When the user navigates to page "/transactions"
Then the "<senderName>" and "120.50" are displayed on the "transactions" page
Examples:
| senderName | amount |
| Bob der Baumeister | 120,50 |

View File

@ -2,6 +2,7 @@
export class OverviewPage {
navbarName = '[data-test="navbar-item-username"]'
rightLastTransactionsList = '.rightside-last-transactions'
goto() {
cy.visit('/overview')

View File

@ -14,9 +14,7 @@ export class ResetPasswordPage {
}
repeatNewPassword(password: string) {
cy.get(this.newPasswordRepeatInput)
.find('input[type=password]')
.type(password)
cy.get(this.newPasswordRepeatInput).find('input[type=password]').type(password)
return this
}

View File

@ -0,0 +1,25 @@
/// <reference types='cypress' />
export class SendPage {
confirmationBox = '.transaction-confirm-send'
submitBtn = '.btn-gradido'
enterReceiverEmail(email: string) {
cy.get('[data-test="input-identifier"]').find('input').clear().type(email)
return this
}
enterAmount(amount: string) {
cy.get('[data-test="input-amount"]').find('input').clear().type(amount)
return this
}
enterMemoText(text: string) {
cy.get('[data-test="input-textarea"]').find('textarea').clear().type(text)
return this
}
submit() {
cy.get(this.submitBtn).click()
}
}

View File

@ -8,10 +8,7 @@ export class UserEMailSite {
emailSubject = '.subject'
openRecentPasswordResetEMail() {
cy.get(this.emailList)
.find('email-item')
.filter(':contains(asswor)')
.click()
cy.get(this.emailList).find('email-item').filter(':contains(asswor)').click()
expect(cy.get(this.emailSubject)).to('contain', 'asswor')
}
}

View File

@ -7,6 +7,7 @@ import './e2e'
declare global {
namespace Cypress {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Chainable<Subject> {
login(email: string, password: string): Chainable<any>
}

View File

@ -1,4 +1,4 @@
import { Given, Then, When } from '@badeball/cypress-cucumber-preprocessor'
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
import { OverviewPage } from '../../e2e/models/OverviewPage'
import { SideNavMenu } from '../../e2e/models/SideNavMenu'
import { Toasts } from '../../e2e/models/Toasts'
@ -9,12 +9,9 @@ Given('the user navigates to page {string}', (page: string) => {
// login related
Given(
'the user is logged in as {string} {string}',
(email: string, password: string) => {
cy.login(email, password)
}
)
Given('the user is logged in as {string} {string}', (email: string, password: string) => {
cy.login(email, password)
})
Then('the user is logged in with username {string}', (username: string) => {
const overviewPage = new OverviewPage()

View File

@ -1,9 +1,9 @@
import { Then, When } from '@badeball/cypress-cucumber-preprocessor'
import { OverviewPage } from '../../e2e/models/OverviewPage'
import { ResetPasswordPage } from '../../e2e/models/ResetPasswordPage'
import { UserEMailSite } from '../../e2e/models/UserEMailSite'
const userEMailSite = new UserEMailSite()
const resetPasswordPage = new ResetPasswordPage()
Then('the user receives an e-mail containing the {string} link', (linkName: string) => {
let emailSubject: string
@ -18,14 +18,20 @@ Then('the user receives an e-mail containing the {string} link', (linkName: stri
emailSubject = 'asswor'
linkPattern = /\/reset-password\/[0-9]+\d/
break
case 'transaction':
emailSubject = 'Gradido gesendet'
linkPattern = /\/overview/
break
default:
throw new Error(`Error in "Then the user receives an e-mail containing the {string} link" step: incorrect linkname string "${linkName}"`)
throw new Error(
`Error in "Then the user receives an e-mail containing the {string} link" step: incorrect linkname string "${linkName}"`,
)
}
cy.origin(
Cypress.env('mailserverURL'),
{ args: { emailSubject, linkPattern, userEMailSite } },
({ emailSubject, linkPattern, userEMailSite }) => {
({ emailSubject, linkPattern, userEMailSite }) => {
cy.visit('/') // navigate to user's e-mail site (on fake mail server)
cy.get(userEMailSite.emailInbox).should('be.visible')
@ -35,11 +41,9 @@ Then('the user receives an e-mail containing the {string} link', (linkName: stri
.first()
.click()
cy.get(userEMailSite.emailMeta)
.find(userEMailSite.emailSubject)
.contains(emailSubject)
cy.get(userEMailSite.emailMeta).find(userEMailSite.emailSubject).contains(emailSubject)
cy.get('.email-content', { timeout: 2000})
cy.get('.email-content', { timeout: 2000 })
.find('.plain-text')
.contains(linkPattern)
.invoke('text')
@ -47,13 +51,64 @@ Then('the user receives an e-mail containing the {string} link', (linkName: stri
const emailLink = text.match(linkPattern)[0]
cy.task('setEmailLink', emailLink)
})
}
},
)
})
When(
'the user receives the transaction e-mail about {string} GDD from {string}',
(amount: string, senderName: string) => {
cy.origin(
Cypress.env('mailserverURL'),
{ args: { amount, senderName, userEMailSite } },
({ amount, senderName, userEMailSite }) => {
const subject = `${senderName} hat dir ${amount} Gradido gesendet`
const linkPattern = /\/transactions/
cy.visit('/')
cy.get(userEMailSite.emailInbox).should('be.visible')
cy.get(userEMailSite.emailList)
.find('.email-item')
.filter(`:contains(${subject})`)
.first()
.click()
cy.get(userEMailSite.emailMeta).find(userEMailSite.emailSubject).contains(subject)
cy.get('.email-content', { timeout: 2000 })
.find('.plain-text')
.contains(linkPattern)
.invoke('text')
.then((text) => {
const emailLink = text.match(linkPattern)[0]
cy.task('setEmailLink', emailLink)
})
},
)
},
)
When('the user opens the {string} link in the browser', (linkName: string) => {
const resetPasswordPage = new ResetPasswordPage()
cy.task('getEmailLink').then((emailLink) => {
cy.visit(emailLink)
})
cy.get(resetPasswordPage.newPasswordInput).should('be.visible')
switch (linkName) {
case 'activation':
cy.get(resetPasswordPage.newPasswordInput).should('be.visible')
break
case 'password reset':
cy.get(resetPasswordPage.newPasswordInput).should('be.visible')
break
case 'transaction':
// eslint-disable-next-line no-case-declarations
const overviewPage = new OverviewPage()
cy.get(overviewPage.rightLastTransactionsList).should('be.visible')
break
default:
throw new Error(
`Error in "Then the user receives an e-mail containing the {string} link" step: incorrect link name string "${linkName}"`,
)
}
})

View File

@ -0,0 +1,90 @@
import { Then, When } from '@badeball/cypress-cucumber-preprocessor'
import { SendPage } from '../../e2e/models/SendPage'
const sendPage = new SendPage()
When(
'the user fills the send form with {string} {string} {string}',
(email: string, amount: string, memoText: string) => {
sendPage.enterReceiverEmail(email)
sendPage.enterAmount(amount)
sendPage.enterMemoText(memoText)
},
)
When('the user submits the send form', () => {
sendPage.submit()
cy.get(sendPage.confirmationBox).should('be.visible')
})
Then(
'the transaction details are presented for confirmation {string} {string} {string} {string} {string}',
(
receiverEmail: string,
sendAmount: string,
memoText: string,
senderBalance: string,
newSenderBalance: string,
) => {
cy.get('.transaction-confirm-send').contains(receiverEmail)
cy.get('.transaction-confirm-send').contains(`+ ${sendAmount} GDD`)
cy.get('.transaction-confirm-send').contains(memoText)
cy.get('.transaction-confirm-send').contains(`+ ${senderBalance} GDD`)
cy.get('.transaction-confirm-send').contains(` ${sendAmount} GDD`)
cy.get('.transaction-confirm-send').contains(`+ ${newSenderBalance} GDD`)
},
)
When('the user submits the transaction by confirming', () => {
cy.intercept({
method: 'POST',
url: '/graphql',
hostname: 'localhost',
}).as('sendCoins')
sendPage.submit()
cy.wait('@sendCoins').then((interception) => {
cy.wrap(interception.response?.statusCode).should('eq', 200)
cy.wrap(interception.request.body).should(
'have.property',
'query',
`mutation ($identifier: String!, $amount: Decimal!, $memo: String!) {
sendCoins(identifier: $identifier, amount: $amount, memo: $memo)
}
`,
)
cy.wrap(interception.response?.body)
.should('have.nested.property', 'data.sendCoins')
.and('equal', true)
})
cy.get('[data-test="send-transaction-success-text"]').should('be.visible')
})
Then(
'the {string} and {string} are displayed on the {string} page',
(name: string, amount: string, page: string) => {
switch (page) {
case 'overview':
cy.get('.align-items-center').contains(`${name}`)
cy.get('.align-items-center').contains(`${amount} GDD`)
break
case 'send':
cy.get('.align-items-center').contains(`${name}`)
cy.get('.align-items-center').contains(`${amount} GDD`)
break
case 'transactions':
cy.get('div.mt-3 > div > div.test-list-group-item')
.eq(0)
.contains('div.gdd-transaction-list-item-name', `${name}`)
cy.get('div.mt-3 > div > div.test-list-group-item')
.eq(0)
.contains('[data-test="transaction-amount"]', `${amount} GDD`)
break
default:
throw new Error(
`Error in "Then the {string} and {string} are displayed on the {string}} page" step: incorrect page name string "${page}"`,
)
}
},
)

View File

@ -1,4 +1,4 @@
import { When, And } from '@badeball/cypress-cucumber-preprocessor'
import { When } from '@badeball/cypress-cucumber-preprocessor'
import { ForgotPasswordPage } from '../../e2e/models/ForgotPasswordPage'
import { LoginPage } from '../../e2e/models/LoginPage'
import { ResetPasswordPage } from '../../e2e/models/ResetPasswordPage'
@ -13,30 +13,25 @@ When('the user submits no credentials', () => {
loginPage.submitLogin()
})
When(
'the user submits the credentials {string} {string}',
(email: string, password: string) => {
cy.intercept('POST', '/graphql', (req) => {
if (
req.body.hasOwnProperty('query') &&
req.body.query.includes('mutation')
) {
req.alias = 'login'
}
})
When('the user submits the credentials {string} {string}', (email: string, password: string) => {
cy.intercept('POST', '/graphql', (req) => {
// eslint-disable-next-line no-prototype-builtins
if (req.body.hasOwnProperty('query') && req.body.query.includes('mutation')) {
req.alias = 'login'
}
})
loginPage.enterEmail(email)
loginPage.enterPassword(password)
loginPage.submitLogin()
cy.wait('@login').then((interception) => {
expect(interception.response.statusCode).equals(200)
})
}
)
loginPage.enterEmail(email)
loginPage.enterPassword(password)
loginPage.submitLogin()
cy.wait('@login').then((interception) => {
expect(interception.response.statusCode).equals(200)
})
})
// password reset related
And('the user navigates to the forgot password page', () => {
When('the user navigates to the forgot password page', () => {
loginPage.openForgotPasswordPage()
cy.url().should('include', '/forgot-password')
})
@ -45,25 +40,25 @@ When('the user enters the e-mail address {string}', (email: string) => {
forgotPasswordPage.enterEmail(email)
})
And('the user submits the e-mail form', () => {
When('the user submits the e-mail form', () => {
forgotPasswordPage.submitEmail()
cy.get(forgotPasswordPage.successComponent).should('be.visible')
})
And('the user enters the password {string}', (password: string) => {
When('the user enters the password {string}', (password: string) => {
resetPasswordPage.enterNewPassword(password)
})
And('the user repeats the password {string}', (password: string) => {
When('the user repeats the password {string}', (password: string) => {
resetPasswordPage.repeatNewPassword(password)
})
And('the user submits the new password', () => {
When('the user submits the new password', () => {
resetPasswordPage.submitNewPassword()
cy.get(resetPasswordPage.resetPasswordMessageBlock).should('be.visible')
})
And('the user clicks the sign in button', () => {
When('the user clicks the sign in button', () => {
resetPasswordPage.openSigninPage()
cy.url().should('contain', '/login')
})

View File

@ -1,28 +1,28 @@
import { And, When } from '@badeball/cypress-cucumber-preprocessor'
import { DataTable, When } from '@badeball/cypress-cucumber-preprocessor'
import { ProfilePage } from '../../e2e/models/ProfilePage'
import { Toasts } from '../../e2e/models/Toasts'
const profilePage = new ProfilePage()
And('the user opens the change password menu', () => {
When('the user opens the change password menu', () => {
cy.get(profilePage.openChangePassword).click()
cy.get(profilePage.newPasswordRepeatInput).should('be.visible')
cy.get(profilePage.submitNewPasswordBtn).should('be.disabled')
})
When('the user fills the password form with:', (table) => {
let hashedTableRows = table.rowsHash()
When('the user fills the password form with:', (table: DataTable) => {
const hashedTableRows = table.rowsHash()
profilePage.enterOldPassword(hashedTableRows['Old password'])
profilePage.enterNewPassword(hashedTableRows['New password'])
profilePage.enterRepeatPassword(hashedTableRows['Repeat new password'])
cy.get(profilePage.submitNewPasswordBtn).should('be.enabled')
})
And('the user submits the password form', () => {
When('the user submits the password form', () => {
profilePage.submitPasswordForm()
})
When('the user is presented a {string} message', (type: string) => {
When('the user is presented a {string} message', () => {
const toast = new Toasts()
cy.get(toast.toastSlot).within(() => {
cy.get(toast.toastTypeSuccess)

View File

@ -1,4 +1,4 @@
import { And, When } from '@badeball/cypress-cucumber-preprocessor'
import { When } from '@badeball/cypress-cucumber-preprocessor'
import { RegistrationPage } from '../../e2e/models/RegistrationPage'
const registrationPage = new RegistrationPage()
@ -10,14 +10,14 @@ When(
registrationPage.enterFirstname(firstname)
registrationPage.enterLastname(lastname)
registrationPage.enterEmail(email)
}
},
)
And('the user agrees to the privacy policy', () => {
When('the user agrees to the privacy policy', () => {
registrationPage.checkPrivacyCheckbox()
})
And('the user submits the registration form', () => {
When('the user submits the registration form', () => {
registrationPage.submitRegistrationForm()
cy.get(registrationPage.RegistrationThanxHeadline).should('be.visible')
cy.get(registrationPage.RegistrationThanxText).should('be.visible')

View File

@ -18,20 +18,20 @@
"lint": "eslint --max-warnings=0 --ext .js,.ts ."
},
"dependencies": {
"@badeball/cypress-cucumber-preprocessor": "^12.0.0",
"@badeball/cypress-cucumber-preprocessor": "^18.0.1",
"@cypress/browserify-preprocessor": "^3.0.2",
"@typescript-eslint/eslint-plugin": "^5.38.0",
"@typescript-eslint/parser": "^5.38.0",
"cypress": "^12.7.0",
"cypress": "^12.16.0",
"eslint": "^8.23.1",
"eslint-config-prettier": "^8.3.0",
"eslint-config-standard": "^16.0.3",
"eslint-loader": "^4.0.2",
"eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-cypress": "^2.13.3",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-promise": "^6.1.1",
"jwt-decode": "^3.1.2",
"prettier": "^2.7.1",
"typescript": "^4.7.4"

File diff suppressed because it is too large Load Diff

View File

@ -73,8 +73,8 @@ describe('ContributionMessagesFormular', () => {
)
})
it('emitted "update-state" with data', async () => {
expect(wrapper.emitted('update-state')).toEqual(
it('emitted "update-status" with data', async () => {
expect(wrapper.emitted('update-status')).toEqual(
expect.arrayContaining([expect.arrayContaining([42])]),
)
})

View File

@ -55,7 +55,7 @@ export default {
})
.then((result) => {
this.$emit('get-list-contribution-messages', false)
this.$emit('update-state', this.contributionId)
this.$emit('update-status', this.contributionId)
this.form.text = ''
this.toastSuccess(this.$t('message.reply'))
this.isSubmitting = false

View File

@ -8,7 +8,7 @@ describe('ContributionMessagesList', () => {
const propsData = {
contributionId: 42,
state: 'IN_PROGRESS',
status: 'IN_PROGRESS',
messages: [],
}
@ -40,13 +40,13 @@ describe('ContributionMessagesList', () => {
expect(wrapper.findComponent({ name: 'ContributionMessagesFormular' }).exists()).toBe(true)
})
describe('update State', () => {
describe('update Status', () => {
beforeEach(() => {
wrapper.vm.updateState()
wrapper.vm.updateStatus()
})
it('emits getListContributionMessages', async () => {
expect(wrapper.vm.$emit('update-state')).toBeTruthy()
expect(wrapper.vm.$emit('update-status')).toBeTruthy()
})
})
})

View File

@ -7,10 +7,10 @@
</div>
<div>
<contribution-messages-formular
v-if="['PENDING', 'IN_PROGRESS'].includes(state)"
v-if="['PENDING', 'IN_PROGRESS'].includes(status)"
:contributionId="contributionId"
v-on="$listeners"
@update-state="updateState"
@update-status="updateStatus"
/>
</div>
@ -37,7 +37,7 @@ export default {
type: Number,
required: true,
},
state: {
status: {
type: String,
required: true,
},
@ -47,8 +47,8 @@ export default {
},
},
methods: {
updateState(id) {
this.$emit('update-state', id)
updateStatus(id) {
this.$emit('update-status', id)
},
},
}

View File

@ -21,7 +21,7 @@ const mocks = {
describe('ContributionMessagesList', () => {
const propsData = {
contributionId: 42,
state: 'PENDING',
status: 'PENDING',
messages: [
{
id: 111,

View File

@ -119,11 +119,11 @@ describe('ContributionList', () => {
describe('update status', () => {
beforeEach(() => {
wrapper.findComponent({ name: 'ContributionListItem' }).vm.$emit('update-state', { id: 2 })
wrapper.findComponent({ name: 'ContributionListItem' }).vm.$emit('update-status', { id: 2 })
})
it('emits update status', () => {
expect(wrapper.emitted('update-state')).toEqual([[{ id: 2 }]])
expect(wrapper.emitted('update-status')).toEqual([[{ id: 2 }]])
})
})
})

View File

@ -2,26 +2,26 @@
<div class="contribution-list">
<div class="mb-3" v-for="item in items" :key="item.id + 'a'">
<contribution-list-item
v-if="item.state === 'IN_PROGRESS'"
v-if="item.status === 'IN_PROGRESS'"
v-bind="item"
@closeAllOpenCollapse="$emit('closeAllOpenCollapse')"
:contributionId="item.id"
:allContribution="allContribution"
@update-contribution-form="updateContributionForm"
@delete-contribution="deleteContribution"
@update-state="updateState"
@update-status="updateStatus"
/>
</div>
<div class="mb-3" v-for="item2 in items" :key="item2.id">
<contribution-list-item
v-if="item2.state !== 'IN_PROGRESS'"
v-if="item2.status !== 'IN_PROGRESS'"
v-bind="item2"
@closeAllOpenCollapse="$emit('closeAllOpenCollapse')"
:contributionId="item2.id"
:allContribution="allContribution"
@update-contribution-form="updateContributionForm"
@delete-contribution="deleteContribution"
@update-state="updateState"
@update-status="updateStatus"
/>
</div>
<b-pagination
@ -85,8 +85,8 @@ export default {
deleteContribution(item) {
this.$emit('delete-contribution', item)
},
updateState(id) {
this.$emit('update-state', id)
updateStatus(id) {
this.$emit('update-status', id)
},
},
computed: {

View File

@ -14,7 +14,7 @@ describe('ContributionListItem', () => {
const propsData = {
contributionId: 42,
state: 'PENDING',
status: 'PENDING',
messagesCount: 2,
id: 1,
createdAt: '26/07/2022',
@ -72,8 +72,8 @@ describe('ContributionListItem', () => {
expect(wrapper.vm.variant).toBe('success')
})
it('is warning at when state is IN_PROGRESS', async () => {
await wrapper.setProps({ state: 'IN_PROGRESS' })
it('is warning at when status is IN_PROGRESS', async () => {
await wrapper.setProps({ status: 'IN_PROGRESS' })
expect(wrapper.vm.variant).toBe('205')
})
})
@ -134,13 +134,13 @@ describe('ContributionListItem', () => {
})
})
describe('updateState', () => {
describe('updateStatus', () => {
beforeEach(async () => {
await wrapper.vm.updateState()
await wrapper.vm.updateStatus()
})
it('emit update-state', () => {
expect(wrapper.vm.$emit('update-state')).toBeTruthy()
it('emit update-status', () => {
expect(wrapper.vm.$emit('update-status')).toBeTruthy()
})
})
})

View File

@ -2,7 +2,7 @@
<div>
<div
class="contribution-list-item bg-white appBoxShadow gradido-border-radius pt-3 px-3"
:class="state === 'IN_PROGRESS' && !allContribution ? 'pulse border border-205' : ''"
:class="status === 'IN_PROGRESS' && !allContribution ? 'pulse border border-205' : ''"
>
<b-row>
<b-col cols="3" lg="2" md="2">
@ -26,7 +26,7 @@
<div class="mt-3 font-weight-bold">{{ $t('contributionText') }}</div>
<div class="mb-3 text-break word-break">{{ memo }}</div>
<div
v-if="state === 'IN_PROGRESS'"
v-if="status === 'IN_PROGRESS'"
class="text-205 pointer hover-font-bold"
@click="visible = !visible"
>
@ -37,11 +37,11 @@
<div class="small">
{{ $t('creation') }} {{ $t('(') }}{{ amount / 20 }} {{ $t('h') }}{{ $t(')') }}
</div>
<div v-if="state === 'DENIED' && allContribution" class="font-weight-bold">
<div v-if="status === 'DENIED' && allContribution" class="font-weight-bold">
<b-icon icon="x-circle" variant="danger"></b-icon>
{{ $t('contribution.alert.denied') }}
</div>
<div v-if="state === 'DELETED'" class="small">
<div v-if="status === 'DELETED'" class="small">
{{ $t('contribution.deleted') }}
</div>
<div v-else class="font-weight-bold">{{ amount | GDD }}</div>
@ -53,12 +53,12 @@
</b-col>
</b-row>
<b-row
v-if="(!['CONFIRMED', 'DELETED'].includes(state) && !allContribution) || messagesCount > 0"
v-if="(!['CONFIRMED', 'DELETED'].includes(status) && !allContribution) || messagesCount > 0"
class="p-2"
>
<b-col cols="3" class="mr-auto text-center">
<div
v-if="!['CONFIRMED', 'DELETED'].includes(state) && !allContribution && !moderatorId"
v-if="!['CONFIRMED', 'DELETED'].includes(status) && !allContribution && !moderatorId"
class="test-delete-contribution pointer mr-3"
@click="deleteContribution({ id })"
>
@ -69,7 +69,7 @@
</b-col>
<b-col cols="3" class="text-center">
<div
v-if="!['CONFIRMED', 'DELETED'].includes(state) && !allContribution && !moderatorId"
v-if="!['CONFIRMED', 'DELETED'].includes(status) && !allContribution && !moderatorId"
class="test-edit-contribution pointer mr-3"
@click="
$emit('update-contribution-form', {
@ -95,10 +95,10 @@
<b-collapse :id="collapsId" class="mt-2" v-model="visible">
<contribution-messages-list
:messages="messages_get"
:state="state"
:status="status"
:contributionId="contributionId"
@get-list-contribution-messages="getListContributionMessages"
@update-state="updateState"
@update-status="updateStatus"
/>
</b-collapse>
</div>
@ -161,7 +161,7 @@ export default {
type: String,
required: false,
},
state: {
status: {
type: String,
required: false,
default: '',
@ -197,14 +197,14 @@ export default {
if (this.deletedAt) return 'trash'
if (this.deniedAt) return 'x-circle'
if (this.confirmedAt) return 'check'
if (this.state === 'IN_PROGRESS') return 'question'
if (this.status === 'IN_PROGRESS') return 'question'
return 'bell-fill'
},
variant() {
if (this.deletedAt) return 'danger'
if (this.deniedAt) return 'warning'
if (this.confirmedAt) return 'success'
if (this.state === 'IN_PROGRESS') return '205'
if (this.status === 'IN_PROGRESS') return '205'
return 'primary'
},
date() {
@ -245,8 +245,8 @@ export default {
this.toastError(error.message)
})
},
updateState(id) {
this.$emit('update-state', id)
updateStatus(id) {
this.$emit('update-status', id)
},
},
watch: {

View File

@ -1,5 +1,5 @@
<template>
<div class="decayinformation-long px-2">
<div class="decayinformation-long px-1">
<div class="word-break mb-5 mt-lg-3">
<div class="font-weight-bold pb-2">{{ $t('form.memo') }}</div>
<div class="">{{ memo }}</div>
@ -11,10 +11,10 @@
<b-row>
<b-col>
<b-row>
<b-col cols="12" lg="4" md="4">
<b-col cols="6" lg="4" md="6" sm="6">
<div>{{ $t('decay.last_transaction') }}</div>
</b-col>
<b-col offset="1" offset-md="0" offset-lg="0" class="text-right mr-5">
<b-col offset="0" class="text-right mr-0">
<div>
<span>
{{ $d(new Date(decay.start), 'long') }}
@ -26,20 +26,20 @@
<!-- Previous Balance -->
<b-row class="mt-2">
<b-col cols="12" lg="6" md="3">
<b-col cols="6" lg="4" md="6" sm="6">
<div>{{ $t('decay.old_balance') }}</div>
</b-col>
<b-col offset="1" offset-md="0" offset-lg="0" class="text-right mr-5">
<b-col offset="0" class="text-right mr-0">
{{ previousBalance | GDD }}
</b-col>
</b-row>
<!-- Decay-->
<b-row class="mt-0">
<b-col cols="12" lg="3" md="3">
<b-col cols="6" lg="3" md="6" sm="6">
<div>{{ $t('decay.decay') }}</div>
</b-col>
<b-col offset="1" offset-md="0" offset-lg="0" class="text-right mr-5">
<b-col offset="0" class="text-right mr-0">
{{ decay.decay | GDD }}
</b-col>
</b-row>
@ -49,18 +49,21 @@
<b-row>
<b-col>
<b-row class="mb-2">
<!-- eslint-disable-next-line @intlify/vue-i18n/no-dynamic-keys-->
<b-col cols="12" lg="3" md="3">{{ $t(`decay.types.${typeId.toLowerCase()}`) }}</b-col>
<b-col offset="1" offset-md="0" offset-lg="0" class="text-right mr-5">
<!-- eslint-disable @intlify/vue-i18n/no-dynamic-keys-->
<b-col cols="6" lg="3" md="6" sm="6">
{{ $t(`decay.types.${typeId.toLowerCase()}`) }}
</b-col>
<!-- eslint-enable @intlify/vue-i18n/no-dynamic-keys-->
<b-col offset="0" class="text-right mr-0">
{{ amount | GDD }}
</b-col>
</b-row>
<!-- Total-->
<b-row class="border-top pt-2">
<b-col cols="12" lg="3" md="3">
<b-col cols="6" lg="3" md="6" sm="6">
<div>{{ $t('decay.new_balance') }}</div>
</b-col>
<b-col offset="1" offset-md="0" offset-lg="0" class="text-right mr-5">
<b-col offset="0" class="text-right mr-0">
<b>{{ balance | GDD }}</b>
</b-col>
</b-row>

View File

@ -1,10 +1,10 @@
<template>
<div class="duration-row">
<b-row>
<b-col cols="12" lg="4" md="4">
<b-col cols="6" lg="4" md="6" sm="6">
<div>{{ $t('decay.past_time') }}</div>
</b-col>
<b-col offset="1" offset-md="0" offset-lg="0" class="text-right mr-5">
<b-col offset="0" class="text-right mr-0">
<span v-if="duration">{{ duration }}</span>
</b-col>
</b-row>

View File

@ -26,7 +26,9 @@
<div class="small mb-2">
{{ $t('decay.types.receive') }}
</div>
<div class="font-weight-bold gradido-global-color-accent">{{ amount | GDD }}</div>
<div class="font-weight-bold gradido-global-color-accent" data-test="transaction-amount">
{{ amount | GDD }}
</div>
<div v-if="linkId" class="small">
{{ $t('via_link') }}
<b-icon

View File

@ -25,7 +25,9 @@
<div class="small mb-2">
{{ $t('decay.types.send') }}
</div>
<div class="font-weight-bold text-140">{{ amount | GDD }}</div>
<div class="font-weight-bold text-140" data-test="transaction-amount">
{{ amount | GDD }}
</div>
<div v-if="linkId" class="small">
{{ $t('via_link') }}
<b-icon

View File

@ -190,7 +190,7 @@ export const listContributions = gql`
confirmedAt
confirmedBy
deletedAt
state
status
messagesCount
deniedAt
deniedBy
@ -214,7 +214,7 @@ export const listAllContributions = gql`
contributionDate
confirmedAt
confirmedBy
state
status
messagesCount
deniedAt
deniedBy

View File

@ -276,7 +276,7 @@ export default {
} = result
this.GdtBalance =
transactionList.balance.balanceGDT === null
? null
? 0
: Number(transactionList.balance.balanceGDT)
this.transactions = transactionList.transactions
this.balance = Number(transactionList.balance.balance)

View File

@ -68,7 +68,7 @@ describe('Community', () => {
firstName: 'Bibi',
contributionDate: '2022-07-15T08:47:06.000Z',
lastName: 'Bloxberg',
state: 'IN_PROGRESS',
status: 'IN_PROGRESS',
messagesCount: 0,
deniedAt: null,
deniedBy: null,
@ -85,7 +85,7 @@ describe('Community', () => {
firstName: 'Bibi',
contributionDate: '2022-06-15T08:47:06.000Z',
lastName: 'Bloxberg',
state: 'CONFIRMED',
status: 'CONFIRMED',
messagesCount: 0,
deniedAt: null,
deniedBy: null,
@ -121,7 +121,7 @@ describe('Community', () => {
deniedAt: null,
deniedBy: null,
messagesCount: 0,
state: 'IN_PROGRESS',
status: 'IN_PROGRESS',
},
{
id: 1550,
@ -137,7 +137,7 @@ describe('Community', () => {
deniedAt: null,
deniedBy: null,
messagesCount: 0,
state: 'IN_PROGRESS',
status: 'IN_PROGRESS',
},
{
id: 1556,
@ -153,7 +153,7 @@ describe('Community', () => {
deniedAt: null,
deniedBy: null,
messagesCount: 0,
state: 'IN_PROGRESS',
status: 'IN_PROGRESS',
},
],
contributionCount: 3,
@ -263,7 +263,7 @@ describe('Community', () => {
expect(wrapper.findAll('div[role="tabpanel"]')).toHaveLength(3)
})
it('check for correct tabIndex if state is "IN_PROGRESS" or not', () => {
it('check for correct tabIndex if status is "IN_PROGRESS" or not', () => {
expect(routerPushMock).toBeCalledWith({ params: { tab: 'contributions' } })
})

View File

@ -30,7 +30,7 @@
@update-list-contributions="updateListContributions"
@update-contribution-form="updateContributionForm"
@delete-contribution="deleteContribution"
@update-state="updateState"
@update-status="updateStatus"
:contributionCount="contributionCount"
:showPagination="true"
:pageSize="pageSize"
@ -148,7 +148,7 @@ export default {
update({ listContributions }) {
this.contributionCount = listContributions.contributionCount
this.items = listContributions.contributionList
if (this.items.find((item) => item.state === 'IN_PROGRESS')) {
if (this.items.find((item) => item.status === 'IN_PROGRESS')) {
this.tabIndex = 1
if (this.$route.params.tab !== 'contributions')
this.$router.push({ params: { tab: 'contributions' } })
@ -290,8 +290,8 @@ export default {
updateTransactions(pagination) {
this.$emit('update-transactions', pagination)
},
updateState(id) {
this.items.find((item) => item.id === id).state = 'PENDING'
updateStatus(id) {
this.items.find((item) => item.id === id).status = 'PENDING'
},
},
}

View File

@ -142,6 +142,7 @@ describe('Transactions', () => {
currentPage: 1,
pageSize: 25,
},
fetchPolicy: 'network-only',
})
})
@ -170,6 +171,7 @@ describe('Transactions', () => {
currentPage: 2,
pageSize: 25,
},
fetchPolicy: 'network-only',
})
})
})

View File

@ -59,6 +59,7 @@ export default {
currentPage: this.currentPage,
pageSize: this.pageSize,
},
fetchPolicy: 'network-only',
})
.then((result) => {
const {