mirror of
https://github.com/bs-community/blessing-skin-server.git
synced 2025-01-08 12:07:42 +08:00
rewrite plugins market with React
This commit is contained in:
parent
b183dae6bd
commit
437ac5b120
@ -82,7 +82,7 @@ export default [
|
||||
},
|
||||
{
|
||||
path: 'admin/plugins/market',
|
||||
component: () => import('../views/admin/Market.vue'),
|
||||
react: () => import('../views/admin/PluginsMarket'),
|
||||
el: '.content > .container-fluid',
|
||||
},
|
||||
{
|
||||
|
@ -1,168 +0,0 @@
|
||||
<template>
|
||||
<div class="container-fluid">
|
||||
<vue-good-table
|
||||
:rows="plugins"
|
||||
: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 === 'title'">
|
||||
<strong>{{ props.formattedRow[props.column.field] }}</strong>
|
||||
<div>{{ props.row.name }}</div>
|
||||
</span>
|
||||
<span v-else-if="props.column.field === 'dependencies'">
|
||||
<span v-if="Object.keys(props.row.dependencies.all).length === 0">
|
||||
<i v-t="'admin.noDependencies'" />
|
||||
</span>
|
||||
<div v-else>
|
||||
<span
|
||||
v-for="(constraint, name) in props.row.dependencies.all"
|
||||
:key="name"
|
||||
class="badge"
|
||||
:class="`bg-${name in props.row.dependencies.unsatisfied ? 'red' : 'green'}`"
|
||||
>
|
||||
{{ name }}: {{ constraint }}
|
||||
<br>
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
<span v-else-if="props.column.field === 'operations'">
|
||||
<template v-if="props.row.installed">
|
||||
<button
|
||||
v-if="props.row.can_update"
|
||||
class="btn btn-success"
|
||||
:disabled="installing === props.row.name"
|
||||
@click="updatePlugin(props.row)"
|
||||
>
|
||||
<template v-if="installing === props.row.name">
|
||||
<i class="fas fa-spinner fa-spin" /> {{ $t('admin.pluginUpdating') }}
|
||||
</template>
|
||||
<template v-else>
|
||||
<i class="fas fa-sync-alt" /> {{ $t('admin.updatePlugin') }}
|
||||
</template>
|
||||
</button>
|
||||
<button v-else class="btn btn-default" disabled>
|
||||
<i class="fas fa-download" /> {{ $t('admin.installPlugin') }}
|
||||
</button>
|
||||
</template>
|
||||
<button
|
||||
v-else
|
||||
class="btn btn-default"
|
||||
:disabled="installing === props.row.name"
|
||||
@click="installPlugin(props.row)"
|
||||
>
|
||||
<template v-if="installing === props.row.name">
|
||||
<i class="fas fa-spinner fa-spin" /> {{ $t('admin.pluginInstalling') }}
|
||||
</template>
|
||||
<template v-else>
|
||||
<i class="fas fa-download" /> {{ $t('admin.installPlugin') }}
|
||||
</template>
|
||||
</button>
|
||||
</span>
|
||||
<span v-else v-text="props.formattedRow[props.column.field]" />
|
||||
</template>
|
||||
</vue-good-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { VueGoodTable } from 'vue-good-table'
|
||||
import 'vue-good-table/dist/vue-good-table.min.css'
|
||||
import alertUnresolvedPlugins from '../../components/mixins/alertUnresolvedPlugins'
|
||||
import tableOptions from '../../components/mixins/tableOptions'
|
||||
import emitMounted from '../../components/mixins/emitMounted'
|
||||
import { showModal, toast } from '../../scripts/notify'
|
||||
|
||||
export default {
|
||||
name: 'Market',
|
||||
components: {
|
||||
VueGoodTable,
|
||||
},
|
||||
mixins: [
|
||||
emitMounted,
|
||||
tableOptions,
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
plugins: [],
|
||||
columns: [
|
||||
{ field: 'title', label: this.$t('admin.pluginTitle') },
|
||||
{
|
||||
field: 'description',
|
||||
label: this.$t('admin.pluginDescription'),
|
||||
sortable: false,
|
||||
width: '37%',
|
||||
},
|
||||
{ field: 'author', label: this.$t('admin.pluginAuthor') },
|
||||
{
|
||||
field: 'version',
|
||||
label: this.$t('admin.pluginVersion'),
|
||||
sortable: false,
|
||||
globalSearchDisabled: true,
|
||||
width: '5%',
|
||||
},
|
||||
{
|
||||
field: 'dependencies',
|
||||
label: this.$t('admin.pluginDependencies'),
|
||||
sortable: false,
|
||||
globalSearchDisabled: true,
|
||||
},
|
||||
{
|
||||
field: 'operations',
|
||||
label: this.$t('admin.operationsTitle'),
|
||||
sortable: false,
|
||||
globalSearchDisabled: true,
|
||||
width: '12%',
|
||||
},
|
||||
],
|
||||
installing: '',
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
async fetchData() {
|
||||
this.plugins = await this.$http.get('/admin/plugins/market/list')
|
||||
},
|
||||
async installPlugin({ name, originalIndex }) {
|
||||
this.installing = name
|
||||
|
||||
const {
|
||||
code,
|
||||
message,
|
||||
data,
|
||||
} = await this.$http.post(
|
||||
'/admin/plugins/market/download',
|
||||
{ name },
|
||||
)
|
||||
if (code === 0) {
|
||||
toast.success(message)
|
||||
this.plugins[originalIndex].can_update = false
|
||||
this.plugins[originalIndex].installed = true
|
||||
} else if (data && data.reason) {
|
||||
alertUnresolvedPlugins(message, data.reason)
|
||||
} else {
|
||||
toast.error(message)
|
||||
}
|
||||
|
||||
this.installing = ''
|
||||
},
|
||||
async updatePlugin(plugin) {
|
||||
try {
|
||||
await showModal({
|
||||
text: this.$t('admin.confirmUpdate', {
|
||||
plugin: plugin.title, old: plugin.installed, new: plugin.version,
|
||||
}),
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
this.installPlugin(plugin)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
92
resources/assets/src/views/admin/PluginsMarket/Row.tsx
Normal file
92
resources/assets/src/views/admin/PluginsMarket/Row.tsx
Normal file
@ -0,0 +1,92 @@
|
||||
import React from 'react'
|
||||
import { t } from '@/scripts/i18n'
|
||||
import { Plugin } from './types'
|
||||
|
||||
interface Props {
|
||||
plugin: Plugin
|
||||
isInstalling: boolean
|
||||
onInstall(): void
|
||||
onUpdate(): void
|
||||
}
|
||||
|
||||
const Row: React.FC<Props> = (props) => {
|
||||
const { plugin, isInstalling } = props
|
||||
|
||||
const allDeps = Object.entries(plugin.dependencies.all)
|
||||
const unsatisfied = Object.keys(plugin.dependencies.unsatisfied)
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td style={{ width: '18%' }}>
|
||||
<div>
|
||||
<b>{plugin.title}</b>
|
||||
</div>
|
||||
<div>{plugin.name}</div>
|
||||
</td>
|
||||
<td style={{ width: '37%' }}>{plugin.description}</td>
|
||||
<td>{plugin.author}</td>
|
||||
<td>{plugin.version}</td>
|
||||
<td style={{ width: '100px' }}>
|
||||
{allDeps.length === 0 ? (
|
||||
<i>{t('admin.noDependencies')}</i>
|
||||
) : (
|
||||
<div className="d-flex flex-column">
|
||||
{allDeps.map(([name, constraint]) => {
|
||||
const classes = [
|
||||
'mb-1',
|
||||
'badge',
|
||||
`bg-${unsatisfied.includes(name) ? 'red' : 'green'}`,
|
||||
]
|
||||
return (
|
||||
<span key={name} className={classes.join(' ')}>
|
||||
{name}: {constraint}
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
<td style={{ width: '12%' }}>
|
||||
{plugin.can_update ? (
|
||||
<button
|
||||
className="btn btn-success"
|
||||
disabled={isInstalling}
|
||||
onClick={props.onUpdate}
|
||||
>
|
||||
{isInstalling ? (
|
||||
<>
|
||||
<i className="fas fa-spinner fa-spin mr-1"></i>
|
||||
{t('admin.pluginUpdating')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<i className="fas fa-sync-alt mr-1"></i>
|
||||
{t('admin.updatePlugin')}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="btn btn-default"
|
||||
disabled={props.isInstalling || !!plugin.installed}
|
||||
onClick={props.onInstall}
|
||||
>
|
||||
{isInstalling ? (
|
||||
<>
|
||||
<i className="fas fa-spinner fa-spin mr-1"></i>
|
||||
{t('admin.pluginInstalling')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<i className="fas fa-download mr-1"></i>
|
||||
{t('admin.installPlugin')}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
|
||||
export default Row
|
162
resources/assets/src/views/admin/PluginsMarket/index.tsx
Normal file
162
resources/assets/src/views/admin/PluginsMarket/index.tsx
Normal file
@ -0,0 +1,162 @@
|
||||
import React, { useState, useEffect, useMemo } from 'react'
|
||||
import { hot } from 'react-hot-loader/root'
|
||||
import { enableMapSet } from 'immer'
|
||||
import { useImmer } from 'use-immer'
|
||||
import { t } from '@/scripts/i18n'
|
||||
import * as fetch from '@/scripts/net'
|
||||
import { toast, showModal } from '@/scripts/notify'
|
||||
import Loading from '@/components/Loading'
|
||||
import Pagination from '@/components/Pagination'
|
||||
import { Plugin } from './types'
|
||||
import Row from './Row'
|
||||
|
||||
enableMapSet()
|
||||
|
||||
const PluginsMarket: React.FC = () => {
|
||||
const [plugins, setPlugins] = useImmer<Plugin[]>([])
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [search, setSearch] = useState('')
|
||||
const [page, setPage] = useState(1)
|
||||
const [totalPages, setTotalPages] = useState(1)
|
||||
const [installings, setInstallings] = useImmer<Set<string>>(() => new Set())
|
||||
|
||||
const searchedPlugins = useMemo(
|
||||
() =>
|
||||
plugins.filter(
|
||||
(plugin) =>
|
||||
plugin.name.includes(search) || plugin.title.includes(search),
|
||||
),
|
||||
[plugins, search],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const getPlugins = async () => {
|
||||
setIsLoading(true)
|
||||
const plugins = await fetch.get<Plugin[]>('/admin/plugins/market/list')
|
||||
setPlugins(() => plugins)
|
||||
setTotalPages(Math.ceil(plugins.length / 10))
|
||||
setIsLoading(false)
|
||||
}
|
||||
getPlugins()
|
||||
}, [])
|
||||
|
||||
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const search = event.target.value
|
||||
setSearch(search)
|
||||
setPage(1)
|
||||
|
||||
const searchedPlugins = plugins.filter(
|
||||
(plugin) => plugin.name.includes(search) || plugin.title.includes(search),
|
||||
)
|
||||
setTotalPages(Math.ceil(searchedPlugins.length / 10))
|
||||
}
|
||||
|
||||
const handleInstall = async (plugin: Plugin, index: number) => {
|
||||
setInstallings((installings) => {
|
||||
installings.add(plugin.name)
|
||||
})
|
||||
|
||||
const { code, message, data = { reason: [] } } = await fetch.post<
|
||||
fetch.ResponseBody<{ reason: string[] }>
|
||||
>('/admin/plugins/market/download', {
|
||||
name: plugin.name,
|
||||
})
|
||||
if (code === 0) {
|
||||
toast.success(message)
|
||||
setPlugins((plugins) => {
|
||||
plugins[index].can_update = false
|
||||
plugins[index].installed = plugins[index].version
|
||||
})
|
||||
} else {
|
||||
showModal({
|
||||
mode: 'alert',
|
||||
children: (
|
||||
<div>
|
||||
<p>{message}</p>
|
||||
<ul>
|
||||
{data.reason.map((t, i) => (
|
||||
<li key={i}>{t}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
setInstallings((installings) => {
|
||||
installings.delete(plugin.name)
|
||||
})
|
||||
}
|
||||
|
||||
const handleUpdate = async (plugin: Plugin, index: number) => {
|
||||
try {
|
||||
await showModal({
|
||||
text: t('admin.confirmUpdate', {
|
||||
plugin: plugin.title,
|
||||
old: plugin.installed,
|
||||
new: plugin.version,
|
||||
}),
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
handleInstall(plugin, index)
|
||||
}
|
||||
|
||||
const pagedPlugins = searchedPlugins.slice((page - 1) * 10, page * 10)
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('vendor.datatable.search')}
|
||||
value={search}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
</div>
|
||||
{isLoading ? (
|
||||
<div className="card-body">
|
||||
<Loading />
|
||||
</div>
|
||||
) : searchedPlugins.length === 0 ? (
|
||||
<div className="card-body text-center">{t('general.noResult')}</div>
|
||||
) : (
|
||||
<div className="card-body table-responsive p-0">
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t('admin.pluginTitle')}</th>
|
||||
<th>{t('admin.pluginDescription')}</th>
|
||||
<th>{t('admin.pluginAuthor')}</th>
|
||||
<th>{t('admin.pluginVersion')}</th>
|
||||
<th>{t('admin.pluginDependencies')}</th>
|
||||
<th>{t('admin.operationsTitle')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{pagedPlugins.map((plugin, i) => (
|
||||
<Row
|
||||
key={plugin.name}
|
||||
plugin={plugin}
|
||||
isInstalling={installings.has(plugin.name)}
|
||||
onInstall={() => handleInstall(plugin, i)}
|
||||
onUpdate={() => handleUpdate(plugin, i)}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
<div className="card-footer">
|
||||
<div className="float-right">
|
||||
<Pagination page={page} totalPages={totalPages} onChange={setPage} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default hot(PluginsMarket)
|
13
resources/assets/src/views/admin/PluginsMarket/types.ts
Normal file
13
resources/assets/src/views/admin/PluginsMarket/types.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export type Plugin = {
|
||||
name: string
|
||||
version: string
|
||||
title: string
|
||||
description: string
|
||||
author: string
|
||||
installed: string | false
|
||||
can_update?: boolean
|
||||
dependencies: {
|
||||
all: Record<string, string>
|
||||
unsatisfied: Record<string, string>
|
||||
}
|
||||
}
|
@ -1,112 +0,0 @@
|
||||
import Vue from 'vue'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { flushPromises } from '../../utils'
|
||||
import { showModal } from '@/scripts/notify'
|
||||
import Market from '@/views/admin/Market.vue'
|
||||
|
||||
jest.mock('@/scripts/notify')
|
||||
|
||||
test('render dependencies', async () => {
|
||||
Vue.prototype.$http.get.mockResolvedValue([
|
||||
{ name: 'a', dependencies: { all: {}, unsatisfied: {} } },
|
||||
{
|
||||
name: 'b',
|
||||
dependencies: {
|
||||
all: { a: '^1.0.0', c: '^2.0.0' }, unsatisfied: { c: {} },
|
||||
},
|
||||
},
|
||||
])
|
||||
const wrapper = mount(Market)
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.text()).toContain('admin.noDependencies')
|
||||
expect(wrapper.find('span.badge.bg-green').text()).toBe('a: ^1.0.0')
|
||||
expect(wrapper.find('span.badge.bg-red').text()).toBe('c: ^2.0.0')
|
||||
})
|
||||
|
||||
test('render operation buttons', async () => {
|
||||
Vue.prototype.$http.get.mockResolvedValue([
|
||||
{
|
||||
name: 'a', dependencies: { all: {}, unsatisfied: {} }, installed: true, can_update: true,
|
||||
},
|
||||
{
|
||||
name: 'b', dependencies: { all: {}, unsatisfied: {} }, installed: true,
|
||||
},
|
||||
{
|
||||
name: 'c', dependencies: { all: {}, unsatisfied: {} }, installed: false,
|
||||
},
|
||||
])
|
||||
const wrapper = mount(Market)
|
||||
await flushPromises()
|
||||
const tbody = wrapper.find('tbody')
|
||||
|
||||
expect(tbody.find('tr:nth-child(1)').text()).toContain('admin.updatePlugin')
|
||||
expect(tbody.find('tr:nth-child(2)').text()).toContain('admin.installPlugin')
|
||||
expect(tbody.find('tr:nth-child(2) button').attributes('disabled')).toBeTruthy()
|
||||
expect(tbody.find('tr:nth-child(3)').text()).toContain('admin.installPlugin')
|
||||
})
|
||||
|
||||
test('install plugin', async () => {
|
||||
Vue.prototype.$http.get.mockResolvedValue([
|
||||
{
|
||||
name: 'd', dependencies: { all: {}, unsatisfied: {} }, installed: false,
|
||||
},
|
||||
])
|
||||
Vue.prototype.$http.post
|
||||
.mockResolvedValueOnce({ code: 1, message: '1' })
|
||||
.mockResolvedValueOnce({
|
||||
code: 1,
|
||||
message: 'unresolved',
|
||||
data: { reason: ['u'] },
|
||||
})
|
||||
.mockResolvedValueOnce({ code: 0, message: '0' })
|
||||
const wrapper = mount(Market)
|
||||
await flushPromises()
|
||||
const button = wrapper.find('button')
|
||||
|
||||
button.trigger('click')
|
||||
await flushPromises()
|
||||
expect(Vue.prototype.$http.post).toBeCalledWith(
|
||||
'/admin/plugins/market/download',
|
||||
{ name: 'd' },
|
||||
)
|
||||
|
||||
button.trigger('click')
|
||||
await flushPromises()
|
||||
expect(showModal).toBeCalledWith(expect.objectContaining({ mode: 'alert' }))
|
||||
|
||||
button.trigger('click')
|
||||
await flushPromises()
|
||||
expect(wrapper.find('.btn-default').attributes('disabled')).toBeTruthy()
|
||||
})
|
||||
|
||||
test('update plugin', async () => {
|
||||
Vue.prototype.$http.get.mockResolvedValue([
|
||||
{
|
||||
name: 'a',
|
||||
version: '2.0.0',
|
||||
dependencies: { all: {}, unsatisfied: {} },
|
||||
installed: '1.0.0',
|
||||
can_update: true,
|
||||
},
|
||||
])
|
||||
Vue.prototype.$http.post
|
||||
.mockResolvedValueOnce({ code: 1, message: '1' })
|
||||
showModal
|
||||
.mockRejectedValueOnce(null)
|
||||
.mockResolvedValue({ value: '' })
|
||||
const wrapper = mount(Market)
|
||||
await flushPromises()
|
||||
const button = wrapper.find('button')
|
||||
|
||||
button.trigger('click')
|
||||
await flushPromises()
|
||||
expect(Vue.prototype.$http.post).not.toBeCalled()
|
||||
|
||||
button.trigger('click')
|
||||
await flushPromises()
|
||||
expect(Vue.prototype.$http.post).toBeCalledWith(
|
||||
'/admin/plugins/market/download',
|
||||
{ name: 'a' },
|
||||
)
|
||||
})
|
188
resources/assets/tests/views/admin/PluginsMarket.test.tsx
Normal file
188
resources/assets/tests/views/admin/PluginsMarket.test.tsx
Normal file
@ -0,0 +1,188 @@
|
||||
import React from 'react'
|
||||
import { render, waitFor, fireEvent } from '@testing-library/react'
|
||||
import { t } from '@/scripts/i18n'
|
||||
import * as fetch from '@/scripts/net'
|
||||
import PluginsMarket from '@/views/admin/PluginsMarket'
|
||||
import { Plugin } from '@/views/admin/PluginsMarket/types'
|
||||
|
||||
jest.mock('@/scripts/net')
|
||||
|
||||
const fixture: Readonly<Plugin> = Object.freeze<Readonly<Plugin>>({
|
||||
name: 'yggdrasil-api',
|
||||
title: 'Yggdrasil API',
|
||||
description: 'Auth System',
|
||||
version: '1.0.0',
|
||||
author: 'Blessing Skin',
|
||||
installed: false,
|
||||
dependencies: {
|
||||
all: {
|
||||
'blessing-skin-server': '^5.0.0',
|
||||
},
|
||||
unsatisfied: {},
|
||||
},
|
||||
})
|
||||
|
||||
test('search plugins', async () => {
|
||||
fetch.get.mockResolvedValue([fixture])
|
||||
|
||||
const { getByPlaceholderText, queryByText } = render(<PluginsMarket />)
|
||||
await waitFor(() => expect(fetch.get).toBeCalled())
|
||||
|
||||
fireEvent.input(getByPlaceholderText(t('vendor.datatable.search')), {
|
||||
target: { value: 'test' },
|
||||
})
|
||||
expect(queryByText('yggdrasil-api')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
describe('dependencies', () => {
|
||||
it('no dependencies', async () => {
|
||||
fetch.get.mockResolvedValue([
|
||||
{ ...fixture, dependencies: { all: {}, unsatisfied: {} } },
|
||||
])
|
||||
|
||||
const { queryByText } = render(<PluginsMarket />)
|
||||
await waitFor(() => expect(fetch.get).toBeCalled())
|
||||
expect(queryByText(t('admin.noDependencies'))).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('satisfied dependencies', async () => {
|
||||
fetch.get.mockResolvedValue([fixture])
|
||||
|
||||
const { queryByText } = render(<PluginsMarket />)
|
||||
await waitFor(() => expect(fetch.get).toBeCalled())
|
||||
expect(
|
||||
queryByText(
|
||||
`blessing-skin-server: ${fixture.dependencies.all['blessing-skin-server']}`,
|
||||
),
|
||||
).toHaveClass('bg-green')
|
||||
})
|
||||
|
||||
it('unsatisfied dependencies', async () => {
|
||||
fetch.get.mockResolvedValue([
|
||||
{
|
||||
...fixture,
|
||||
dependencies: {
|
||||
all: { 'blessing-skin-server': '^5.0.0' },
|
||||
unsatisfied: { 'blessing-skin-server': '4.0.0' },
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
const { queryByText } = render(<PluginsMarket />)
|
||||
await waitFor(() => expect(fetch.get).toBeCalled())
|
||||
expect(
|
||||
queryByText(
|
||||
`blessing-skin-server: ${fixture.dependencies.all['blessing-skin-server']}`,
|
||||
),
|
||||
).toHaveClass('bg-red')
|
||||
})
|
||||
})
|
||||
|
||||
describe('install plugin', async () => {
|
||||
beforeEach(() => {
|
||||
fetch.get.mockResolvedValue([fixture])
|
||||
})
|
||||
|
||||
it('succeeded', async () => {
|
||||
fetch.post.mockResolvedValue({ code: 0, message: 'ok' })
|
||||
|
||||
const { getByText, queryByRole, queryByText } = render(<PluginsMarket />)
|
||||
await waitFor(() => expect(fetch.get).toBeCalled())
|
||||
|
||||
fireEvent.click(getByText(t('admin.installPlugin')))
|
||||
await waitFor(() =>
|
||||
expect(fetch.post).toBeCalledWith('/admin/plugins/market/download', {
|
||||
name: fixture.name,
|
||||
}),
|
||||
)
|
||||
expect(queryByText(t('admin.installPlugin'))).toBeDisabled()
|
||||
expect(queryByText('ok')).toBeInTheDocument()
|
||||
expect(queryByRole('status')).toHaveClass('alert-success')
|
||||
})
|
||||
|
||||
it('failed', async () => {
|
||||
fetch.post.mockResolvedValue({ code: 1, message: 'failed' })
|
||||
|
||||
const { getByText, queryByText } = render(<PluginsMarket />)
|
||||
await waitFor(() => expect(fetch.get).toBeCalled())
|
||||
|
||||
fireEvent.click(getByText(t('admin.installPlugin')))
|
||||
await waitFor(() =>
|
||||
expect(fetch.post).toBeCalledWith('/admin/plugins/market/download', {
|
||||
name: fixture.name,
|
||||
}),
|
||||
)
|
||||
expect(queryByText('failed')).toBeInTheDocument()
|
||||
expect(queryByText(t('admin.installPlugin'))).toBeEnabled()
|
||||
|
||||
fireEvent.click(getByText(t('general.confirm')))
|
||||
})
|
||||
|
||||
it('failed with unsatisfied', async () => {
|
||||
fetch.post.mockResolvedValue({
|
||||
code: 1,
|
||||
message: 'failed',
|
||||
data: { reason: ['version is too low'] },
|
||||
})
|
||||
|
||||
const { getByText, queryByText } = render(<PluginsMarket />)
|
||||
await waitFor(() => expect(fetch.get).toBeCalled())
|
||||
|
||||
fireEvent.click(getByText(t('admin.installPlugin')))
|
||||
await waitFor(() =>
|
||||
expect(fetch.post).toBeCalledWith('/admin/plugins/market/download', {
|
||||
name: fixture.name,
|
||||
}),
|
||||
)
|
||||
expect(queryByText('failed')).toBeInTheDocument()
|
||||
expect(queryByText('version is too low')).toBeInTheDocument()
|
||||
expect(queryByText(t('admin.installPlugin'))).toBeEnabled()
|
||||
|
||||
fireEvent.click(getByText(t('general.confirm')))
|
||||
})
|
||||
})
|
||||
|
||||
describe('update plugin', () => {
|
||||
beforeEach(() => {
|
||||
fetch.get.mockResolvedValue([
|
||||
{ ...fixture, can_update: true, installed: '0.5.0' },
|
||||
])
|
||||
})
|
||||
|
||||
it('cancelled', async () => {
|
||||
const { getByText, queryByText } = render(<PluginsMarket />)
|
||||
await waitFor(() => expect(fetch.get).toBeCalled())
|
||||
|
||||
fireEvent.click(getByText(t('admin.updatePlugin')))
|
||||
expect(
|
||||
queryByText(
|
||||
t('admin.confirmUpdate', {
|
||||
plugin: fixture.title,
|
||||
old: '0.5.0',
|
||||
new: fixture.version,
|
||||
}),
|
||||
),
|
||||
).toBeInTheDocument()
|
||||
|
||||
fireEvent.click(getByText(t('general.cancel')))
|
||||
expect(fetch.post).not.toBeCalled()
|
||||
})
|
||||
|
||||
it('confirm to update', async () => {
|
||||
fetch.post.mockResolvedValue({ code: 0, message: 'ok' })
|
||||
|
||||
const { getByText, queryByText } = render(<PluginsMarket />)
|
||||
await waitFor(() => expect(fetch.get).toBeCalled())
|
||||
|
||||
fireEvent.click(getByText(t('admin.updatePlugin')))
|
||||
fireEvent.click(getByText(t('general.confirm')))
|
||||
|
||||
await waitFor(() =>
|
||||
expect(fetch.post).toBeCalledWith('/admin/plugins/market/download', {
|
||||
name: fixture.name,
|
||||
}),
|
||||
)
|
||||
expect(queryByText('ok')).toBeInTheDocument()
|
||||
expect(queryByText(t('admin.installPlugin'))).toBeDisabled()
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue
Block a user