build OAuth mgmt page with React
This commit is contained in:
parent
de64694002
commit
93f64b034f
14
resources/assets/src/components/ButtonEdit.tsx
Normal file
14
resources/assets/src/components/ButtonEdit.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import React from 'react'
|
||||
|
||||
interface Props {
|
||||
title?: string
|
||||
onClick: React.MouseEventHandler<HTMLAnchorElement>
|
||||
}
|
||||
|
||||
const ButtonEdit: React.FC<Props> = props => (
|
||||
<a href="#" title={props.title} className="ml-2" onClick={props.onClick}>
|
||||
<i className="fas fa-edit"></i>
|
||||
</a>
|
||||
)
|
||||
|
||||
export default ButtonEdit
|
||||
|
|
@ -36,7 +36,7 @@ export default [
|
|||
},
|
||||
{
|
||||
path: 'user/oauth/manage',
|
||||
component: () => import('../views/user/OAuth.vue'),
|
||||
react: () => import('../views/user/OAuth'),
|
||||
el: '.content > .container-fluid',
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,207 +0,0 @@
|
|||
<template>
|
||||
<div class="container-fluid">
|
||||
<button
|
||||
type="primary"
|
||||
class="btn-create-app btn btn-primary"
|
||||
data-toggle="modal"
|
||||
data-target="#modal-create"
|
||||
>
|
||||
{{ $t('user.oauth.create') }}
|
||||
</button>
|
||||
<vue-good-table
|
||||
:rows="clients"
|
||||
:columns="columns"
|
||||
:search-options="tableOptions.search"
|
||||
:pagination-options="tableOptions.pagination"
|
||||
style-class="vgt-table striped"
|
||||
>
|
||||
<template #table-row="props">
|
||||
<span v-if="props.column.field === 'name'">
|
||||
{{ props.formattedRow[props.column.field] }}
|
||||
<a
|
||||
:title="$t('user.oauth.modifyName')"
|
||||
href="#"
|
||||
data-test="name"
|
||||
@click="modifyName(props.row)"
|
||||
>
|
||||
<i class="fas fa-edit btn-edit" />
|
||||
</a>
|
||||
</span>
|
||||
<span v-else-if="props.column.field === 'redirect'">
|
||||
{{ props.formattedRow[props.column.field] }}
|
||||
<a
|
||||
:title="$t('user.oauth.modifyUrl')"
|
||||
href="#"
|
||||
data-test="callback"
|
||||
@click="modifyCallback(props.row)"
|
||||
>
|
||||
<i class="fas fa-edit btn-edit" />
|
||||
</a>
|
||||
</span>
|
||||
<span v-else-if="props.column.field === 'operations'">
|
||||
<button class="btn btn-danger" data-test="remove" @click="remove(props.row)">
|
||||
{{ $t('report.delete') }}
|
||||
</button>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ props.formattedRow[props.column.field] }}
|
||||
</span>
|
||||
</template>
|
||||
</vue-good-table>
|
||||
|
||||
<modal id="modal-create" :title="$t('user.oauth.create')" @confirm="create">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td v-t="'user.oauth.name'" class="key" />
|
||||
<td class="value">
|
||||
<input v-model="name" class="form-control" type="text">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td v-t="'user.oauth.redirect'" class="key" />
|
||||
<td class="value">
|
||||
<input v-model="callback" class="form-control" type="text">
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { VueGoodTable } from 'vue-good-table'
|
||||
import 'vue-good-table/dist/vue-good-table.min.css'
|
||||
import Modal from '../../components/Modal.vue'
|
||||
import tableOptions from '../../components/mixins/tableOptions'
|
||||
import emitMounted from '../../components/mixins/emitMounted'
|
||||
import { walkFetch, init } from '../../scripts/net'
|
||||
import { showModal, toast } from '../../scripts/notify'
|
||||
|
||||
export default {
|
||||
name: 'OAuthApps',
|
||||
components: {
|
||||
Modal,
|
||||
VueGoodTable,
|
||||
},
|
||||
mixins: [
|
||||
emitMounted,
|
||||
tableOptions,
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
name: '',
|
||||
callback: '',
|
||||
clients: [],
|
||||
columns: [
|
||||
{
|
||||
field: 'id', label: this.$t('user.oauth.id'), type: 'number',
|
||||
},
|
||||
{ field: 'name', label: this.$t('user.oauth.name') },
|
||||
{
|
||||
field: 'secret',
|
||||
label: this.$t('user.oauth.secret'),
|
||||
sortable: false,
|
||||
globalSearchDisabled: true,
|
||||
},
|
||||
{
|
||||
field: 'redirect',
|
||||
label: this.$t('user.oauth.redirect'),
|
||||
sortable: false,
|
||||
globalSearchDisabled: true,
|
||||
},
|
||||
{
|
||||
field: 'operations',
|
||||
label: this.$t('admin.operationsTitle'),
|
||||
sortable: false,
|
||||
globalSearchDisabled: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
async fetchData() {
|
||||
this.clients = await this.$http.get('/oauth/clients')
|
||||
},
|
||||
async create() {
|
||||
const client = await this.$http.post('/oauth/clients', {
|
||||
name: this.name,
|
||||
redirect: this.callback,
|
||||
})
|
||||
if (client.id) {
|
||||
this.clients.unshift(client)
|
||||
} else {
|
||||
toast.error(client.message)
|
||||
}
|
||||
},
|
||||
async modifyName(client) {
|
||||
let name
|
||||
try {
|
||||
({ value: name } = await showModal({
|
||||
mode: 'prompt',
|
||||
title: this.$t('user.oauth.name'),
|
||||
input: client.name,
|
||||
}))
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
await this.modify(client, { name })
|
||||
},
|
||||
async modifyCallback(client) {
|
||||
let redirect
|
||||
try {
|
||||
({ value: redirect } = await showModal({
|
||||
mode: 'prompt',
|
||||
title: this.$t('user.oauth.redirect'),
|
||||
input: client.redirect,
|
||||
}))
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
await this.modify(client, { redirect })
|
||||
},
|
||||
async modify(client, modified) {
|
||||
const request = new Request(
|
||||
`/oauth/clients/${client.id}`,
|
||||
Object.assign({}, init, {
|
||||
body: JSON.stringify(Object.assign({ name: client.name, redirect: client.redirect }, modified)),
|
||||
method: 'PUT',
|
||||
}),
|
||||
)
|
||||
request.headers.set('Content-Type', 'application/json')
|
||||
const result = await walkFetch(request)
|
||||
if (result.id) {
|
||||
Object.assign(client, modified)
|
||||
} else {
|
||||
toast.error(result.message)
|
||||
}
|
||||
},
|
||||
async remove(client) {
|
||||
try {
|
||||
await showModal({
|
||||
text: this.$t('user.oauth.confirmRemove'),
|
||||
okButtonType: 'danger',
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
const request = new Request(
|
||||
`/oauth/clients/${client.id}`,
|
||||
Object.assign({}, init, { method: 'DELETE' }),
|
||||
)
|
||||
await walkFetch(request)
|
||||
this.$delete(this.clients, this.clients.findIndex(({ id }) => id === client.id))
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.btn-create-app
|
||||
margin-bottom 5px
|
||||
margin-right 10px
|
||||
</style>
|
||||
70
resources/assets/src/views/user/OAuth/ModalCreate.tsx
Normal file
70
resources/assets/src/views/user/OAuth/ModalCreate.tsx
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import React, { useState } from 'react'
|
||||
import Modal from '../../../components/Modal'
|
||||
import { trans } from '../../../scripts/i18n'
|
||||
|
||||
interface Props {
|
||||
onCreate(name: string, redirect: string): Promise<void>
|
||||
}
|
||||
|
||||
const ModalCreate: React.FC<Props> = props => {
|
||||
const [name, setName] = useState('')
|
||||
const [url, setUrl] = useState('')
|
||||
|
||||
const handleNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setName(event.target.value)
|
||||
}
|
||||
|
||||
const handleUrlChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setUrl(event.target.value)
|
||||
}
|
||||
|
||||
const handleComplete = () => {
|
||||
props.onCreate(name, url)
|
||||
}
|
||||
|
||||
const handleDismiss = () => {
|
||||
setName('')
|
||||
setUrl('')
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
id="modal-create"
|
||||
onConfirm={handleComplete}
|
||||
onDismiss={handleDismiss}
|
||||
>
|
||||
<table className="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="key">{trans('user.oauth.name')}</td>
|
||||
<td className="value">
|
||||
<input
|
||||
value={name}
|
||||
onChange={handleNameChange}
|
||||
className="form-control"
|
||||
placeholder={trans('user.oauth.name')}
|
||||
type="text"
|
||||
required
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="key">{trans('user.oauth.redirect')}</td>
|
||||
<td className="value">
|
||||
<input
|
||||
value={url}
|
||||
onChange={handleUrlChange}
|
||||
className="form-control"
|
||||
placeholder={trans('user.oauth.redirect')}
|
||||
type="url"
|
||||
required
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default ModalCreate
|
||||
43
resources/assets/src/views/user/OAuth/Row.tsx
Normal file
43
resources/assets/src/views/user/OAuth/Row.tsx
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import React from 'react'
|
||||
import { trans } from '../../../scripts/i18n'
|
||||
import ButtonEdit from '../../../components/ButtonEdit'
|
||||
import { App } from './types'
|
||||
|
||||
interface Props {
|
||||
app: App
|
||||
onEditName: React.MouseEventHandler<HTMLAnchorElement>
|
||||
onEditRedirect: React.MouseEventHandler<HTMLAnchorElement>
|
||||
onDelete: React.MouseEventHandler<HTMLButtonElement>
|
||||
}
|
||||
|
||||
const Row: React.FC<Props> = props => {
|
||||
const { app } = props
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td>{app.id}</td>
|
||||
<td>
|
||||
<span>{app.name}</span>
|
||||
<ButtonEdit
|
||||
title={trans('user.oauth.modifyName')}
|
||||
onClick={props.onEditName}
|
||||
/>
|
||||
</td>
|
||||
<td>{app.secret}</td>
|
||||
<td>
|
||||
<span>{app.redirect}</span>
|
||||
<ButtonEdit
|
||||
title={trans('user.oauth.modifyUrl')}
|
||||
onClick={props.onEditRedirect}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<button className="btn btn-danger" onClick={props.onDelete}>
|
||||
{trans('report.delete')}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
|
||||
export default Row
|
||||
155
resources/assets/src/views/user/OAuth/index.tsx
Normal file
155
resources/assets/src/views/user/OAuth/index.tsx
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
import React, { useState, useEffect } from 'react'
|
||||
import { hot } from 'react-hot-loader/root'
|
||||
import { trans } from '../../../scripts/i18n'
|
||||
import * as fetch from '../../../scripts/net'
|
||||
import { showModal, toast } from '../../../scripts/notify'
|
||||
import Loading from '../../../components/Loading'
|
||||
import Row from './Row'
|
||||
import ModalCreate from './ModalCreate'
|
||||
import { App } from './types'
|
||||
|
||||
type Exception = {
|
||||
message: string
|
||||
}
|
||||
|
||||
const OAuth: React.FC = () => {
|
||||
const [apps, setApps] = useState<App[]>([])
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const getApps = async () => {
|
||||
setIsLoading(true)
|
||||
const allApps = await fetch.get<App[]>('/oauth/clients')
|
||||
setApps(allApps)
|
||||
setIsLoading(false)
|
||||
}
|
||||
getApps()
|
||||
}, [])
|
||||
|
||||
const handleAdd = async (name: string, redirect: string) => {
|
||||
const result = await fetch.post<App | Exception>('/oauth/clients', {
|
||||
name,
|
||||
redirect,
|
||||
})
|
||||
if ('id' in result) {
|
||||
setApps(apps => [...apps, result])
|
||||
} else {
|
||||
toast.error(result.message)
|
||||
}
|
||||
}
|
||||
|
||||
const editName = async (app: App, index: number) => {
|
||||
let name: string
|
||||
try {
|
||||
;({ value: name } = await showModal({
|
||||
mode: 'prompt',
|
||||
title: trans('user.oauth.name'),
|
||||
input: app.name,
|
||||
}))
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
const result = await fetch.put<App | Exception>(
|
||||
`/oauth/clients/${app.id}`,
|
||||
{ ...app, name },
|
||||
)
|
||||
if ('id' in result) {
|
||||
setApps(apps => {
|
||||
apps[index].name = name
|
||||
return apps.slice()
|
||||
})
|
||||
} else {
|
||||
toast.error(result.message)
|
||||
}
|
||||
}
|
||||
|
||||
const editRedirect = async (app: App, index: number) => {
|
||||
let redirect: string
|
||||
try {
|
||||
;({ value: redirect } = await showModal({
|
||||
mode: 'prompt',
|
||||
title: trans('user.oauth.redirect'),
|
||||
input: app.redirect,
|
||||
}))
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
const result = await fetch.put<App | Exception>(
|
||||
`/oauth/clients/${app.id}`,
|
||||
{ ...app, redirect },
|
||||
)
|
||||
if ('id' in result) {
|
||||
setApps(apps => {
|
||||
apps[index].redirect = redirect
|
||||
return apps.slice()
|
||||
})
|
||||
} else {
|
||||
toast.error(result.message)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async (app: App) => {
|
||||
try {
|
||||
await showModal({
|
||||
text: trans('user.oauth.confirmRemove'),
|
||||
okButtonType: 'danger',
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
await fetch.del(`/oauth/clients/${app.id}`)
|
||||
setApps(apps => apps.filter(a => a.id !== app.id))
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
data-toggle="modal"
|
||||
data-target="#modal-create"
|
||||
>
|
||||
{trans('user.oauth.create')}
|
||||
</button>
|
||||
<div className="card mt-2">
|
||||
<div className="card-body p-0">
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{trans('user.oauth.id')}</th>
|
||||
<th>{trans('user.oauth.name')}</th>
|
||||
<th>{trans('user.oauth.secret')}</th>
|
||||
<th>{trans('user.oauth.redirect')}</th>
|
||||
<th>{trans('admin.operationsTitle')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{apps.length === 0 ? (
|
||||
<tr>
|
||||
<td className="text-center" colSpan={5}>
|
||||
{isLoading ? <Loading /> : 'Nothing here.'}
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
apps.map((app, i) => (
|
||||
<Row
|
||||
key={app.id}
|
||||
app={app}
|
||||
onEditName={() => editName(app, i)}
|
||||
onEditRedirect={() => editRedirect(app, i)}
|
||||
onDelete={() => handleDelete(app)}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<ModalCreate onCreate={handleAdd} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default hot(OAuth)
|
||||
6
resources/assets/src/views/user/OAuth/types.ts
Normal file
6
resources/assets/src/views/user/OAuth/types.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export type App = {
|
||||
id: number
|
||||
name: string
|
||||
secret: string
|
||||
redirect: string
|
||||
}
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
import Vue from 'vue'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { flushPromises } from '../../utils'
|
||||
import { walkFetch } from '@/scripts/net'
|
||||
import { showModal, toast } from '@/scripts/notify'
|
||||
import Modal from '@/components/Modal.vue'
|
||||
import OAuth from '@/views/user/OAuth.vue'
|
||||
|
||||
jest.mock('@/scripts/notify')
|
||||
|
||||
jest.mock('@/scripts/net', () => ({
|
||||
walkFetch: jest.fn(),
|
||||
init: {},
|
||||
}))
|
||||
|
||||
test('basic render', async () => {
|
||||
Vue.prototype.$http.get.mockResolvedValue([
|
||||
{ id: 1 },
|
||||
])
|
||||
const wrapper = mount(OAuth)
|
||||
await flushPromises()
|
||||
expect(wrapper.findAll('[data-test=remove]')).toHaveLength(1)
|
||||
})
|
||||
|
||||
test('create app', async () => {
|
||||
Vue.prototype.$http.get.mockResolvedValue([])
|
||||
Vue.prototype.$http.post
|
||||
.mockResolvedValueOnce({ message: 'fail' })
|
||||
.mockResolvedValueOnce({ id: 1, name: 'name' })
|
||||
const wrapper = mount(OAuth)
|
||||
await flushPromises()
|
||||
|
||||
const modal = wrapper.find(Modal)
|
||||
const inputs = wrapper.findAll('.value')
|
||||
inputs.at(0).find('input')
|
||||
.setValue('name')
|
||||
inputs.at(1).find('input')
|
||||
.setValue('https://example.com/')
|
||||
|
||||
modal.vm.$emit('confirm')
|
||||
await flushPromises()
|
||||
expect(Vue.prototype.$http.post).toBeCalledWith(
|
||||
'/oauth/clients',
|
||||
{ name: 'name', redirect: 'https://example.com/' },
|
||||
)
|
||||
expect(toast.error).toBeCalledWith('fail')
|
||||
|
||||
modal.vm.$emit('confirm')
|
||||
await flushPromises()
|
||||
expect(wrapper.text()).toContain('name')
|
||||
})
|
||||
|
||||
test('modify name', async () => {
|
||||
Vue.prototype.$http.get.mockResolvedValue([
|
||||
{ id: 1, name: 'old' },
|
||||
])
|
||||
walkFetch
|
||||
.mockResolvedValueOnce({ message: 'fail' })
|
||||
.mockResolvedValueOnce({ id: 1, name: 'new-name' })
|
||||
showModal
|
||||
.mockRejectedValueOnce(null)
|
||||
.mockResolvedValue({ value: 'new-name' })
|
||||
const wrapper = mount(OAuth)
|
||||
await flushPromises()
|
||||
const button = wrapper.find('[data-test=name]')
|
||||
|
||||
button.trigger('click')
|
||||
await flushPromises()
|
||||
expect(walkFetch).not.toBeCalled()
|
||||
|
||||
button.trigger('click')
|
||||
await flushPromises()
|
||||
expect(walkFetch).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
url: '/oauth/clients/1',
|
||||
body: JSON.stringify({ name: 'new-name' }),
|
||||
method: 'PUT',
|
||||
}),
|
||||
)
|
||||
expect(toast.error).toBeCalledWith('fail')
|
||||
|
||||
button.trigger('click')
|
||||
await flushPromises()
|
||||
expect(wrapper.text()).toContain('new-name')
|
||||
})
|
||||
|
||||
test('modify redirect', async () => {
|
||||
Vue.prototype.$http.get.mockResolvedValue([
|
||||
{ id: 1, redirect: 'https://example.com/' },
|
||||
])
|
||||
walkFetch
|
||||
.mockResolvedValueOnce({ message: 'fail' })
|
||||
.mockResolvedValueOnce({ id: 1, redirect: 'https://example.net/' })
|
||||
showModal
|
||||
.mockRejectedValueOnce(null)
|
||||
.mockResolvedValue({ value: 'https://example.net/' })
|
||||
const wrapper = mount(OAuth)
|
||||
await flushPromises()
|
||||
const button = wrapper.find('[data-test=callback]')
|
||||
|
||||
button.trigger('click')
|
||||
await flushPromises()
|
||||
expect(walkFetch).not.toBeCalled()
|
||||
|
||||
button.trigger('click')
|
||||
await flushPromises()
|
||||
expect(walkFetch).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
url: '/oauth/clients/1',
|
||||
body: JSON.stringify({ redirect: 'https://example.net/' }),
|
||||
method: 'PUT',
|
||||
}),
|
||||
)
|
||||
expect(toast.error).toBeCalledWith('fail')
|
||||
|
||||
button.trigger('click')
|
||||
await flushPromises()
|
||||
expect(wrapper.text()).toContain('https://example.net/')
|
||||
})
|
||||
|
||||
test('remove app', async () => {
|
||||
Vue.prototype.$http.get.mockResolvedValue([
|
||||
{ id: 1, name: 'name' },
|
||||
])
|
||||
showModal
|
||||
.mockRejectedValueOnce(null)
|
||||
.mockResolvedValue({ value: '' })
|
||||
|
||||
const wrapper = mount(OAuth)
|
||||
await flushPromises()
|
||||
const button = wrapper.find('[data-test=remove]')
|
||||
|
||||
button.trigger('click')
|
||||
await flushPromises()
|
||||
expect(walkFetch).not.toBeCalled()
|
||||
|
||||
button.trigger('click')
|
||||
await flushPromises()
|
||||
expect(wrapper.text()).toContain('No data')
|
||||
})
|
||||
238
resources/assets/tests/views/user/OAuth.test.tsx
Normal file
238
resources/assets/tests/views/user/OAuth.test.tsx
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
import React from 'react'
|
||||
import { render, fireEvent, wait } from '@testing-library/react'
|
||||
import * as fetch from '@/scripts/net'
|
||||
import { trans } from '@/scripts/i18n'
|
||||
import { toast, showModal } from '@/scripts/notify'
|
||||
import OAuth from '@/views/user/OAuth'
|
||||
import { App } from '@/views/user/OAuth/types'
|
||||
|
||||
jest.mock('@/scripts/net')
|
||||
jest.mock('@/scripts/notify')
|
||||
|
||||
const example: App = {
|
||||
id: 1,
|
||||
name: 'My App',
|
||||
redirect: 'http://url.test/',
|
||||
secret: 'abc',
|
||||
}
|
||||
|
||||
test('loading data', () => {
|
||||
fetch.get.mockResolvedValue([])
|
||||
const { queryByTitle } = render(<OAuth />)
|
||||
expect(queryByTitle('Loading...')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
describe('create app', () => {
|
||||
beforeEach(() => {
|
||||
fetch.get.mockResolvedValue([])
|
||||
})
|
||||
|
||||
it('succeeded', async () => {
|
||||
fetch.post.mockResolvedValue(example)
|
||||
const { getByPlaceholderText, getByText, queryByText } = render(<OAuth />)
|
||||
await wait()
|
||||
|
||||
fireEvent.click(getByText(trans('user.oauth.create')))
|
||||
fireEvent.input(getByPlaceholderText(trans('user.oauth.name')), {
|
||||
target: { value: 'My App' },
|
||||
})
|
||||
fireEvent.input(getByPlaceholderText(trans('user.oauth.redirect')), {
|
||||
target: { value: 'http://url.test/' },
|
||||
})
|
||||
fireEvent.click(getByText(trans('general.confirm')))
|
||||
await wait()
|
||||
|
||||
expect(fetch.post).toBeCalledWith('/oauth/clients', {
|
||||
name: 'My App',
|
||||
redirect: 'http://url.test/',
|
||||
})
|
||||
expect(queryByText(example.id.toString())).toBeInTheDocument()
|
||||
expect(queryByText(example.name)).toBeInTheDocument()
|
||||
expect(queryByText(example.redirect)).toBeInTheDocument()
|
||||
expect(queryByText(example.secret)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('failed', async () => {
|
||||
fetch.post.mockResolvedValue({ message: 'exception' })
|
||||
const { getByPlaceholderText, getByText, queryByText } = render(<OAuth />)
|
||||
await wait()
|
||||
|
||||
fireEvent.click(getByText(trans('user.oauth.create')))
|
||||
fireEvent.input(getByPlaceholderText(trans('user.oauth.name')), {
|
||||
target: { value: 'My App' },
|
||||
})
|
||||
fireEvent.input(getByPlaceholderText(trans('user.oauth.redirect')), {
|
||||
target: { value: 'http://url.test/' },
|
||||
})
|
||||
fireEvent.click(getByText(trans('general.confirm')))
|
||||
|
||||
await wait()
|
||||
expect(fetch.post).toBeCalledWith('/oauth/clients', {
|
||||
name: 'My App',
|
||||
redirect: 'http://url.test/',
|
||||
})
|
||||
expect(toast.error).toBeCalledWith('exception')
|
||||
expect(queryByText(example.name)).not.toBeInTheDocument()
|
||||
expect(queryByText(example.redirect)).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('cancel dialog', async () => {
|
||||
const { getByPlaceholderText, getByText } = render(<OAuth />)
|
||||
await wait()
|
||||
|
||||
fireEvent.click(getByText(trans('user.oauth.create')))
|
||||
fireEvent.input(getByPlaceholderText(trans('user.oauth.name')), {
|
||||
target: { value: 'My App' },
|
||||
})
|
||||
fireEvent.input(getByPlaceholderText(trans('user.oauth.redirect')), {
|
||||
target: { value: 'http://url.test/' },
|
||||
})
|
||||
fireEvent.click(getByText(trans('general.cancel')))
|
||||
|
||||
await wait()
|
||||
expect(fetch.post).not.toBeCalled()
|
||||
|
||||
fireEvent.click(getByText(trans('user.oauth.create')))
|
||||
expect(getByPlaceholderText(trans('user.oauth.name'))).toHaveValue('')
|
||||
expect(getByPlaceholderText(trans('user.oauth.redirect'))).toHaveValue('')
|
||||
})
|
||||
})
|
||||
|
||||
describe('edit app', () => {
|
||||
beforeEach(() => {
|
||||
fetch.get.mockResolvedValue([example])
|
||||
})
|
||||
|
||||
describe('edit name', () => {
|
||||
it('succeeded', async () => {
|
||||
fetch.put.mockResolvedValue({ ...example, name: 'new name' })
|
||||
showModal.mockResolvedValue({ value: 'new name' })
|
||||
|
||||
const { getByTitle, queryByText } = render(<OAuth />)
|
||||
await wait()
|
||||
|
||||
fireEvent.click(getByTitle(trans('user.oauth.modifyName')))
|
||||
await wait()
|
||||
|
||||
expect(fetch.put).toBeCalledWith(`/oauth/clients/${example.id}`, {
|
||||
...example,
|
||||
name: 'new name',
|
||||
})
|
||||
expect(queryByText('new name')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('failed', async () => {
|
||||
fetch.put.mockResolvedValue({ message: 'exception' })
|
||||
showModal.mockResolvedValue({ value: 'new name' })
|
||||
|
||||
const { getByTitle, queryByText } = render(<OAuth />)
|
||||
await wait()
|
||||
|
||||
fireEvent.click(getByTitle(trans('user.oauth.modifyName')))
|
||||
await wait()
|
||||
|
||||
expect(fetch.put).toBeCalledWith(`/oauth/clients/${example.id}`, {
|
||||
...example,
|
||||
name: 'new name',
|
||||
})
|
||||
expect(queryByText(example.name)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('cancel dialog', async () => {
|
||||
showModal.mockRejectedValue(null)
|
||||
|
||||
const { getByTitle, queryByText } = render(<OAuth />)
|
||||
await wait()
|
||||
|
||||
fireEvent.click(getByTitle(trans('user.oauth.modifyName')))
|
||||
await wait()
|
||||
|
||||
expect(fetch.put).not.toBeCalled()
|
||||
expect(queryByText(example.name)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('edit redirect url', () => {
|
||||
it('succeeded', async () => {
|
||||
fetch.put.mockResolvedValue({ ...example, redirect: 'http://new.test/' })
|
||||
showModal.mockResolvedValue({ value: 'http://new.test/' })
|
||||
|
||||
const { getByTitle, queryByText } = render(<OAuth />)
|
||||
await wait()
|
||||
|
||||
fireEvent.click(getByTitle(trans('user.oauth.modifyUrl')))
|
||||
await wait()
|
||||
|
||||
expect(fetch.put).toBeCalledWith(`/oauth/clients/${example.id}`, {
|
||||
...example,
|
||||
redirect: 'http://new.test/',
|
||||
})
|
||||
expect(queryByText('http://new.test/')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('failed', async () => {
|
||||
fetch.put.mockResolvedValue({ message: 'exception' })
|
||||
showModal.mockResolvedValue({ value: 'http://new.test/' })
|
||||
|
||||
const { getByTitle, queryByText } = render(<OAuth />)
|
||||
await wait()
|
||||
|
||||
fireEvent.click(getByTitle(trans('user.oauth.modifyUrl')))
|
||||
await wait()
|
||||
|
||||
expect(fetch.put).toBeCalledWith(`/oauth/clients/${example.id}`, {
|
||||
...example,
|
||||
redirect: 'http://new.test/',
|
||||
})
|
||||
expect(toast.error).toBeCalledWith('exception')
|
||||
expect(queryByText(example.redirect)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('cancel dialog', async () => {
|
||||
showModal.mockRejectedValue(null)
|
||||
|
||||
const { getByTitle, queryByText } = render(<OAuth />)
|
||||
await wait()
|
||||
|
||||
fireEvent.click(getByTitle(trans('user.oauth.modifyUrl')))
|
||||
await wait()
|
||||
|
||||
expect(fetch.put).not.toBeCalled()
|
||||
expect(queryByText(example.redirect)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('delete app', () => {
|
||||
beforeEach(() => {
|
||||
fetch.get.mockResolvedValue([example])
|
||||
})
|
||||
|
||||
it('succeeded', async () => {
|
||||
showModal.mockResolvedValue({ value: '' })
|
||||
|
||||
const { getByText, queryByText } = render(<OAuth />)
|
||||
await wait()
|
||||
|
||||
fireEvent.click(getByText(trans('report.delete')))
|
||||
await wait()
|
||||
|
||||
expect(fetch.del).toBeCalledWith(`/oauth/clients/${example.id}`)
|
||||
expect(queryByText(example.name)).not.toBeInTheDocument()
|
||||
expect(queryByText(example.redirect)).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('cancel dialog', async () => {
|
||||
showModal.mockRejectedValue(null)
|
||||
|
||||
const { getByText, queryByText } = render(<OAuth />)
|
||||
await wait()
|
||||
|
||||
fireEvent.click(getByText(trans('report.delete')))
|
||||
await wait()
|
||||
|
||||
expect(fetch.post).not.toBeCalled()
|
||||
expect(queryByText(example.name)).toBeInTheDocument()
|
||||
expect(queryByText(example.redirect)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
Loading…
Reference in New Issue
Block a user