Merge branch 'feature/add-tests' into 'master'

Add tests

Closes #7

See merge request pleroma/admin-fe!6
This commit is contained in:
Maxim Filippov 2019-03-22 20:58:58 +00:00
commit 9f3403aebc
13 changed files with 2798 additions and 980 deletions

View file

@ -1,17 +1,25 @@
{
"presets": [
["env", {
"modules": false,
[
"@babel/preset-env", {
"modules": "auto",
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}],
"stage-2"
}
],
"@vue/babel-preset-jsx"
],
"plugins": [
"@babel/plugin-transform-runtime",
"@babel/plugin-syntax-dynamic-import"
],
"plugins": ["transform-vue-jsx", "transform-runtime"],
"env": {
"development":{
"plugins": ["dynamic-import-node"]
"plugins": ["dynamic-import-node-babel-7"]
},
"test":{
"plugins": ["dynamic-import-node-babel-7"]
}
}
}

View file

@ -9,7 +9,8 @@
"build:prod": "cross-env NODE_ENV=production env_config=prod node build/build.js",
"build:sit": "cross-env NODE_ENV=production env_config=sit node build/build.js",
"lint": "eslint --ext .js,.vue src",
"test": "npm run lint",
"test": "jest",
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand",
"precommit": "lint-staged",
"svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml"
},
@ -34,6 +35,7 @@
"url": "https://github.com/PanJiaChen/vue-element-admin/issues"
},
"dependencies": {
"@babel/runtime": "^7.3.4",
"axios": "0.18.0",
"clipboard": "1.7.1",
"codemirror": "5.39.2",
@ -46,6 +48,7 @@
"js-cookie": "2.2.0",
"jsonlint": "1.6.3",
"jszip": "3.1.5",
"lodash": "^4.17.11",
"lodash.debounce": "^4.0.8",
"mockjs": "1.0.1-beta3",
"normalize.css": "7.0.0",
@ -54,7 +57,7 @@
"showdown": "1.8.6",
"sortablejs": "1.7.0",
"tui-editor": "1.2.7",
"vue": "2.5.17",
"vue": "^2.6.8",
"vue-count-to": "1.0.13",
"vue-i18n": "7.3.2",
"vue-router": "3.0.2",
@ -64,17 +67,20 @@
"xlsx": "^0.11.16"
},
"devDependencies": {
"@babel/core": "^7.3.4",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-transform-runtime": "^7.3.4",
"@babel/preset-env": "^7.3.4",
"@vue/babel-helper-vue-jsx-merge-props": "^1.0.0-beta.2",
"@vue/babel-preset-jsx": "^1.0.0-beta.2",
"@vue/test-utils": "^1.0.0-beta.29",
"autoprefixer": "8.5.0",
"babel-core": "6.26.3",
"babel-eslint": "8.2.6",
"babel-helper-vue-jsx-merge-props": "2.0.3",
"babel-loader": "7.1.5",
"babel-plugin-dynamic-import-node": "2.0.0",
"babel-plugin-syntax-jsx": "6.18.0",
"babel-plugin-transform-runtime": "6.23.0",
"babel-plugin-transform-vue-jsx": "3.7.0",
"babel-preset-env": "1.7.0",
"babel-preset-stage-2": "6.24.1",
"babel-jest": "^24.1.0",
"babel-loader": "^8.0.5",
"babel-plugin-dynamic-import-node-babel-7": "^2.0.7",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
"chalk": "2.4.1",
"compression-webpack-plugin": "2.0.0",
"connect": "3.6.6",
@ -88,8 +94,10 @@
"file-loader": "1.1.11",
"friendly-errors-webpack-plugin": "1.7.0",
"hash-sum": "1.0.2",
"html-webpack-plugin": "4.0.0-alpha",
"html-webpack-plugin": "^3.2.0",
"husky": "0.14.3",
"jest": "^24.1.0",
"jest-transform-stub": "^2.0.0",
"lint-staged": "7.2.2",
"mini-css-extract-plugin": "0.4.1",
"node-notifier": "5.2.1",
@ -112,12 +120,13 @@
"svgo": "1.0.5",
"uglifyjs-webpack-plugin": "1.2.7",
"url-loader": "1.0.1",
"vue-jest": "4.0.0-beta.2",
"vue-loader": "15.3.0",
"vue-style-loader": "4.1.2",
"vue-template-compiler": "2.5.17",
"webpack": "4.16.5",
"vue-template-compiler": "^2.6.8",
"webpack": "^4.29.6",
"webpack-bundle-analyzer": "2.13.1",
"webpack-cli": "3.1.0",
"webpack-cli": "^3.2.3",
"webpack-dev-server": "3.1.14",
"webpack-merge": "4.1.4"
},
@ -129,5 +138,25 @@
"> 1%",
"last 2 versions",
"not ie <= 8"
]
],
"jest": {
"moduleFileExtensions": [
"js",
"json",
"vue"
],
"moduleDirectories": [
"node_modules",
"src"
],
"transform": {
"^.+\\.vue$": "vue-jest",
"^.+\\.js$": "babel-jest",
".+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$'": "jest-transform-stub"
},
"moduleNameMapper": {
"^.+.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$": "jest-transform-stub",
"^@/(.*)$": "<rootDir>/src/$1"
}
}
}

