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',
|
||||
component: () => import('../views/user/Bind.vue'),
|
||||
react: () => import('../views/user/BindPlayers'),
|
||||
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