Merge branch 'master' into 2789-double-redeem-transaction-link

This commit is contained in:
Hannes Heine 2023-06-30 11:24:23 +02:00 committed by GitHub
commit 52fa4cea98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 326 additions and 109 deletions

View File

@ -1,6 +1,7 @@
import { mount } from '@vue/test-utils'
import ContributionMessagesFormular from './ContributionMessagesFormular'
import { toastErrorSpy, toastSuccessSpy } from '../../../test/testSetup'
import { adminCreateContributionMessage } from '@/graphql/adminCreateContributionMessage'
const localVue = global.localVue
@ -34,6 +35,7 @@ describe('ContributionMessagesFormular', () => {
describe('mount', () => {
beforeEach(() => {
wrapper = Wrapper()
jest.clearAllMocks()
})
it('has a DIV .contribution-messages-formular', () => {
@ -80,6 +82,58 @@ describe('ContributionMessagesFormular', () => {
})
})
describe('send DIALOG contribution message with success', () => {
beforeEach(async () => {
await wrapper.setData({
form: {
text: 'text form message',
},
})
await wrapper.find('button[data-test="submit-dialog"]').trigger('click')
})
it('moderatorMesage has `DIALOG`', () => {
expect(apolloMutateMock).toBeCalledWith({
mutation: adminCreateContributionMessage,
variables: {
contributionId: 42,
message: 'text form message',
messageType: 'DIALOG',
},
})
})
it('toasts an success message', () => {
expect(toastSuccessSpy).toBeCalledWith('message.request')
})
})
describe('send MODERATOR contribution message with success', () => {
beforeEach(async () => {
await wrapper.setData({
form: {
text: 'text form message',
},
})
await wrapper.find('button[data-test="submit-moderator"]').trigger('click')
})
it('moderatorMesage has `MODERATOR`', () => {
expect(apolloMutateMock).toBeCalledWith({
mutation: adminCreateContributionMessage,
variables: {
contributionId: 42,
message: 'text form message',
messageType: 'MODERATOR',
},
})
})
it('toasts an success message', () => {
expect(toastSuccessSpy).toBeCalledWith('message.request')
})
})
describe('send contribution message with error', () => {
beforeEach(async () => {
apolloMutateMock.mockRejectedValue({ message: 'OUCH!' })
@ -91,21 +145,5 @@ describe('ContributionMessagesFormular', () => {
expect(toastErrorSpy).toBeCalledWith('OUCH!')
})
})
describe('send contribution message with success', () => {
beforeEach(async () => {
wrapper.setData({
form: {
text: 'text form message',
},
})
wrapper = Wrapper()
await wrapper.find('form').trigger('submit')
})
it('toasts an success message', () => {
expect(toastSuccessSpy).toBeCalledWith('message.request')
})
})
})
})

View File

@ -1,7 +1,7 @@
<template>
<div class="contribution-messages-formular">
<div class="mt-5">
<b-form @submit.prevent="onSubmit" @reset.prevent="onReset">
<b-form @reset.prevent="onReset" @submit="onSubmit(messageType.DIALOG)">
<b-form-textarea
id="textarea"
v-model="form.text"
@ -12,8 +12,27 @@
<b-col>
<b-button type="reset" variant="danger">{{ $t('form.cancel') }}</b-button>
</b-col>
<b-col class="text-center">
<b-button
type="button"
variant="warning"
class="text-black"
:disabled="disabled"
@click.prevent="onSubmit(messageType.MODERATOR)"
data-test="submit-moderator"
>
{{ $t('moderator.notice') }}
</b-button>
</b-col>
<b-col class="text-right">
<b-button type="submit" variant="primary" :disabled="disabled">
<b-button
type="submit"
variant="primary"
:disabled="disabled"
@click.prevent="onSubmit(messageType.DIALOG)"
data-test="submit-dialog"
>
{{ $t('form.submit') }}
</b-button>
</b-col>
@ -39,10 +58,14 @@ export default {
text: '',
},
loading: false,
messageType: {
DIALOG: 'DIALOG',
MODERATOR: 'MODERATOR',
},
}
},
methods: {
onSubmit(event) {
onSubmit(mType) {
this.loading = true
this.$apollo
.mutate({
@ -50,6 +73,7 @@ export default {
variables: {
contributionId: this.contributionId,
message: this.form.text,
messageType: mType,
},
})
.then((result) => {

View File

@ -1,26 +1,102 @@
import { mount } from '@vue/test-utils'
import ContributionMessagesList from './ContributionMessagesList'
import VueApollo from 'vue-apollo'
import { createMockClient } from 'mock-apollo-client'
import { adminListContributionMessages } from '../../graphql/adminListContributionMessages.js'
import { toastErrorSpy } from '../../../test/testSetup'
const mockClient = createMockClient()
const apolloProvider = new VueApollo({
defaultClient: mockClient,
})
const localVue = global.localVue
const apolloQueryMock = jest.fn().mockResolvedValue()
localVue.use(VueApollo)
const defaultData = () => {
return {
adminListContributionMessages: {
count: 4,
messages: [
{
id: 43,
message: 'A DIALOG message',
createdAt: new Date().toString(),
updatedAt: null,
type: 'DIALOG',
userFirstName: 'Peter',
userLastName: 'Lustig',
userId: 1,
isModerator: true,
},
{
id: 44,
message: 'Another DIALOG message',
createdAt: new Date().toString(),
updatedAt: null,
type: 'DIALOG',
userFirstName: 'Bibi',
userLastName: 'Bloxberg',
userId: 2,
isModerator: false,
},
{
id: 45,
message: `DATE
---
A HISTORY message
---
AMOUNT`,
createdAt: new Date().toString(),
updatedAt: null,
type: 'HISTORY',
userFirstName: 'Bibi',
userLastName: 'Bloxberg',
userId: 2,
isModerator: false,
},
{
id: 46,
message: 'A MODERATOR message',
createdAt: new Date().toString(),
updatedAt: null,
type: 'MODERATOR',
userFirstName: 'Peter',
userLastName: 'Lustig',
userId: 1,
isModerator: true,
},
],
},
}
}
describe('ContributionMessagesList', () => {
let wrapper
const adminListContributionMessagessMock = jest.fn()
mockClient.setRequestHandler(
adminListContributionMessages,
adminListContributionMessagessMock
.mockRejectedValueOnce({ message: 'Auaa!' })
.mockResolvedValue({ data: defaultData() }),
)
const propsData = {
contributionId: 42,
contributionUserId: 108,
contributionStatus: 'PENDING',
}
const mocks = {
$t: jest.fn((t) => t),
$d: jest.fn((d) => d),
$n: jest.fn((n) => n),
$i18n: {
locale: 'en',
},
$apollo: {
query: apolloQueryMock,
},
}
const Wrapper = () => {
@ -28,30 +104,34 @@ describe('ContributionMessagesList', () => {
localVue,
mocks,
propsData,
apolloProvider,
})
}
describe('mount', () => {
beforeEach(() => {
jest.clearAllMocks()
wrapper = Wrapper()
})
it('sends query to Apollo when created', () => {
expect(apolloQueryMock).toBeCalledWith(
expect.objectContaining({
variables: {
contributionId: propsData.contributionId,
},
}),
)
describe('server response for admin list contribution messages is error', () => {
it('toast an error message', () => {
expect(toastErrorSpy).toBeCalledWith('Auaa!')
})
})
it('has a DIV .contribution-messages-list', () => {
expect(wrapper.find('div.contribution-messages-list').exists()).toBe(true)
})
describe('server response is succes', () => {
it('has a DIV .contribution-messages-list', () => {
expect(wrapper.find('div.contribution-messages-list').exists()).toBe(true)
})
it('has a Component ContributionMessagesFormular', () => {
expect(wrapper.findComponent({ name: 'ContributionMessagesFormular' }).exists()).toBe(true)
it('has 4 messages', () => {
expect(wrapper.findAll('div.contribution-messages-list-item')).toHaveLength(4)
})
it('has a Component ContributionMessagesFormular', () => {
expect(wrapper.findComponent({ name: 'ContributionMessagesFormular' }).exists()).toBe(true)
})
})
})
})

View File

@ -2,14 +2,16 @@
<div class="contribution-messages-list">
<b-container>
<div v-for="message in messages" v-bind:key="message.id">
<contribution-messages-list-item :message="message" />
<contribution-messages-list-item
:message="message"
:contributionUserId="contributionUserId"
/>
</div>
</b-container>
<div v-if="contributionStatus === 'PENDING' || contributionStatus === 'IN_PROGRESS'">
<contribution-messages-formular
:contributionId="contributionId"
@get-list-contribution-messages="getListContributionMessages"
@get-list-contribution-messages="$apollo.queries.Messages.refetch()"
@update-status="updateStatus"
/>
</div>
@ -18,7 +20,7 @@
<script>
import ContributionMessagesListItem from './slots/ContributionMessagesListItem'
import ContributionMessagesFormular from '../ContributionMessages/ContributionMessagesFormular'
import { listContributionMessages } from '../../graphql/listContributionMessages.js'
import { adminListContributionMessages } from '../../graphql/adminListContributionMessages.js'
export default {
name: 'ContributionMessagesList',
@ -35,36 +37,40 @@ export default {
type: String,
required: true,
},
contributionUserId: {
type: Number,
required: true,
},
},
data() {
return {
messages: [],
}
},
methods: {
getListContributionMessages(id) {
this.$apollo
.query({
query: listContributionMessages,
variables: {
contributionId: id,
},
fetchPolicy: 'no-cache',
})
.then((result) => {
this.messages = result.data.listContributionMessages.messages
})
.catch((error) => {
this.toastError(error.message)
})
apollo: {
Messages: {
query() {
return adminListContributionMessages
},
variables() {
return {
contributionId: this.contributionId,
}
},
fetchPolicy: 'no-cache',
update({ adminListContributionMessages }) {
this.messages = adminListContributionMessages.messages
},
error({ message }) {
this.toastError(message)
},
},
},
methods: {
updateStatus(id) {
this.$emit('update-status', id)
},
},
created() {
this.getListContributionMessages(this.contributionId)
},
}
</script>
<style scoped>

View File

@ -13,12 +13,21 @@ describe('ContributionMessagesListItem', () => {
$t: jest.fn((t) => t),
$d: dateMock,
$n: numberMock,
$store: {
state: {
moderator: {
firstName: 'Peter',
lastName: 'Lustig',
},
},
},
}
describe('if message author has moderator role', () => {
const propsData = {
contributionId: 42,
status: 'PENDING',
contributionUserId: 108,
state: 'PENDING',
message: {
id: 111,
message: 'Lorem ipsum?',
@ -51,27 +60,21 @@ describe('ContributionMessagesListItem', () => {
})
it('has the complete user name', () => {
expect(wrapper.find('div.text-right.is-moderator > span:nth-child(2)').text()).toBe(
'Peter Lustig',
)
expect(wrapper.find('[data-test="moderator-name"]').text()).toBe('Peter Lustig')
})
it('has the message creation date', () => {
expect(wrapper.find('div.text-right.is-moderator > span:nth-child(3)').text()).toMatch(
expect(wrapper.find('[data-test="moderator-date"]').text()).toMatch(
'Mon Aug 29 2022 12:23:27 GMT+0000',
)
})
it('has the moderator label', () => {
expect(wrapper.find('div.text-right.is-moderator > small:nth-child(4)').text()).toBe(
'moderator',
)
expect(wrapper.find('[data-test="moderator-label"]').text()).toBe('moderator.moderator')
})
it('has the message', () => {
expect(wrapper.find('div.text-right.is-moderator > div:nth-child(5)').text()).toBe(
'Lorem ipsum?',
)
expect(wrapper.find('[data-test="moderator-message"]').text()).toBe('Lorem ipsum?')
})
})
})
@ -79,7 +82,8 @@ describe('ContributionMessagesListItem', () => {
describe('if message author does not have moderator role', () => {
const propsData = {
contributionId: 42,
status: 'PENDING',
contributionUserId: 108,
state: 'PENDING',
message: {
id: 113,
message: 'Asda sdad ad asdasd, das Ass das Das. ',
@ -107,23 +111,21 @@ describe('ContributionMessagesListItem', () => {
})
it('has a DIV .text-left.is-not-moderator', () => {
expect(wrapper.find('div.text-left.is-not-moderator').exists()).toBe(true)
expect(wrapper.find('div.text-left.is-user').exists()).toBe(true)
})
it('has the complete user name', () => {
expect(wrapper.find('div.is-not-moderator.text-left > span:nth-child(2)').text()).toBe(
'Bibi Bloxberg',
)
expect(wrapper.find('[data-test="user-name"]').text()).toBe('Bibi Bloxberg')
})
it('has the message creation date', () => {
expect(wrapper.find('div.is-not-moderator.text-left > span:nth-child(3)').text()).toMatch(
expect(wrapper.find('[data-test="user-date"]').text()).toMatch(
'Mon Aug 29 2022 12:25:34 GMT+0000',
)
})
it('has the message', () => {
expect(wrapper.find('div.is-not-moderator.text-left > div:nth-child(4)').text()).toBe(
expect(wrapper.find('[data-test="user-message"]').text()).toBe(
'Asda sdad ad asdasd, das Ass das Das.',
)
})
@ -132,6 +134,7 @@ describe('ContributionMessagesListItem', () => {
describe('links in contribtion message', () => {
const propsData = {
contributionUserId: 108,
message: {
id: 111,
message: 'Lorem ipsum?',
@ -159,7 +162,7 @@ describe('ContributionMessagesListItem', () => {
beforeEach(() => {
propsData.message.message = 'https://gradido.net/de/'
wrapper = ModeratorItemWrapper()
messageField = wrapper.find('div.is-not-moderator.text-left > div:nth-child(4)')
messageField = wrapper.find('[data-test="moderator-message"]')
})
it('contains the link as text', () => {
@ -176,7 +179,7 @@ describe('ContributionMessagesListItem', () => {
propsData.message.message = `Here you find all you need to know about Gradido: https://gradido.net/de/
and here is the link to the repository: https://github.com/gradido/gradido`
wrapper = ModeratorItemWrapper()
messageField = wrapper.find('div.is-not-moderator.text-left > div:nth-child(4)')
messageField = wrapper.find('[data-test="moderator-message"]')
})
it('contains the whole text', () => {
@ -196,6 +199,7 @@ and here is the link to the repository: https://github.com/gradido/gradido`)
describe('contribution message type HISTORY', () => {
const propsData = {
contributionUserId: 108,
message: {
id: 111,
message: `Sun Nov 13 2022 13:05:48 GMT+0100 (Central European Standard Time)
@ -227,7 +231,7 @@ This message also contains a link: https://gradido.net/de/
beforeEach(() => {
jest.clearAllMocks()
wrapper = itemWrapper()
messageField = wrapper.find('div.is-not-moderator.text-left > div:nth-child(4)')
messageField = wrapper
})
it('renders the date', () => {

View File

@ -1,17 +1,37 @@
<template>
<div class="contribution-messages-list-item">
<div v-if="message.isModerator" class="text-right is-moderator">
<div v-if="isModeratorMessage" class="text-right p-2 rounded-sm mb-3" :class="boxClass">
<small class="ml-4" data-test="moderator-label">
{{ $t('moderator.moderator') }}
</small>
<small class="ml-2" data-test="moderator-date">
{{ $d(new Date(message.createdAt), 'short') }}
</small>
<span class="ml-2 mr-2" data-test="moderator-name">
{{ message.userFirstName }} {{ message.userLastName }}
</span>
<b-avatar square variant="warning"></b-avatar>
<span class="ml-2 mr-2">{{ message.userFirstName }} {{ message.userLastName }}</span>
<span class="ml-2">{{ $d(new Date(message.createdAt), 'short') }}</span>
<small class="ml-4 text-success">{{ $t('moderator') }}</small>
<parse-message v-bind="message"></parse-message>
<parse-message v-bind="message" data-test="moderator-message"></parse-message>
<small v-if="isModeratorHiddenMessage">
<hr />
{{ $t('moderator.request') }}
</small>
</div>
<div v-else class="text-left is-not-moderator">
<div v-else class="text-left p-2 rounded-sm mb-3" :class="boxClass">
<b-avatar variant="info"></b-avatar>
<span class="ml-2 mr-2">{{ message.userFirstName }} {{ message.userLastName }}</span>
<span class="ml-2">{{ $d(new Date(message.createdAt), 'short') }}</span>
<parse-message v-bind="message"></parse-message>
<span class="ml-2 mr-2" data-test="user-name">
{{ message.userFirstName }} {{ message.userLastName }}
</span>
<small class="ml-2" data-test="user-date">
{{ $d(new Date(message.createdAt), 'short') }}
</small>
<small v-if="isHistory">
<hr />
{{ $t('moderator.history') }}
<hr />
</small>
<parse-message v-bind="message" data-test="user-message"></parse-message>
</div>
</div>
</template>
@ -28,22 +48,50 @@ export default {
type: Object,
required: true,
},
contributionUserId: {
type: Number,
required: true,
},
},
computed: {
isModeratorMessage() {
return this.contributionUserId !== this.message.userId
},
isModeratorHiddenMessage() {
return this.message.type === 'MODERATOR'
},
isHistory() {
return this.message.type === 'HISTORY'
},
boxClass() {
if (this.isModeratorHiddenMessage) return 'is-moderator is-moderator-hidden-message'
if (this.isHistory) return 'is-user is-user-history-message'
if (this.isModeratorMessage) return 'is-moderator is-moderator-message'
return 'is-user is-user-message'
},
},
}
</script>
<style>
.is-not-moderator {
clear: both;
width: 75%;
margin-top: 20px;
/* background-color: rgb(261, 204, 221); */
}
.is-moderator {
clear: both;
float: right;
width: 75%;
margin-top: 20px;
margin-bottom: 20px;
/* background-color: rgb(255, 255, 128); */
}
.is-moderator-message {
background-color: rgb(228, 237, 245);
}
.is-moderator-hidden-message {
background-color: rgb(217, 161, 228);
}
.is-user {
clear: both;
width: 75%;
}
.is-user-message {
background-color: rgb(236, 235, 213);
}
.is-user-history-message {
background-color: rgb(235, 226, 57);
}
</style>

View File

@ -39,6 +39,7 @@ const defaultData = () => {
deletedBy: null,
deletedAt: null,
createdAt: new Date(),
moderatorId: null,
},
{
id: 2,
@ -61,6 +62,7 @@ const defaultData = () => {
deletedBy: null,
deletedAt: null,
createdAt: new Date(),
moderatorId: null,
},
],
},

View File

@ -103,6 +103,7 @@
<contribution-messages-list
:contributionId="row.item.id"
:contributionStatus="row.item.status"
:contributionUserId="row.item.userId"
@update-status="updateStatus"
/>
</div>

View File

@ -1,8 +1,12 @@
import gql from 'graphql-tag'
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

@ -1,8 +1,8 @@
import gql from 'graphql-tag'
export const listContributionMessages = gql`
export const adminListContributionMessages = gql`
query ($contributionId: Int!, $pageSize: Int = 25, $currentPage: Int = 1, $order: Order = ASC) {
listContributionMessages(
adminListContributionMessages(
contributionId: $contributionId
pageSize: $pageSize
currentPage: $currentPage

View File

@ -108,7 +108,12 @@
"message": {
"request": "Die Anfrage wurde gesendet."
},
"moderator": "Moderator",
"moderator": {
"history": "Die Daten wurden geändert. Dies sind die alten Daten.",
"moderator": "Moderator",
"notice": "Moderator Notiz",
"request": "Diese Nachricht ist nur für die Moderatoren sichtbar!"
},
"name": "Name",
"navbar": {
"automaticContributions": "Automatische Beiträge",

View File

@ -108,7 +108,12 @@
"message": {
"request": "Request has been sent."
},
"moderator": "Moderator",
"moderator": {
"history": "The data has been changed. This is the old data.",
"moderator": "Moderator",
"notice": "Moderator note",
"request": "This message is only visible to the moderators!"
},
"name": "Name",
"navbar": {
"automaticContributions": "Automatic Contributions",

View File

@ -217,7 +217,7 @@ export default {
return this.formatDateOrDash(value)
},
},
{ key: 'moderatorId', label: this.$t('moderator') },
{ key: 'moderatorId', label: this.$t('moderator.moderator') },
{ key: 'editCreation', label: this.$t('chat') },
{ key: 'confirm', label: this.$t('save') },
],
@ -254,7 +254,7 @@ export default {
return this.formatDateOrDash(value)
},
},
{ key: 'confirmedBy', label: this.$t('moderator') },
{ key: 'confirmedBy', label: this.$t('moderator.moderator') },
{ key: 'chatCreation', label: this.$t('chat') },
],
[
@ -290,7 +290,7 @@ export default {
return this.formatDateOrDash(value)
},
},
{ key: 'deniedBy', label: this.$t('moderator') },
{ key: 'deniedBy', label: this.$t('moderator.moderator') },
{ key: 'chatCreation', label: this.$t('chat') },
],
[
@ -326,7 +326,7 @@ export default {
return this.formatDateOrDash(value)
},
},
{ key: 'deletedBy', label: this.$t('moderator') },
{ key: 'deletedBy', label: this.$t('moderator.moderator') },
{ key: 'chatCreation', label: this.$t('chat') },
],
[
@ -363,7 +363,7 @@ export default {
return this.formatDateOrDash(value)
},
},
{ key: 'confirmedBy', label: this.$t('moderator') },
{ key: 'confirmedBy', label: this.$t('moderator.moderator') },
{ key: 'chatCreation', label: this.$t('chat') },
],
][this.tabIndex]