rewrite registration page with React
This commit is contained in:
parent
6c4f5d61f4
commit
361444ca7e
|
|
@ -65,15 +65,6 @@ class ViewServiceProvider extends ServiceProvider
|
||||||
|
|
||||||
View::composer('shared.foot', Composers\FootComposer::class);
|
View::composer('shared.foot', Composers\FootComposer::class);
|
||||||
|
|
||||||
View::composer('auth.*', function ($view) {
|
|
||||||
$view->with('enable_recaptcha', (bool) option('recaptcha_sitekey'));
|
|
||||||
$view->with(
|
|
||||||
'recaptcha_url',
|
|
||||||
'https://www.recaptcha.net/recaptcha/api.js'
|
|
||||||
.'?onload=vueRecaptchaApiLoaded&render=explicit'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
View::composer(['errors.*', 'setup.*'], function ($view) use ($webpack) {
|
View::composer(['errors.*', 'setup.*'], function ($view) use ($webpack) {
|
||||||
$view->with([
|
$view->with([
|
||||||
'styles' => [
|
'styles' => [
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,6 @@
|
||||||
"use-immer": "^0.3.5",
|
"use-immer": "^0.3.5",
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
"vue-good-table": "^2.18.1",
|
"vue-good-table": "^2.18.1",
|
||||||
"vue-recaptcha": "^1.2.0",
|
|
||||||
"xterm": "^4.4.0",
|
"xterm": "^4.4.0",
|
||||||
"xterm-addon-fit": "^0.3.0"
|
"xterm-addon-fit": "^0.3.0"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
<template>
|
|
||||||
<div v-if="recaptcha" class="row">
|
|
||||||
<div class="d-block ml-2 pb-3">
|
|
||||||
<vue-recaptcha
|
|
||||||
ref="recaptcha"
|
|
||||||
:size="invisible ? 'invisible' : ''"
|
|
||||||
:sitekey="recaptcha"
|
|
||||||
@verify="onVerify"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="d-flex">
|
|
||||||
<div class="form-group mb-3 mr-2">
|
|
||||||
<input
|
|
||||||
ref="captcha"
|
|
||||||
v-model="value"
|
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
:placeholder="$t('auth.captcha')"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<img
|
|
||||||
class="captcha"
|
|
||||||
:src="`${baseUrl}/auth/captcha?v=${time}`"
|
|
||||||
alt="CAPTCHA"
|
|
||||||
:title="$t('auth.change-captcha')"
|
|
||||||
data-placement="top"
|
|
||||||
data-toggle="tooltip"
|
|
||||||
@click="refresh"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import VueRecaptcha from 'vue-recaptcha'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Captcha',
|
|
||||||
components: {
|
|
||||||
VueRecaptcha,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
baseUrl: {
|
|
||||||
type: String,
|
|
||||||
default: blessing.base_url,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
value: '',
|
|
||||||
time: Date.now(),
|
|
||||||
recaptcha: blessing.extra.recaptcha,
|
|
||||||
invisible: blessing.extra.invisible,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
execute() {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
if (this.recaptcha && this.invisible) {
|
|
||||||
this.$refs.recaptcha.$once('verify', resolve)
|
|
||||||
this.$refs.recaptcha.execute()
|
|
||||||
} else {
|
|
||||||
resolve(this.value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
onVerify(response) {
|
|
||||||
this.value = response
|
|
||||||
},
|
|
||||||
refresh() {
|
|
||||||
if (this.recaptcha) {
|
|
||||||
this.$refs.recaptcha.reset()
|
|
||||||
} else {
|
|
||||||
this.time = Date.now()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
@ -96,8 +96,8 @@ export default [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'auth/register',
|
path: 'auth/register',
|
||||||
component: () => import('../views/auth/Register.vue'),
|
react: () => import('../views/auth/Registration'),
|
||||||
el: 'form',
|
el: 'main',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'auth/forgot',
|
path: 'auth/forgot',
|
||||||
|
|
|
||||||
|
|
@ -1,186 +0,0 @@
|
||||||
<template>
|
|
||||||
<form @submit.prevent="submit">
|
|
||||||
<div class="input-group mb-3">
|
|
||||||
<input
|
|
||||||
v-model="email"
|
|
||||||
type="email"
|
|
||||||
class="form-control"
|
|
||||||
:placeholder="$t('auth.email')"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<div class="input-group-append">
|
|
||||||
<div class="input-group-text">
|
|
||||||
<span class="fas fa-envelope" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="input-group mb-3">
|
|
||||||
<input
|
|
||||||
v-model="password"
|
|
||||||
type="password"
|
|
||||||
class="form-control"
|
|
||||||
:placeholder="$t('auth.password')"
|
|
||||||
required
|
|
||||||
minlength="8"
|
|
||||||
maxlength="32"
|
|
||||||
>
|
|
||||||
<div class="input-group-append">
|
|
||||||
<div class="input-group-text">
|
|
||||||
<span class="fas fa-lock" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="input-group mb-3">
|
|
||||||
<input
|
|
||||||
ref="confirm"
|
|
||||||
v-model="confirm"
|
|
||||||
type="password"
|
|
||||||
class="form-control"
|
|
||||||
:placeholder="$t('auth.repeat-pwd')"
|
|
||||||
required
|
|
||||||
minlength="8"
|
|
||||||
maxlength="32"
|
|
||||||
>
|
|
||||||
<div class="input-group-append">
|
|
||||||
<div class="input-group-text">
|
|
||||||
<span class="fas fa-sign-in-alt" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="requirePlayer"
|
|
||||||
class="input-group mb-3"
|
|
||||||
:title="$t('auth.player-name-intro')"
|
|
||||||
data-placement="top"
|
|
||||||
data-toggle="tooltip"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
v-model="playerName"
|
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
:placeholder="$t('auth.player-name')"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<div class="input-group-append">
|
|
||||||
<div class="input-group-text">
|
|
||||||
<span class="fas fa-gamepad" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="input-group mb-3"
|
|
||||||
:title="$t('auth.nickname-intro')"
|
|
||||||
data-placement="top"
|
|
||||||
data-toggle="tooltip"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
v-model="nickname"
|
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
:placeholder="$t('auth.nickname')"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<div class="input-group-append">
|
|
||||||
<div class="input-group-text">
|
|
||||||
<span class="fas fa-gamepad" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<captcha ref="captcha" />
|
|
||||||
|
|
||||||
<div class="alert alert-info" :class="{ 'd-none': !infoMsg }">
|
|
||||||
<i class="icon fas fa-info" />
|
|
||||||
{{ infoMsg }}
|
|
||||||
</div>
|
|
||||||
<div class="alert alert-warning" :class="{ 'd-none': !warningMsg }">
|
|
||||||
<i class="icon fas fa-exclamation-triangle" />
|
|
||||||
{{ warningMsg }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-between mb-3">
|
|
||||||
<a v-t="'auth.login-link'" :href="`${baseUrl}/auth/login`" class="text-center" />
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
class="btn btn-primary"
|
|
||||||
type="submit"
|
|
||||||
:disabled="pending"
|
|
||||||
>
|
|
||||||
<template v-if="pending">
|
|
||||||
<i class="fa fa-spinner fa-spin" /> {{ $t('auth.registering') }}
|
|
||||||
</template>
|
|
||||||
<span v-else>{{ $t('auth.register-button') }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import Captcha from '../../components/Captcha.vue'
|
|
||||||
import emitMounted from '../../components/mixins/emitMounted'
|
|
||||||
import { toast } from '../../scripts/notify'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Register',
|
|
||||||
components: {
|
|
||||||
Captcha,
|
|
||||||
},
|
|
||||||
mixins: [
|
|
||||||
emitMounted,
|
|
||||||
],
|
|
||||||
props: {
|
|
||||||
baseUrl: {
|
|
||||||
type: String,
|
|
||||||
default: blessing.base_url,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data: () => ({
|
|
||||||
email: '',
|
|
||||||
password: '',
|
|
||||||
confirm: '',
|
|
||||||
nickname: '',
|
|
||||||
playerName: '',
|
|
||||||
infoMsg: '',
|
|
||||||
warningMsg: '',
|
|
||||||
pending: false,
|
|
||||||
requirePlayer: blessing.extra.player,
|
|
||||||
}),
|
|
||||||
methods: {
|
|
||||||
async submit() {
|
|
||||||
const {
|
|
||||||
email, password, confirm, playerName, nickname,
|
|
||||||
} = this
|
|
||||||
|
|
||||||
if (password !== confirm) {
|
|
||||||
this.infoMsg = this.$t('auth.invalidConfirmPwd')
|
|
||||||
this.$refs.confirm.focus()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.pending = true
|
|
||||||
const { code, message } = await this.$http.post(
|
|
||||||
'/auth/register',
|
|
||||||
Object.assign({
|
|
||||||
email,
|
|
||||||
password,
|
|
||||||
captcha: await this.$refs.captcha.execute(),
|
|
||||||
}, this.requirePlayer ? { player_name: playerName } : { nickname }),
|
|
||||||
)
|
|
||||||
if (code === 0) {
|
|
||||||
toast.success(message)
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location = `${blessing.base_url}/user`
|
|
||||||
}, 1000)
|
|
||||||
} else {
|
|
||||||
this.infoMsg = ''
|
|
||||||
this.warningMsg = message
|
|
||||||
this.$refs.captcha.refresh()
|
|
||||||
this.pending = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
182
resources/assets/src/views/auth/Registration.tsx
Normal file
182
resources/assets/src/views/auth/Registration.tsx
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
import React, { useState, useRef } from 'react'
|
||||||
|
import { hot } from 'react-hot-loader/root'
|
||||||
|
import useBlessingExtra from '@/scripts/hooks/useBlessingExtra'
|
||||||
|
import { t } from '@/scripts/i18n'
|
||||||
|
import * as fetch from '@/scripts/net'
|
||||||
|
import { toast } from '@/scripts/notify'
|
||||||
|
import Alert from '@/components/Alert'
|
||||||
|
import Captcha from '@/components/Captcha'
|
||||||
|
|
||||||
|
const Registration: React.FC = () => {
|
||||||
|
const [email, setEmail] = useState('')
|
||||||
|
const [password, setPassword] = useState('')
|
||||||
|
const [confirmation, setConfirmation] = useState('')
|
||||||
|
const [nickName, setNickName] = useState('')
|
||||||
|
const [playerName, setPlayerName] = useState('')
|
||||||
|
const [isPending, setIsPending] = useState(false)
|
||||||
|
const [warningMessage, setWarningMessage] = useState('')
|
||||||
|
const requirePlayer = useBlessingExtra<boolean>('player')
|
||||||
|
const confirmationRef = useRef<HTMLInputElement | null>(null)
|
||||||
|
const captchaRef = useRef<Captcha | null>(null)
|
||||||
|
|
||||||
|
const handleEmailChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setEmail(event.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePasswordChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setPassword(event.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleConfirmationChange = (
|
||||||
|
event: React.ChangeEvent<HTMLInputElement>,
|
||||||
|
) => {
|
||||||
|
setConfirmation(event.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleNickNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setNickName(event.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePlayerNameChange = (
|
||||||
|
event: React.ChangeEvent<HTMLInputElement>,
|
||||||
|
) => {
|
||||||
|
setPlayerName(event.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault()
|
||||||
|
setWarningMessage('')
|
||||||
|
|
||||||
|
if (password !== confirmation) {
|
||||||
|
setWarningMessage(t('auth.invalidConfirmPwd'))
|
||||||
|
confirmationRef.current!.focus()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsPending(true)
|
||||||
|
const { code, message } = await fetch.post<fetch.ResponseBody>(
|
||||||
|
'/auth/register',
|
||||||
|
Object.assign(
|
||||||
|
{ email, password, captcha: await captchaRef.current!.execute() },
|
||||||
|
requirePlayer ? { player_name: playerName } : { nickname: nickName },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if (code === 0) {
|
||||||
|
toast.success(message)
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = `${blessing.base_url}/user`
|
||||||
|
}, 3000)
|
||||||
|
} else {
|
||||||
|
setWarningMessage(message)
|
||||||
|
captchaRef.current!.reset()
|
||||||
|
}
|
||||||
|
setIsPending(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="input-group mb-3">
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
required
|
||||||
|
className="form-control"
|
||||||
|
placeholder={t('auth.email')}
|
||||||
|
value={email}
|
||||||
|
onChange={handleEmailChange}
|
||||||
|
/>
|
||||||
|
<div className="input-group-append">
|
||||||
|
<div className="input-group-text">
|
||||||
|
<i className="fas fa-envelope"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="input-group mb-3">
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
required
|
||||||
|
minLength={8}
|
||||||
|
maxLength={32}
|
||||||
|
className="form-control"
|
||||||
|
placeholder={t('auth.password')}
|
||||||
|
value={password}
|
||||||
|
onChange={handlePasswordChange}
|
||||||
|
/>
|
||||||
|
<div className="input-group-append">
|
||||||
|
<div className="input-group-text">
|
||||||
|
<i className="fas fa-lock"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="input-group mb-3">
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
required
|
||||||
|
minLength={8}
|
||||||
|
maxLength={32}
|
||||||
|
className="form-control"
|
||||||
|
placeholder={t('auth.repeat-pwd')}
|
||||||
|
ref={confirmationRef}
|
||||||
|
value={confirmation}
|
||||||
|
onChange={handleConfirmationChange}
|
||||||
|
/>
|
||||||
|
<div className="input-group-append">
|
||||||
|
<div className="input-group-text">
|
||||||
|
<i className="fas fa-sign-in-alt"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{requirePlayer ? (
|
||||||
|
<div className="input-group mb-3" title={t('auth.player-name-intro')}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
className="form-control"
|
||||||
|
placeholder={t('auth.player-name')}
|
||||||
|
value={playerName}
|
||||||
|
onChange={handlePlayerNameChange}
|
||||||
|
/>
|
||||||
|
<div className="input-group-append">
|
||||||
|
<div className="input-group-text">
|
||||||
|
<i className="fas fa-gamepad"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="input-group mb-3" title={t('auth.nickname-intro')}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
className="form-control"
|
||||||
|
placeholder={t('auth.nickname')}
|
||||||
|
value={nickName}
|
||||||
|
onChange={handleNickNameChange}
|
||||||
|
/>
|
||||||
|
<div className="input-group-append">
|
||||||
|
<div className="input-group-text">
|
||||||
|
<i className="fas fa-gamepad"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Captcha ref={captchaRef} />
|
||||||
|
|
||||||
|
<Alert type="warning">{warningMessage}</Alert>
|
||||||
|
|
||||||
|
<div className="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<a href={`${blessing.base_url}/auth/login`}>{t('auth.login-link')}</a>
|
||||||
|
<button className="btn btn-primary" type="submit" disabled={isPending}>
|
||||||
|
{isPending ? (
|
||||||
|
<>
|
||||||
|
<i className="fas fa-spinner fa-spin mr-1"></i>
|
||||||
|
{t('auth.registering')}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
t('auth.register-button')
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default hot(Registration)
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
import Vue from 'vue'
|
|
||||||
import { mount } from '@vue/test-utils'
|
|
||||||
import Captcha from '@/components/Captcha.vue'
|
|
||||||
|
|
||||||
const VueRecaptcha = Vue.extend({
|
|
||||||
methods: {
|
|
||||||
execute() {
|
|
||||||
this.$emit('verify', 'value')
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
test('display recaptcha', () => {
|
|
||||||
blessing.extra = { recaptcha: 'sitekey' }
|
|
||||||
const wrapper = mount(Captcha)
|
|
||||||
expect(wrapper.find('img').exists()).toBeFalse()
|
|
||||||
})
|
|
||||||
|
|
||||||
test('refresh recaptcha', () => {
|
|
||||||
const wrapper = mount<Vue & { refresh(): void }>(Captcha)
|
|
||||||
wrapper.vm.refresh()
|
|
||||||
})
|
|
||||||
|
|
||||||
test('recaptcha verified', () => {
|
|
||||||
const wrapper =
|
|
||||||
mount<Vue & { onVerify(response: string): void, value: string }>(Captcha)
|
|
||||||
wrapper.vm.onVerify('value')
|
|
||||||
expect(wrapper.vm.value).toBe('value')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('invoke recaptcha', async () => {
|
|
||||||
const wrapper = mount<Vue & { execute(): Promise<string> }>(Captcha, { stubs: { VueRecaptcha } })
|
|
||||||
wrapper.setData({ invisible: true })
|
|
||||||
expect(await wrapper.vm.execute()).toBe('value')
|
|
||||||
|
|
||||||
wrapper.setData({ invisible: false, value: 'haha' })
|
|
||||||
expect(await wrapper.vm.execute()).toBe('haha')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('display characters captcha', async () => {
|
|
||||||
blessing.extra = {}
|
|
||||||
const wrapper = mount<Vue & { execute(): Promise<string> }>(Captcha)
|
|
||||||
expect(wrapper.find('img').exists()).toBeTrue()
|
|
||||||
const input = wrapper.find('input')
|
|
||||||
input.setValue('abc')
|
|
||||||
expect(await wrapper.vm.execute()).toBe('abc')
|
|
||||||
|
|
||||||
wrapper.setData({ invisible: true })
|
|
||||||
input.setValue('123')
|
|
||||||
expect(await wrapper.vm.execute()).toBe('123')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('refresh captcha', () => {
|
|
||||||
jest.spyOn(Date, 'now')
|
|
||||||
const wrapper = mount(Captcha)
|
|
||||||
wrapper.find('img').trigger('click')
|
|
||||||
expect(Date.now).toBeCalledTimes(2)
|
|
||||||
})
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
import Vue from 'vue'
|
|
||||||
import { mount } from '@vue/test-utils'
|
|
||||||
import { flushPromises } from '../../utils'
|
|
||||||
import { toast } from '@/scripts/notify'
|
|
||||||
import Register from '@/views/auth/Register.vue'
|
|
||||||
|
|
||||||
jest.mock('@/scripts/notify')
|
|
||||||
|
|
||||||
window.blessing.extra = { player: false }
|
|
||||||
|
|
||||||
const Captcha = Vue.extend({
|
|
||||||
methods: {
|
|
||||||
execute() {
|
|
||||||
return Promise.resolve('captcha')
|
|
||||||
},
|
|
||||||
refresh() { /* */ },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
test('require player name', () => {
|
|
||||||
window.blessing.extra = { player: true }
|
|
||||||
|
|
||||||
const wrapper = mount(Register)
|
|
||||||
expect(wrapper.findAll('[type="text"]').at(0)
|
|
||||||
.attributes('placeholder')).toBe('auth.player-name')
|
|
||||||
|
|
||||||
window.blessing.extra = { player: false }
|
|
||||||
})
|
|
||||||
|
|
||||||
test('register', async () => {
|
|
||||||
jest.spyOn(Date, 'now')
|
|
||||||
Vue.prototype.$http.post
|
|
||||||
.mockResolvedValueOnce({ code: 1, message: 'fail' })
|
|
||||||
.mockResolvedValueOnce({ code: 0, message: 'ok' })
|
|
||||||
const wrapper = mount(Register, { stubs: { Captcha } })
|
|
||||||
const form = wrapper.find('form')
|
|
||||||
const info = wrapper.find('.alert-info')
|
|
||||||
const warning = wrapper.find('.alert-warning')
|
|
||||||
|
|
||||||
wrapper.find('[type="email"]').setValue('a@b.c')
|
|
||||||
wrapper.findAll('[type="password"]').at(0)
|
|
||||||
.setValue('12345678')
|
|
||||||
wrapper.findAll('[type="password"]').at(1)
|
|
||||||
.setValue('123456')
|
|
||||||
form.trigger('submit')
|
|
||||||
expect(Vue.prototype.$http.post).not.toBeCalled()
|
|
||||||
expect(info.text()).toBe('auth.invalidConfirmPwd')
|
|
||||||
|
|
||||||
wrapper.findAll('[type="password"]').at(1)
|
|
||||||
.setValue('12345678')
|
|
||||||
wrapper.findAll('[type="text"]').at(0)
|
|
||||||
.setValue('abc')
|
|
||||||
form.trigger('submit')
|
|
||||||
await flushPromises()
|
|
||||||
expect(Vue.prototype.$http.post).toBeCalledWith(
|
|
||||||
'/auth/register',
|
|
||||||
{
|
|
||||||
email: 'a@b.c',
|
|
||||||
password: '12345678',
|
|
||||||
nickname: 'abc',
|
|
||||||
captcha: 'captcha',
|
|
||||||
},
|
|
||||||
)
|
|
||||||
await flushPromises()
|
|
||||||
expect(warning.text()).toBe('fail')
|
|
||||||
|
|
||||||
form.trigger('submit')
|
|
||||||
await flushPromises()
|
|
||||||
jest.runAllTimers()
|
|
||||||
expect(toast.success).toBeCalledWith('ok')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('register with player name', async () => {
|
|
||||||
window.blessing.extra = { player: true }
|
|
||||||
Vue.prototype.$http.post.mockResolvedValue({ code: 0, message: 'ok' })
|
|
||||||
const wrapper = mount(Register, { stubs: { Captcha } })
|
|
||||||
const form = wrapper.find('form')
|
|
||||||
wrapper.find('[type="email"]').setValue('a@b.c')
|
|
||||||
wrapper.findAll('[type="password"]').at(0)
|
|
||||||
.setValue('12345678')
|
|
||||||
wrapper.findAll('[type="password"]').at(1)
|
|
||||||
.setValue('12345678')
|
|
||||||
wrapper.findAll('[type="text"]').at(0)
|
|
||||||
.setValue('abc')
|
|
||||||
form.trigger('submit')
|
|
||||||
await flushPromises()
|
|
||||||
expect(Vue.prototype.$http.post).toBeCalledWith(
|
|
||||||
'/auth/register',
|
|
||||||
{
|
|
||||||
email: 'a@b.c',
|
|
||||||
password: '12345678',
|
|
||||||
player_name: 'abc',
|
|
||||||
captcha: 'captcha',
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
139
resources/assets/tests/views/auth/Registration.test.tsx
Normal file
139
resources/assets/tests/views/auth/Registration.test.tsx
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { render, waitFor, fireEvent } from '@testing-library/react'
|
||||||
|
import { t } from '@/scripts/i18n'
|
||||||
|
import * as fetch from '@/scripts/net'
|
||||||
|
import Registration from '@/views/auth/Registration'
|
||||||
|
|
||||||
|
jest.mock('@/scripts/net')
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
window.blessing.extra = { player: false }
|
||||||
|
})
|
||||||
|
|
||||||
|
test('confirmation is not matched', () => {
|
||||||
|
const { getByText, getByPlaceholderText, queryByText } = render(
|
||||||
|
<Registration />,
|
||||||
|
)
|
||||||
|
|
||||||
|
fireEvent.input(getByPlaceholderText(t('auth.email')), {
|
||||||
|
target: { value: 'a@b.c' },
|
||||||
|
})
|
||||||
|
fireEvent.input(getByPlaceholderText(t('auth.password')), {
|
||||||
|
target: { value: 'password' },
|
||||||
|
})
|
||||||
|
fireEvent.input(getByPlaceholderText(t('auth.repeat-pwd')), {
|
||||||
|
target: { value: 'password1' },
|
||||||
|
})
|
||||||
|
fireEvent.input(getByPlaceholderText(t('auth.nickname')), {
|
||||||
|
target: { value: 't' },
|
||||||
|
})
|
||||||
|
fireEvent.input(getByPlaceholderText(t('auth.captcha')), {
|
||||||
|
target: { value: 'a' },
|
||||||
|
})
|
||||||
|
fireEvent.click(getByText(t('auth.register-button')))
|
||||||
|
|
||||||
|
expect(queryByText(t('auth.invalidConfirmPwd'))).toBeInTheDocument()
|
||||||
|
expect(fetch.post).not.toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('succeeded', async () => {
|
||||||
|
fetch.post.mockResolvedValue({ code: 0, message: 'ok' })
|
||||||
|
const { getByText, getByPlaceholderText, getByRole, queryByText } = render(
|
||||||
|
<Registration />,
|
||||||
|
)
|
||||||
|
|
||||||
|
fireEvent.input(getByPlaceholderText(t('auth.email')), {
|
||||||
|
target: { value: 'a@b.c' },
|
||||||
|
})
|
||||||
|
fireEvent.input(getByPlaceholderText(t('auth.password')), {
|
||||||
|
target: { value: 'password' },
|
||||||
|
})
|
||||||
|
fireEvent.input(getByPlaceholderText(t('auth.repeat-pwd')), {
|
||||||
|
target: { value: 'password' },
|
||||||
|
})
|
||||||
|
fireEvent.input(getByPlaceholderText(t('auth.nickname')), {
|
||||||
|
target: { value: 't' },
|
||||||
|
})
|
||||||
|
fireEvent.input(getByPlaceholderText(t('auth.captcha')), {
|
||||||
|
target: { value: 'a' },
|
||||||
|
})
|
||||||
|
fireEvent.click(getByText(t('auth.register-button')))
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(fetch.post).toBeCalledWith('/auth/register', {
|
||||||
|
email: 'a@b.c',
|
||||||
|
password: 'password',
|
||||||
|
nickname: 't',
|
||||||
|
captcha: 'a',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
expect(queryByText('ok')).toBeInTheDocument()
|
||||||
|
expect(getByRole('status')).toHaveClass('alert-success')
|
||||||
|
jest.runAllTimers()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('failed', async () => {
|
||||||
|
fetch.post.mockResolvedValue({ code: 1, message: 'failed' })
|
||||||
|
const { getByText, getByPlaceholderText, queryByText } = render(
|
||||||
|
<Registration />,
|
||||||
|
)
|
||||||
|
|
||||||
|
fireEvent.input(getByPlaceholderText(t('auth.email')), {
|
||||||
|
target: { value: 'a@b.c' },
|
||||||
|
})
|
||||||
|
fireEvent.input(getByPlaceholderText(t('auth.password')), {
|
||||||
|
target: { value: 'password' },
|
||||||
|
})
|
||||||
|
fireEvent.input(getByPlaceholderText(t('auth.repeat-pwd')), {
|
||||||
|
target: { value: 'password' },
|
||||||
|
})
|
||||||
|
fireEvent.input(getByPlaceholderText(t('auth.nickname')), {
|
||||||
|
target: { value: 't' },
|
||||||
|
})
|
||||||
|
fireEvent.input(getByPlaceholderText(t('auth.captcha')), {
|
||||||
|
target: { value: 'a' },
|
||||||
|
})
|
||||||
|
fireEvent.click(getByText(t('auth.register-button')))
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(fetch.post).toBeCalledWith('/auth/register', {
|
||||||
|
email: 'a@b.c',
|
||||||
|
password: 'password',
|
||||||
|
nickname: 't',
|
||||||
|
captcha: 'a',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
expect(queryByText('failed')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('register with new player', async () => {
|
||||||
|
window.blessing.extra = { player: true }
|
||||||
|
fetch.post.mockResolvedValue({ code: 0, message: 'ok' })
|
||||||
|
const { getByText, getByPlaceholderText, queryByText } = render(
|
||||||
|
<Registration />,
|
||||||
|
)
|
||||||
|
|
||||||
|
fireEvent.input(getByPlaceholderText(t('auth.email')), {
|
||||||
|
target: { value: 'a@b.c' },
|
||||||
|
})
|
||||||
|
fireEvent.input(getByPlaceholderText(t('auth.password')), {
|
||||||
|
target: { value: 'password' },
|
||||||
|
})
|
||||||
|
fireEvent.input(getByPlaceholderText(t('auth.repeat-pwd')), {
|
||||||
|
target: { value: 'password' },
|
||||||
|
})
|
||||||
|
fireEvent.input(getByPlaceholderText(t('auth.player-name')), {
|
||||||
|
target: { value: 'player' },
|
||||||
|
})
|
||||||
|
fireEvent.input(getByPlaceholderText(t('auth.captcha')), {
|
||||||
|
target: { value: 'a' },
|
||||||
|
})
|
||||||
|
fireEvent.click(getByText(t('auth.register-button')))
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(fetch.post).toBeCalledWith('/auth/register', {
|
||||||
|
email: 'a@b.c',
|
||||||
|
password: 'password',
|
||||||
|
player_name: 'player',
|
||||||
|
captcha: 'a',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
expect(queryByText('ok')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
@ -6,14 +6,11 @@
|
||||||
<p class="login-box-msg">
|
<p class="login-box-msg">
|
||||||
{{ trans('auth.register.message', {sitename: site_name}) }}
|
{{ trans('auth.register.message', {sitename: site_name}) }}
|
||||||
</p>
|
</p>
|
||||||
<form></form>
|
<main></main>
|
||||||
{{ include('auth.oauth') }}
|
{{ include('auth.oauth') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block before_foot %}
|
{% block before_foot %}
|
||||||
{% if enable_recaptcha %}
|
|
||||||
<script src="{{ recaptcha_url }}" async defer></script>
|
|
||||||
{% endif %}
|
|
||||||
<script>
|
<script>
|
||||||
Object.defineProperty(blessing, 'extra', {
|
Object.defineProperty(blessing, 'extra', {
|
||||||
configurable: false,
|
configurable: false,
|
||||||
|
|
|
||||||
|
|
@ -10768,11 +10768,6 @@ vue-loader@^15.8.3:
|
||||||
vue-hot-reload-api "^2.3.0"
|
vue-hot-reload-api "^2.3.0"
|
||||||
vue-style-loader "^4.1.0"
|
vue-style-loader "^4.1.0"
|
||||||
|
|
||||||
vue-recaptcha@^1.2.0:
|
|
||||||
version "1.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/vue-recaptcha/-/vue-recaptcha-1.2.0.tgz#a30ab08b4c79326d2e3ab1c1e76c367e02e6eb35"
|
|
||||||
integrity sha512-zgt8bAmlHbLT2XY0diwA/UgGcbp1cOYXJ8za17WM1zlOC8EiO0mpGvYzXaR1aeU0gaiv1qLO6GOUwQVVAjgwMw==
|
|
||||||
|
|
||||||
vue-style-loader@^4.1.0:
|
vue-style-loader@^4.1.0:
|
||||||
version "4.1.2"
|
version "4.1.2"
|
||||||
resolved "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.2.tgz#dedf349806f25ceb4e64f3ad7c0a44fba735fcf8"
|
resolved "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.2.tgz#dedf349806f25ceb4e64f3ad7c0a44fba735fcf8"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user