Merge branch 'master' into preserve-email-on-login

This commit is contained in:
Moriz Wahl 2023-05-19 16:52:23 +02:00 committed by GitHub
commit b26ce5af08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 146 additions and 38 deletions

View File

@ -50,6 +50,7 @@
"prettier": "^2.2.1",
"qrcanvas-vue": "2.1.1",
"regenerator-runtime": "^0.13.7",
"uuid": "^9.0.0",
"vee-validate": "^3.4.5",
"vue": "2.6.12",
"vue-apollo": "^3.0.7",

View File

@ -71,9 +71,9 @@ describe('TransactionForm', () => {
})
describe('with balance <= 0.00 GDD the form is disabled', () => {
it('has a disabled input field of type email', () => {
it('has a disabled input field of type text', () => {
expect(
wrapper.find('div[data-test="input-email"]').find('input').attributes('disabled'),
wrapper.find('div[data-test="input-identifier"]').find('input').attributes('disabled'),
).toBe('disabled')
})
@ -116,51 +116,54 @@ describe('TransactionForm', () => {
expect(wrapper.vm.radioSelected).toBe(SEND_TYPES.send)
})
describe('email field', () => {
it('has an input field of type email', () => {
describe('identifier field', () => {
it('has an input field of type text', () => {
expect(
wrapper.find('div[data-test="input-email"]').find('input').attributes('type'),
).toBe('email')
wrapper.find('div[data-test="input-identifier"]').find('input').attributes('type'),
).toBe('text')
})
it('has a label form.receiver', () => {
expect(wrapper.find('div[data-test="input-email"]').find('label').text()).toBe(
it('has a label form.recipient', () => {
expect(wrapper.find('div[data-test="input-identifier"]').find('label').text()).toBe(
'form.recipient',
)
})
it('has a placeholder "E-Mail"', () => {
it('has a placeholder for identifier', () => {
expect(
wrapper.find('div[data-test="input-email"]').find('input').attributes('placeholder'),
).toBe('form.email')
wrapper
.find('div[data-test="input-identifier"]')
.find('input')
.attributes('placeholder'),
).toBe('form.identifier')
})
it('flushes an error message when no valid email is given', async () => {
await wrapper.find('div[data-test="input-email"]').find('input').setValue('a')
it('flushes an error message when no valid identifier is given', async () => {
await wrapper.find('div[data-test="input-identifier"]').find('input').setValue('a')
await flushPromises()
expect(
wrapper.find('div[data-test="input-email"]').find('.invalid-feedback').text(),
).toBe('validations.messages.email')
wrapper.find('div[data-test="input-identifier"]').find('.invalid-feedback').text(),
).toBe('form.validation.valid-identifier')
})
// TODO:SKIPPED there is no check that the email being sent to is the same as the user's email.
it.skip('flushes an error message when email is the email of logged in user', async () => {
await wrapper
.find('div[data-test="input-email"]')
.find('div[data-test="input-identifier"]')
.find('input')
.setValue('user@example.org')
await flushPromises()
expect(
wrapper.find('div[data-test="input-email"]').find('.invalid-feedback').text(),
wrapper.find('div[data-test="input-identifier"]').find('.invalid-feedback').text(),
).toBe('form.validation.is-not')
})
it('trims the email after blur', async () => {
it('trims the identifier after blur', async () => {
await wrapper
.find('div[data-test="input-email"]')
.find('div[data-test="input-identifier"]')
.find('input')
.setValue(' valid@email.com ')
await wrapper.find('div[data-test="input-email"]').find('input').trigger('blur')
await wrapper.find('div[data-test="input-identifier"]').find('input').trigger('blur')
await flushPromises()
expect(wrapper.vm.form.identifier).toBe('valid@email.com')
})
@ -304,7 +307,7 @@ Die ganze Welt bezwingen.“`)
it('clears all fields on click', async () => {
await wrapper
.find('div[data-test="input-email"]')
.find('div[data-test="input-identifier"]')
.find('input')
.setValue('someone@watches.tv')
await wrapper.find('div[data-test="input-amount"]').find('input').setValue('87.23')
@ -327,7 +330,7 @@ Die ganze Welt bezwingen.“`)
describe('submit', () => {
beforeEach(async () => {
await wrapper
.find('div[data-test="input-email"]')
.find('div[data-test="input-identifier"]')
.find('input')
.setValue('someone@watches.tv')
await wrapper.find('div[data-test="input-amount"]').find('input').setValue('87.23')
@ -380,8 +383,8 @@ Die ganze Welt bezwingen.“`)
})
describe('query for username with success', () => {
it('has no email input field', () => {
expect(wrapper.find('div[data-test="input-email"]').exists()).toBe(false)
it('has no identifier input field', () => {
expect(wrapper.find('div[data-test="input-identifier"]').exists()).toBe(false)
})
it('queries the username', () => {

View File

@ -59,10 +59,10 @@
</b-col>
<b-col cols="12" v-if="radioSelected === sendTypes.send">
<div v-if="!gradidoID">
<input-email
<input-identifier
:name="$t('form.recipient')"
:label="$t('form.recipient')"
:placeholder="$t('form.email')"
:placeholder="$t('form.identifier')"
v-model="form.identifier"
:disabled="isBalanceDisabled"
@onValidation="onValidation"
@ -134,7 +134,7 @@
</template>
<script>
import { SEND_TYPES } from '@/pages/Send'
import InputEmail from '@/components/Inputs/InputEmail'
import InputIdentifier from '@/components/Inputs/InputIdentifier'
import InputAmount from '@/components/Inputs/InputAmount'
import InputTextarea from '@/components/Inputs/InputTextarea'
import { user as userQuery } from '@/graphql/queries'
@ -144,7 +144,7 @@ import { COMMUNITY_NAME } from '@/config'
export default {
name: 'TransactionForm',
components: {
InputEmail,
InputIdentifier,
InputAmount,
InputTextarea,
},

View File

@ -0,0 +1,68 @@
<template>
<validation-provider
tag="div"
:rules="rules"
:name="name"
v-slot="{ errors, valid, validated, ariaInput, ariaMsg }"
>
<b-form-group :label="label" :label-for="labelFor" data-test="input-identifier">
<b-form-input
v-model="currentValue"
v-bind="ariaInput"
:id="labelFor"
:name="name"
:placeholder="placeholder"
type="text"
:state="validated ? valid : false"
trim
class="bg-248"
:disabled="disabled"
autocomplete="off"
></b-form-input>
<b-form-invalid-feedback v-bind="ariaMsg">
{{ errors[0] }}
</b-form-invalid-feedback>
</b-form-group>
</validation-provider>
</template>
<script>
export default {
name: 'InputEmail',
props: {
rules: {
default: () => {
return {
required: true,
validIdentifier: true,
}
},
},
name: { type: String, required: true },
label: { type: String, required: true },
placeholder: { type: String, required: true },
value: { type: String, required: true },
disabled: { type: Boolean, required: false, default: false },
},
data() {
return {
currentValue: this.value,
}
},
computed: {
labelFor() {
return this.name + '-input-field'
},
},
watch: {
currentValue() {
this.$emit('input', this.currentValue)
},
value() {
if (this.value !== this.currentValue) {
this.currentValue = this.value
}
this.$emit('onValidation')
},
},
}
</script>

View File

@ -142,6 +142,7 @@
"from": "Von",
"generate_now": "Jetzt generieren",
"hours": "Stunden",
"identifier": "Email, Nutzername oder Gradido ID",
"lastname": "Nachname",
"memo": "Nachricht",
"message": "Nachricht",
@ -175,7 +176,8 @@
"is-not": "Du kannst dir selbst keine Gradidos überweisen",
"username-allowed-chars": "Der Nutzername darf nur aus Buchstaben (ohne Umlaute), Zahlen, Binde- oder Unterstrichen bestehen.",
"username-hyphens": "Binde- oder Unterstriche müssen zwischen Buchstaben oder Zahlen stehen.",
"username-unique": "Der Nutzername ist bereits vergeben."
"username-unique": "Der Nutzername ist bereits vergeben.",
"valid-identifier": "Muss eine Email, ein Nutzernamen oder eine gradido ID sein."
},
"your_amount": "Dein Betrag"
},

View File

@ -142,6 +142,7 @@
"from": "from",
"generate_now": "Generate now",
"hours": "Hours",
"identifier": "Email, username or gradido ID",
"lastname": "Lastname",
"memo": "Message",
"message": "Message",
@ -175,7 +176,8 @@
"is-not": "You cannot send Gradidos to yourself",
"username-allowed-chars": "The username may only contain letters, numbers, hyphens or underscores.",
"username-hyphens": "Hyphens or underscores must be in between letters or numbers.",
"username-unique": "This username is already taken."
"username-unique": "This username is already taken.",
"valid-identifier": "Must be a valid email, username or gradido ID."
},
"your_amount": "Your amount"
},

View File

@ -66,8 +66,11 @@ describe('Send', () => {
beforeEach(async () => {
const transactionForm = wrapper.findComponent({ name: 'TransactionForm' })
await transactionForm.findAll('input[type="radio"]').at(0).setChecked()
await transactionForm.find('input[type="email"]').setValue('user@example.org')
await transactionForm.find('input[type="text"]').setValue('23.45')
await transactionForm
.find('[data-test="input-identifier"]')
.find('input')
.setValue('user@example.org')
await transactionForm.find('[data-test="input-amount"]').find('input').setValue('23.45')
await transactionForm.find('textarea').setValue('Make the best of it!')
await transactionForm.find('form').trigger('submit')
await flushPromises()
@ -91,8 +94,12 @@ describe('Send', () => {
})
it('restores the previous data in the formular', () => {
expect(wrapper.find("input[type='email']").vm.$el.value).toBe('user@example.org')
expect(wrapper.find("input[type='text']").vm.$el.value).toBe('23.45')
expect(wrapper.find('[data-test="input-identifier"]').find('input').vm.$el.value).toBe(
'user@example.org',
)
expect(wrapper.find('[data-test="input-amount"]').find('input').vm.$el.value).toBe(
'23.45',
)
expect(wrapper.find('textarea').vm.$el.value).toBe('Make the best of it!')
})
})
@ -175,7 +182,10 @@ describe('Send', () => {
it('has no email input field', () => {
expect(
wrapper.findComponent({ name: 'TransactionForm' }).find('input[type="email"]').exists(),
wrapper
.findComponent({ name: 'TransactionForm' })
.find('[data-test="input-identifier"]')
.exists(),
).toBe(false)
})
@ -183,7 +193,7 @@ describe('Send', () => {
beforeEach(async () => {
jest.clearAllMocks()
const transactionForm = wrapper.findComponent({ name: 'TransactionForm' })
await transactionForm.find('input[type="text"]').setValue('34.56')
await transactionForm.find('[data-test="input-amount"]').find('input').setValue('34.56')
await transactionForm.find('textarea').setValue('Make the best of it!')
await transactionForm.find('form').trigger('submit')
await flushPromises()
@ -243,7 +253,7 @@ describe('Send', () => {
})
const transactionForm = wrapper.findComponent({ name: 'TransactionForm' })
await transactionForm.findAll('input[type="radio"]').at(1).setChecked()
await transactionForm.find('input[type="text"]').setValue('56.78')
await transactionForm.find('[data-test="input-amount"]').find('input').setValue('56.78')
await transactionForm.find('textarea').setValue('Make the best of the link!')
await transactionForm.find('form').trigger('submit')
await flushPromises()

View File

@ -2,6 +2,13 @@ import { configure, extend } from 'vee-validate'
// eslint-disable-next-line camelcase
import { required, email, min, max, is_not } from 'vee-validate/dist/rules'
import { checkUsername } from '@/graphql/queries'
import { validate as validateUuid, version as versionUuid } from 'uuid'
// taken from vee-validate
// eslint-disable-next-line no-useless-escape
const EMAIL_REGEX = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
const USERNAME_REGEX = /^(?=.{3,20}$)[a-zA-Z0-9]+(?:[_-][a-zA-Z0-9]+?)*$/
export const loadAllRules = (i18nCallback, apollo) => {
configure({
@ -141,7 +148,7 @@ export const loadAllRules = (i18nCallback, apollo) => {
extend('usernameUnique', {
validate(value) {
if (!value.match(/^(?=.{3,20}$)[a-zA-Z0-9]+(?:[_-][a-zA-Z0-9]+?)*$/)) return true
if (!value.match(USERNAME_REGEX)) return true
return apollo
.query({
query: checkUsername,
@ -155,4 +162,14 @@ export const loadAllRules = (i18nCallback, apollo) => {
},
message: (_, values) => i18nCallback.t('form.validation.username-unique', values),
})
extend('validIdentifier', {
validate(value) {
const isEmail = !!EMAIL_REGEX.test(value)
const isUsername = !!value.match(USERNAME_REGEX)
const isGradidoId = validateUuid(value) && versionUuid(value) === 4
return isEmail || isUsername || isGradidoId
},
message: (_, values) => i18nCallback.t('form.validation.valid-identifier', values),
})
}

View File

@ -14176,6 +14176,11 @@ uuid@^8.3.0:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
uuid@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
v8-compile-cache@^2.0.3, v8-compile-cache@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"