View file

@ -0,0 +1,29 @@
const users = [
{ deactivated: false, id: '1', nickname: 'john', local: true },
{ deactivated: false, id: '2', nickname: 'bob', local: false },
{ deactivated: true, id: '3', nickname: 'allis', local: true }
]
export async function fetchUsers(showLocalUsersOnly, token, page = 1) {
const filteredUsers = showLocalUsersOnly ? users.filter(user => user.local) : users
return Promise.resolve({ data: {
users: filteredUsers,
count: filteredUsers.length,
page_size: 50
}})
}
export async function toggleUserActivation(nickname, token) {
const response = users.find(user => user.nickname === nickname)
return Promise.resolve({ data: { ...response, deactivated: !response.deactivated }})
}
export async function searchUsers(query, showLocalUsersOnly, token, page = 1) {
const filteredUsers = showLocalUsersOnly ? users.filter(user => user.local) : users
const response = filteredUsers.filter(user => user.nickname === query)
return Promise.resolve({ data: {
users: response,
count: response.length,
page_size: 50
}})
}

View file

@ -26,10 +26,11 @@ export async function loginByUsername(username, password) {
})
}
export function getUserInfo() {
export function getUserInfo(token) {
return request({
url: '/api/account/verify_credentials',
method: 'post'
method: 'post',
headers: token ? { 'Authorization': `Bearer ${token}` } : {}
})
}

View file

@ -1,9 +0,0 @@
import request from '@/utils/request'
export function fetchList(query) {
return request({
url: '/transaction/list',
method: 'get',
params: query
})
}

View file

@ -1,23 +1,27 @@
import request from '@/utils/request'
import { getToken } from '@/utils/auth'
export async function fetchUsers(page = 1, showLocalUsers) {
export async function fetchUsers(showLocalUsersOnly, token, page = 1) {
return await request({
url: `/api/pleroma/admin/users?page=${page}&local_only=${showLocalUsers}`,
method: 'get'
url: `/api/pleroma/admin/users?page=${page}&local_only=${showLocalUsersOnly}`,
method: 'get',
headers: token ? { 'Authorization': `Bearer ${getToken()}` } : {}
})
}
export async function toggleUserActivation(nickname) {
export async function toggleUserActivation(nickname, token) {
return await request({
url: `/api/pleroma/admin/users/${nickname}/toggle_activation`,
method: 'patch'
method: 'patch',
headers: token ? { 'Authorization': `Bearer ${getToken()}` } : {}
})
}
export async function searchUsers(query, page = 1, showLocalUsers) {
export async function searchUsers(query, showLocalUsersOnly, token, page = 1) {
return await request({
url: `/api/pleroma/admin/users?query=${query}&page=${page}&local_only=${showLocalUsers}`,
method: 'get'
url: `/api/pleroma/admin/users?query=${query}&page=${page}&local_only=${showLocalUsersOnly}`,
method: 'get',
headers: token ? { 'Authorization': `Bearer ${getToken()}` } : {}
})
}

View file

@ -54,7 +54,7 @@ const user = {
loginByUsername(username, userInfo.password).then(response => {
const data = response.data
commit('SET_TOKEN', data.access_token)
setToken(response.data.access_token)
setToken(data.access_token)
resolve()
}).catch(error => {
reject(error)
@ -116,25 +116,25 @@ const user = {
removeToken()
resolve()
})
},
}
// 动态修改权限
ChangeRoles({ commit, dispatch }, role) {
return new Promise(resolve => {
commit('SET_TOKEN', role)
setToken(role)
getUserInfo(role).then(response => {
const data = response.data
commit('SET_ROLES', data.roles)
commit('SET_NAME', data.name)
commit('SET_ID', data.id)
commit('SET_AVATAR', data.avatar)
commit('SET_INTRODUCTION', data.introduction)
dispatch('GenerateRoutes', data) // 动态修改权限后 重绘侧边菜单
resolve()
})
})
}
// ChangeRoles({ commit, dispatch }, role) {
// return new Promise(resolve => {
// commit('SET_TOKEN', role)
// setToken(role)
// getUserInfo(role).then(response => {
// const data = response.data
// commit('SET_ROLES', data.roles)
// commit('SET_NAME', data.name)
// commit('SET_ID', data.id)
// commit('SET_AVATAR', data.avatar)
// commit('SET_INTRODUCTION', data.introduction)
// dispatch('GenerateRoutes', data) // 动态修改权限后 重绘侧边菜单
// resolve()
// })
// })
// }
}
}

View file

@ -1,13 +1,13 @@
import { fetchUsers, toggleUserActivation, searchUsers } from '@/api/users'
const user = {
const users = {
state: {
fetchedUsers: [],
loading: true,
searchQuery: '',
totalUsersCount: 0,
currentPage: 1,
showLocalUsers: false
showLocalUsersOnly: false
},
mutations: {
SET_USERS: (state, users) => {
@ -38,23 +38,23 @@ const user = {
state.searchQuery = query
},
SET_LOCAL_USERS_FILTER: (state, value) => {
state.showLocalUsers = value
state.showLocalUsersOnly = value
}
},
actions: {
async FetchUsers({ commit, state }, { page }) {
const response = await fetchUsers(page, state.showLocalUsers)
async FetchUsers({ commit, state, getters }, { page }) {
const response = await fetchUsers(state.showLocalUsersOnly, getters.token, page)
commit('SET_LOADING', true)
loadUsers(commit, page, response.data)
},
async ToggleUserActivation({ commit }, nickname) {
const response = await toggleUserActivation(nickname)
async ToggleUserActivation({ commit, getters }, nickname) {
const response = await toggleUserActivation(nickname, getters.token)
commit('SWAP_USER', response.data)
},
async SearchUsers({ commit, dispatch, state }, { query, page }) {
async SearchUsers({ commit, dispatch, state, getters }, { query, page }) {
if (query.length === 0) {
commit('SET_SEARCH_QUERY', query)
dispatch('FetchUsers', page)
@ -62,7 +62,7 @@ const user = {
commit('SET_LOADING', true)
commit('SET_SEARCH_QUERY', query)
const response = await searchUsers(query, page, state.showLocalUsers)
const response = await searchUsers(query, state.showLocalUsersOnly, getters.token, page)
loadUsers(commit, page, response.data)
}
@ -82,4 +82,4 @@ const loadUsers = (commit, page, { users, count, page_size }) => {
commit('SET_LOADING', false)
}
export default user
export default users

View file

@ -1,7 +1,5 @@
import axios from 'axios'
import { Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
// create an axios instance
const service = axios.create({
@ -9,21 +7,6 @@ const service = axios.create({
timeout: 5000 // request timeout
})
// request interceptor
service.interceptors.request.use(
config => {
if (store.getters.token) {
config.headers['Authorization'] = `Bearer ${getToken()}`
}
return config
},
error => {
// Do something with request error
console.log(error) // for debug
Promise.reject(error)
}
)
// response interceptor
service.interceptors.response.use(
response => response,

View file

@ -2,7 +2,7 @@
<div class="users-container">
<h1>Users</h1>
<div class="search-container">
<el-checkbox :value="showLocalUsers" @change="handleLocalUsersCheckbox">Local users only</el-checkbox>
<el-checkbox :value="showLocalUsersOnly" @change="handleLocalUsersCheckbox">Local users only</el-checkbox>
<el-input placeholder="Search" class="search" @input="handleDebounceSearchInput"/>
</div>
<el-table v-loading="loading" :data="users" style="width: 100%">
@ -20,6 +20,7 @@
<template slot-scope="scope">
<el-button
v-if="showDeactivatedButton(scope.row.id)"
class="toggle-activation"
type="text"
size="small"
@click="handleDeactivate(scope.row)"
@ -61,8 +62,8 @@ export default {
currentPage() {
return this.$store.state.users.currentPage
},
showLocalUsers() {
return this.$store.state.users.showLocalUsers
showLocalUsersOnly() {
return this.$store.state.users.showLocalUsersOnly
},
isDesktop() {
return this.$store.state.app.device === 'desktop'
@ -72,6 +73,11 @@ export default {
},
width() {
return this.isMobile ? 60 : false
},
rowStyle(id) {
return {
'data-user-id': id
}
}
},
created() {
@ -144,7 +150,6 @@ only screen and (max-width: 760px),
margin-right: 7px;
float: right;
}
.search-container {
display: flex;
justify-content: space-between;

View file

@ -0,0 +1,136 @@
import Vuex from 'vuex'
import { mount, shallowMount, createLocalVue } from '@vue/test-utils'
import Element from 'element-ui'
import Users from '@/views/users/index'
import storeConfig from './store.conf'
import { cloneDeep } from 'lodash'
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(Element)
jest.mock('@/api/users')
describe('Users', () => {
let store
beforeEach(() => {
store = new Vuex.Store(cloneDeep(storeConfig))
})
it('fetches initial list of users', async (done) => {
const wrapper = mount(Users, {
store,
localVue
})
await wrapper.vm.$nextTick()
expect(wrapper.vm.usersCount).toEqual(3)
done()
})
it('toggles activation status on button click', async (done) => {
const wrapper = mount(Users, {
store,
localVue
})
await wrapper.vm.$nextTick()
const user = store.state.users.fetchedUsers[1]
expect(user.deactivated).toBe(false)
wrapper.find('.el-table__fixed-body-wrapper table tr:nth-child(2) button').trigger('click')
await wrapper.vm.$nextTick()
const updatedUser = store.state.users.fetchedUsers[1]
expect(updatedUser.deactivated).toBe(true)
done()
})
it('starts a search on input change', async (done) => {
const wrapper = mount(Users, {
store,
localVue
})
wrapper.vm.handleDebounceSearchInput = (query) => {
store.dispatch('SearchUsers', { query, page: 1 })
}
await wrapper.vm.$nextTick()
expect(wrapper.vm.usersCount).toEqual(3)
const input = wrapper.find('input.el-input__inner')
input.element.value = 'bob'
input.trigger('input')
await wrapper.vm.$nextTick()
expect(wrapper.vm.usersCount).toEqual(1)
input.element.value = ''
input.trigger('input')
await wrapper.vm.$nextTick()
expect(wrapper.vm.usersCount).toEqual(3)
done()
})
it('shows local users on checkbox click', async (done) => {
const wrapper = mount(Users, {
store,
localVue
})
await wrapper.vm.$nextTick()
expect(wrapper.vm.usersCount).toEqual(3)
const input = wrapper.find('input.el-checkbox__original')
input.trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.vm.usersCount).toEqual(2)
input.trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.vm.usersCount).toEqual(3)
done()
})
it('shows local users with query search', async (done) => {
const wrapper = mount(Users, {
store,
localVue
})
wrapper.vm.handleDebounceSearchInput = (query) => {
store.dispatch('SearchUsers', { query, page: 1 })
}
await wrapper.vm.$nextTick()
expect(wrapper.vm.usersCount).toEqual(3)
const checkboxInput = wrapper.find('input.el-checkbox__original')
checkboxInput.trigger('click')
await wrapper.vm.$nextTick()
const searchInput = wrapper.find('input.el-input__inner')
searchInput.element.value = 'bob'
searchInput.trigger('input')
await wrapper.vm.$nextTick()
expect(wrapper.vm.usersCount).toEqual(0)
searchInput.element.value = 'allis'
searchInput.trigger('input')
await wrapper.vm.$nextTick()
expect(wrapper.vm.usersCount).toEqual(1)
searchInput.element.value = ''
searchInput.trigger('input')
await wrapper.vm.$nextTick()
expect(wrapper.vm.usersCount).toEqual(2)
checkboxInput.trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.vm.usersCount).toEqual(3)
done()
})
})

View file

@ -0,0 +1,28 @@
import getters from '@/store/getters'
import app from '@/store/modules/app'
import user from '@/store/modules/user'
import users from '@/store/modules/users'
export default {
modules: {
app,
users,
user: {
state: {
user: '',
id: '1',
status: '',
code: '',
token: 'MmwSkMgBW6lAkWCFspIjX9icmLfqSCohSi-GReAZrQw',
name: 'john',
avatar: '',
introduction: '',
roles: ["admin"],
setting: {
articlePlatform: []
}
}, ...user
}
},
getters
}

3382
yarn.lock

File diff suppressed because it is too large Load diff