build "bind players" page with React
This commit is contained in:
parent
6a7d9d0fde
commit
ec948ef1c2
|
|
@ -37,7 +37,7 @@ export default [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'user/player/bind',
|
path: 'user/player/bind',
|
||||||
component: () => import('../views/user/Bind.vue'),
|
react: () => import('../views/user/BindPlayers'),
|
||||||
el: 'form',
|
el: 'form',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
<template>
|
|
||||||
<form @submit.prevent="submit">
|
|
||||||
<div v-if="players.length">
|
|
||||||
<p v-t="'user.bindExistedPlayer'" />
|
|
||||||
<div class="form-group mb-3">
|
|
||||||
<select v-model="selected" class="form-control player-select">
|
|
||||||
<option v-for="name in players" :key="name">{{ name }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else>
|
|
||||||
<p v-t="'user.bindNewPlayer'" />
|
|
||||||
<div class="form-group mb-3">
|
|
||||||
<input
|
|
||||||
v-model="selected"
|
|
||||||
class="form-control"
|
|
||||||
:placeholder="$t('general.player.player-name')"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-show="message" class="alert alert-warning" v-text="message" />
|
|
||||||
|
|
||||||
<button class="btn btn-primary float-right" type="submit" :disabled="pending">
|
|
||||||
<template v-if="pending">
|
|
||||||
<i class="fa fa-spinner fa-spin" /> {{ $t('general.wait') }}
|
|
||||||
</template>
|
|
||||||
<span v-else>{{ $t('general.submit') }}</span>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import emitMounted from '../../components/mixins/emitMounted'
|
|
||||||
import { showModal } from '../../scripts/notify'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'BindPlayer',
|
|
||||||
mixins: [
|
|
||||||
emitMounted,
|
|
||||||
],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
players: [],
|
|
||||||
selected: '',
|
|
||||||
pending: false,
|
|
||||||
message: '',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.fetchPlayers()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async fetchPlayers() {
|
|
||||||
const players = (await this.$http.get('/user/player/list')).data
|
|
||||||
this.players = players.map(player => player.name)
|
|
||||||
this.selected = this.players[0]
|
|
||||||
},
|
|
||||||
async submit() {
|
|
||||||
this.pending = true
|
|
||||||
const { code, message } = await this.$http.post(
|
|
||||||
'/user/player/bind',
|
|
||||||
{ player: this.selected },
|
|
||||||
)
|
|
||||||
this.pending = false
|
|
||||||
if (code === 0) {
|
|
||||||
await showModal({ mode: 'alert', text: message })
|
|
||||||
window.location.href = `${blessing.base_url}/user`
|
|
||||||
} else {
|
|
||||||
this.message = message
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus">
|
|
||||||
@import "../../styles/auth.styl"
|
|
||||||
|
|
||||||
.player-select
|
|
||||||
width 100%
|
|
||||||
</style>
|
|
||||||
98
resources/assets/src/views/user/BindPlayers.tsx
Normal file
98
resources/assets/src/views/user/BindPlayers.tsx
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
import React, { useState, useEffect } from 'react'
|
||||||
|
import { hot } from 'react-hot-loader/root'
|
||||||
|
import { t } from '@/scripts/i18n'
|
||||||
|
import * as fetch from '@/scripts/net'
|
||||||
|
import { showModal } from '@/scripts/notify'
|
||||||
|
import { Player } from '@/scripts/types'
|
||||||
|
import Loading from '@/components/Loading'
|
||||||
|
|
||||||
|
const BindPlayers: React.FC = () => {
|
||||||
|
const [players, setPlayers] = useState<string[]>([])
|
||||||
|
const [selected, setSelected] = useState('')
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
const [isPending, setIsPending] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const getPlayers = async () => {
|
||||||
|
setIsLoading(true)
|
||||||
|
const response = await fetch.get<fetch.ResponseBody<Player[]>>(
|
||||||
|
'/user/player/list',
|
||||||
|
)
|
||||||
|
const players = response.data.map(player => player.name)
|
||||||
|
setPlayers(players)
|
||||||
|
setSelected(players[0])
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
getPlayers()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault()
|
||||||
|
setIsPending(true)
|
||||||
|
|
||||||
|
const { code, message } = await fetch.post<fetch.ResponseBody>(
|
||||||
|
'/user/player/bind',
|
||||||
|
{ player: selected },
|
||||||
|
)
|
||||||
|
if (code === 0) {
|
||||||
|
await showModal({ mode: 'alert', text: message })
|
||||||
|
window.location.href = `${blessing.base_url}/user`
|
||||||
|
} else {
|
||||||
|
showModal({ mode: 'alert', text: message })
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsPending(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return isLoading ? (
|
||||||
|
<Loading />
|
||||||
|
) : (
|
||||||
|
<form method="post" onSubmit={handleSubmit}>
|
||||||
|
{players.length > 0 ? (
|
||||||
|
<>
|
||||||
|
<p>{t('user.bindExistedPlayer')}</p>
|
||||||
|
<div className="mb-3">
|
||||||
|
{players.map(player => (
|
||||||
|
<label className="d-block mb-1">
|
||||||
|
<input
|
||||||
|
key={player}
|
||||||
|
type="radio"
|
||||||
|
className="mr-2"
|
||||||
|
checked={selected === player}
|
||||||
|
onChange={() => setSelected(player)}
|
||||||
|
/>
|
||||||
|
{player}
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<p>{t('user.bindNewPlayer')}</p>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control mb-3"
|
||||||
|
placeholder={t('general.player.player-name')}
|
||||||
|
onChange={e => setSelected(e.target.value)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
className="btn btn-primary float-right"
|
||||||
|
type="submit"
|
||||||
|
disabled={isPending}
|
||||||
|
>
|
||||||
|
{isPending ? (
|
||||||
|
<>
|
||||||
|
<i className="fas fa-spinner fa-spin mr-1"></i>
|
||||||
|
{t('general.wait')}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
t('general.submit')
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default hot(BindPlayers)
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
import Vue from 'vue'
|
|
||||||
import { mount } from '@vue/test-utils'
|
|
||||||
import { flushPromises } from '../../utils'
|
|
||||||
import { showModal } from '@/scripts/notify'
|
|
||||||
import Bind from '@/views/user/Bind.vue'
|
|
||||||
|
|
||||||
jest.mock('@/scripts/notify')
|
|
||||||
|
|
||||||
test('list existed players', async () => {
|
|
||||||
Vue.prototype.$http.get
|
|
||||||
.mockResolvedValue({ data: [{ name: 'a' }, { name: 'b' }] })
|
|
||||||
const wrapper = mount(Bind)
|
|
||||||
await flushPromises()
|
|
||||||
const options = wrapper.findAll('option')
|
|
||||||
expect(options).toHaveLength(2)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('show input box', async () => {
|
|
||||||
Vue.prototype.$http.get.mockResolvedValue({ data: [] })
|
|
||||||
const wrapper = mount(Bind)
|
|
||||||
await flushPromises()
|
|
||||||
const input = wrapper.find('input')
|
|
||||||
expect(input.exists()).toBeTrue()
|
|
||||||
})
|
|
||||||
|
|
||||||
test('submit', async () => {
|
|
||||||
Vue.prototype.$http.get.mockResolvedValue({ data: [] })
|
|
||||||
Vue.prototype.$http.post
|
|
||||||
.mockResolvedValueOnce({ code: 1, message: 'fail' })
|
|
||||||
.mockResolvedValueOnce({ code: 0, message: 'ok' })
|
|
||||||
|
|
||||||
const wrapper = mount(Bind)
|
|
||||||
const form = wrapper.find('form')
|
|
||||||
wrapper.find('input').setValue('abc')
|
|
||||||
|
|
||||||
form.trigger('submit')
|
|
||||||
await flushPromises()
|
|
||||||
expect(wrapper.find('.alert').text()).toBe('fail')
|
|
||||||
|
|
||||||
form.trigger('submit')
|
|
||||||
await flushPromises()
|
|
||||||
expect(showModal).toBeCalledWith({ mode: 'alert', text: 'ok' })
|
|
||||||
})
|
|
||||||
76
resources/assets/tests/views/user/BindPlayers.test.tsx
Normal file
76
resources/assets/tests/views/user/BindPlayers.test.tsx
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { render, fireEvent, wait } from '@testing-library/react'
|
||||||
|
import { t } from '@/scripts/i18n'
|
||||||
|
import * as fetch from '@/scripts/net'
|
||||||
|
import BindPlayers from '@/views/user/BindPlayers'
|
||||||
|
|
||||||
|
jest.mock('@/scripts/net')
|
||||||
|
|
||||||
|
test('loading indicator', () => {
|
||||||
|
fetch.get.mockResolvedValue({ data: [] })
|
||||||
|
const { queryByTitle } = render(<BindPlayers />)
|
||||||
|
expect(queryByTitle('Loading...')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('submit', () => {
|
||||||
|
it('have existed players', async () => {
|
||||||
|
fetch.get.mockResolvedValue({
|
||||||
|
data: [{ name: 'kumiko' }, { name: 'reina' }],
|
||||||
|
})
|
||||||
|
fetch.post.mockResolvedValue({ code: 0, message: 'success' })
|
||||||
|
|
||||||
|
const { getByText, getByLabelText, queryByText } = render(<BindPlayers />)
|
||||||
|
await wait()
|
||||||
|
|
||||||
|
fireEvent.click(getByLabelText('reina'))
|
||||||
|
fireEvent.click(getByText(t('general.submit')))
|
||||||
|
await wait()
|
||||||
|
|
||||||
|
expect(fetch.post).toBeCalledWith('/user/player/bind', { player: 'reina' })
|
||||||
|
expect(queryByText('success')).toBeInTheDocument()
|
||||||
|
|
||||||
|
fireEvent.click(getByText(t('general.confirm')))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('no existed players', async () => {
|
||||||
|
fetch.get.mockResolvedValue({ data: [] })
|
||||||
|
fetch.post.mockResolvedValue({ code: 0, message: 'success' })
|
||||||
|
|
||||||
|
const { getByText, getByPlaceholderText, queryByText } = render(
|
||||||
|
<BindPlayers />,
|
||||||
|
)
|
||||||
|
await wait()
|
||||||
|
|
||||||
|
fireEvent.input(getByPlaceholderText(t('general.player.player-name')), {
|
||||||
|
target: { value: 'kumiko' },
|
||||||
|
})
|
||||||
|
fireEvent.click(getByText(t('general.submit')))
|
||||||
|
await wait()
|
||||||
|
|
||||||
|
expect(fetch.post).toBeCalledWith('/user/player/bind', { player: 'kumiko' })
|
||||||
|
expect(queryByText('success')).toBeInTheDocument()
|
||||||
|
|
||||||
|
fireEvent.click(getByText(t('general.confirm')))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('failed', async () => {
|
||||||
|
fetch.get.mockResolvedValue({ data: [] })
|
||||||
|
fetch.post.mockResolvedValue({ code: 1, message: 'failed' })
|
||||||
|
|
||||||
|
const { getByText, getByPlaceholderText, queryByText } = render(
|
||||||
|
<BindPlayers />,
|
||||||
|
)
|
||||||
|
await wait()
|
||||||
|
|
||||||
|
fireEvent.input(getByPlaceholderText(t('general.player.player-name')), {
|
||||||
|
target: { value: 'kumiko' },
|
||||||
|
})
|
||||||
|
fireEvent.click(getByText(t('general.submit')))
|
||||||
|
await wait()
|
||||||
|
|
||||||
|
expect(fetch.post).toBeCalledWith('/user/player/bind', { player: 'kumiko' })
|
||||||
|
expect(queryByText('failed')).toBeInTheDocument()
|
||||||
|
|
||||||
|
fireEvent.click(getByText(t('general.confirm')))
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
Reference in New Issue
Block a user