From ec948ef1c22f7cc02e5c09ce32b90ffee911b607 Mon Sep 17 00:00:00 2001 From: Pig Fang Date: Sun, 9 Feb 2020 11:05:02 +0800 Subject: [PATCH] build "bind players" page with React --- resources/assets/src/scripts/route.tsx | 2 +- resources/assets/src/views/user/Bind.vue | 83 ---------------- .../assets/src/views/user/BindPlayers.tsx | 98 +++++++++++++++++++ .../assets/tests/views/user/Bind.test.ts | 43 -------- .../tests/views/user/BindPlayers.test.tsx | 76 ++++++++++++++ 5 files changed, 175 insertions(+), 127 deletions(-) delete mode 100644 resources/assets/src/views/user/Bind.vue create mode 100644 resources/assets/src/views/user/BindPlayers.tsx delete mode 100644 resources/assets/tests/views/user/Bind.test.ts create mode 100644 resources/assets/tests/views/user/BindPlayers.test.tsx diff --git a/resources/assets/src/scripts/route.tsx b/resources/assets/src/scripts/route.tsx index 2d6b8bb4..de640ced 100644 --- a/resources/assets/src/scripts/route.tsx +++ b/resources/assets/src/scripts/route.tsx @@ -37,7 +37,7 @@ export default [ }, { path: 'user/player/bind', - component: () => import('../views/user/Bind.vue'), + react: () => import('../views/user/BindPlayers'), el: 'form', }, { diff --git a/resources/assets/src/views/user/Bind.vue b/resources/assets/src/views/user/Bind.vue deleted file mode 100644 index 5fceebbf..00000000 --- a/resources/assets/src/views/user/Bind.vue +++ /dev/null @@ -1,83 +0,0 @@ - - - - - diff --git a/resources/assets/src/views/user/BindPlayers.tsx b/resources/assets/src/views/user/BindPlayers.tsx new file mode 100644 index 00000000..111427a7 --- /dev/null +++ b/resources/assets/src/views/user/BindPlayers.tsx @@ -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([]) + 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>( + '/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) => { + event.preventDefault() + setIsPending(true) + + const { code, message } = await fetch.post( + '/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 ? ( + + ) : ( +
+ {players.length > 0 ? ( + <> +

{t('user.bindExistedPlayer')}

+
+ {players.map(player => ( + + ))} +
+ + ) : ( + <> +

{t('user.bindNewPlayer')}

+ setSelected(e.target.value)} + /> + + )} + +
+ ) +} + +export default hot(BindPlayers) diff --git a/resources/assets/tests/views/user/Bind.test.ts b/resources/assets/tests/views/user/Bind.test.ts deleted file mode 100644 index d2f4dd3a..00000000 --- a/resources/assets/tests/views/user/Bind.test.ts +++ /dev/null @@ -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' }) -}) diff --git a/resources/assets/tests/views/user/BindPlayers.test.tsx b/resources/assets/tests/views/user/BindPlayers.test.tsx new file mode 100644 index 00000000..e2184a3f --- /dev/null +++ b/resources/assets/tests/views/user/BindPlayers.test.tsx @@ -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() + 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() + 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( + , + ) + 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( + , + ) + 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'))) + }) +})