From fd2e3f2c1b78e11418b0d2286ebd30d1194cbe0d Mon Sep 17 00:00:00 2001 From: Pig Fang Date: Mon, 30 Mar 2020 10:12:35 +0800 Subject: [PATCH] rewrite "password reset" with React --- resources/assets/src/scripts/route.tsx | 4 +- resources/assets/src/views/auth/Reset.tsx | 105 +++++++++++++++++ resources/assets/src/views/auth/Reset.vue | 106 ------------------ .../assets/tests/views/auth/Reset.test.ts | 44 -------- .../assets/tests/views/auth/Reset.test.tsx | 66 +++++++++++ resources/views/auth/reset.twig | 2 +- 6 files changed, 174 insertions(+), 153 deletions(-) create mode 100644 resources/assets/src/views/auth/Reset.tsx delete mode 100644 resources/assets/src/views/auth/Reset.vue delete mode 100644 resources/assets/tests/views/auth/Reset.test.ts create mode 100644 resources/assets/tests/views/auth/Reset.test.tsx diff --git a/resources/assets/src/scripts/route.tsx b/resources/assets/src/scripts/route.tsx index 3e0756a4..f6da9d0d 100644 --- a/resources/assets/src/scripts/route.tsx +++ b/resources/assets/src/scripts/route.tsx @@ -106,8 +106,8 @@ export default [ }, { path: 'auth/reset/(\\d+)', - component: () => import('../views/auth/Reset.vue'), - el: 'form', + react: () => import('../views/auth/Reset'), + el: 'main', }, { path: 'skinlib', diff --git a/resources/assets/src/views/auth/Reset.tsx b/resources/assets/src/views/auth/Reset.tsx new file mode 100644 index 00000000..4682c553 --- /dev/null +++ b/resources/assets/src/views/auth/Reset.tsx @@ -0,0 +1,105 @@ +import React, { useState } from 'react' +import { hot } from 'react-hot-loader/root' +import { t } from '@/scripts/i18n' +import * as fetch from '@/scripts/net' +import { toast } from '@/scripts/notify' +import Alert from '@/components/Alert' + +const Reset: React.FC = () => { + const [password, setPassword] = useState('') + const [confirmation, setConfirmation] = useState('') + const [warningMessage, setWarningMessage] = useState('') + const [isPending, setIsPending] = useState(false) + + const handlePasswordChange = (event: React.ChangeEvent) => { + setPassword(event.target.value) + } + + const handleConfirmationChange = ( + event: React.ChangeEvent, + ) => { + setConfirmation(event.target.value) + } + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault() + + if (password !== confirmation) { + setWarningMessage(t('auth.invalidConfirmPwd')) + return + } + + setIsPending(true) + const { code, message } = await fetch.post( + location.href.replace(blessing.base_url, ''), + { password }, + ) + if (code === 0) { + toast.success(message) + setTimeout(() => { + window.location.href = `${blessing.base_url}/auth/login` + }, 2000) + } else { + setWarningMessage(message) + setIsPending(false) + } + } + + return ( +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+ + {warningMessage} + + +
+ ) +} + +export default hot(Reset) diff --git a/resources/assets/src/views/auth/Reset.vue b/resources/assets/src/views/auth/Reset.vue deleted file mode 100644 index 08790850..00000000 --- a/resources/assets/src/views/auth/Reset.vue +++ /dev/null @@ -1,106 +0,0 @@ - - - diff --git a/resources/assets/tests/views/auth/Reset.test.ts b/resources/assets/tests/views/auth/Reset.test.ts deleted file mode 100644 index c7c9762c..00000000 --- a/resources/assets/tests/views/auth/Reset.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -import Vue from 'vue' -import { mount } from '@vue/test-utils' -import { flushPromises } from '../../utils' -import { toast } from '@/scripts/notify' -import Reset from '@/views/auth/Reset.vue' - -jest.mock('@/scripts/notify') - -test('reset password', async () => { - Vue.prototype.$http.post - .mockResolvedValueOnce({ code: 1, message: 'fail' }) - .mockResolvedValueOnce({ code: 0, message: 'ok' }) - const wrapper = mount(Reset, { - mocks: { - $route: ['/auth/reset/1', '1'], - }, - }) - const form = wrapper.find('form') - const info = wrapper.find('.alert-info') - const warning = wrapper.find('.alert-warning') - - 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') - form.trigger('submit') - expect(Vue.prototype.$http.post).toBeCalledWith( - '/auth/reset/1', // Ignore `location.search` - { password: '12345678' }, - ) - await flushPromises() - expect(warning.text()).toBe('fail') - - form.trigger('submit') - await flushPromises() - expect(toast.success).toBeCalledWith('ok') - jest.runAllTimers() -}) diff --git a/resources/assets/tests/views/auth/Reset.test.tsx b/resources/assets/tests/views/auth/Reset.test.tsx new file mode 100644 index 00000000..a3f3893b --- /dev/null +++ b/resources/assets/tests/views/auth/Reset.test.tsx @@ -0,0 +1,66 @@ +import React from 'react' +import { render, waitFor, fireEvent } from '@testing-library/react' +import { t } from '@/scripts/i18n' +import * as fetch from '@/scripts/net' +import Reset from '@/views/auth/Reset' + +jest.mock('@/scripts/net') + +test('confirmation is not matched', () => { + const { getByText, getByPlaceholderText, queryByText } = render() + + fireEvent.input(getByPlaceholderText(t('auth.password')), { + target: { value: 'password' }, + }) + fireEvent.input(getByPlaceholderText(t('auth.repeat-pwd')), { + target: { value: 'password1' }, + }) + fireEvent.click(getByText(t('auth.reset-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( + , + ) + + fireEvent.input(getByPlaceholderText(t('auth.password')), { + target: { value: 'password' }, + }) + fireEvent.input(getByPlaceholderText(t('auth.repeat-pwd')), { + target: { value: 'password' }, + }) + fireEvent.click(getByText(t('auth.reset-button'))) + await waitFor(() => + expect(fetch.post).toBeCalledWith( + location.href.replace(blessing.base_url, ''), + { password: 'password' }, + ), + ) + 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() + + fireEvent.input(getByPlaceholderText(t('auth.password')), { + target: { value: 'password' }, + }) + fireEvent.input(getByPlaceholderText(t('auth.repeat-pwd')), { + target: { value: 'password' }, + }) + fireEvent.click(getByText(t('auth.reset-button'))) + await waitFor(() => + expect(fetch.post).toBeCalledWith( + location.href.replace(blessing.base_url, ''), + { password: 'password' }, + ), + ) + expect(queryByText('failed')).toBeInTheDocument() +}) diff --git a/resources/views/auth/reset.twig b/resources/views/auth/reset.twig index e27f4cd4..fecbd54b 100644 --- a/resources/views/auth/reset.twig +++ b/resources/views/auth/reset.twig @@ -6,5 +6,5 @@ -
+
{% endblock %}