Merge branch 'feature/update-login-process' into 'master'

Update login process

Closes #11

See merge request pleroma/admin-fe!9
This commit is contained in:
feld 2019-03-23 14:09:48 +00:00
commit fea370c2a8
25 changed files with 469 additions and 288 deletions

View file

@ -1,5 +1,4 @@
module.exports = {
NODE_ENV: '"development"',
ENV_CONFIG: '"dev"',
BASE_API: '"http://localhost:4000"'
ENV_CONFIG: '"dev"'
}

View file

@ -92,6 +92,7 @@
"eslint-loader": "2.0.0",
"eslint-plugin-vue": "4.7.1",
"file-loader": "1.1.11",
"flush-promises": "^1.0.2",
"friendly-errors-webpack-plugin": "1.7.0",
"hash-sum": "1.0.2",
"html-webpack-plugin": "^3.2.0",

View file

@ -0,0 +1,59 @@
const users = [
{ username: 'bob', password: '123456' }
]
export async function loginByUsername(username, password) {
const user = users.find(user => user.username === username)
const verifyPassword = user.password === password
const data = {
'token_type': 'Bearer',
'scope': 'read write follow',
'refresh_token': 'foo123',
'me': 'bob',
'expires_in': 600,
'access_token': 'bar123'
}
return verifyPassword
? Promise.resolve({ data })
: Promise.reject({ message: 'Invalid credentials' })
}
export function getUserInfo(token, authHost) {
const userInfo = {
'name_html': 'bob',
'background_image': null,
'friends_count': 0,
'description_html': '',
'followers_count': 0,
'locked': false,
'follows_you': true,
'statusnet_blocking': false,
'statusnet_profile_url': '',
'following': true,
'id': '10',
'is_local': true,
'profile_image_url': '',
'role': 'admin',
'profile_image_url_profile_size': '',
'rights': { 'admin': true, 'delete_others_notice': true },
'token': 'foo123456',
'no_rich_text': false,
'statuses_count': 0,
'cover_photo': '',
'hide_follows': false,
'pleroma': { 'confirmation_pending': false, 'deactivated': false, 'tags': ['force_nsfw'] },
'profile_image_url_original': '',
'created_at': 'Fri Mar 01 15:15:19 +0000 2019',
'fields': [],
'name': 'bob',
'description': '',
'favourites_count': 0,
'default_scope': 'public',
'profile_image_url_https': '',
'hide_followers': false,
'show_role': true,
'screen_name': 'bob' }
return Promise.resolve({ data: userInfo })
}

View file

@ -4,7 +4,7 @@ const users = [
{ deactivated: true, id: 'abc', nickname: 'john', local: true, roles: { admin: false, moderator: false }, tags: ['strip_media'] }
]
export async function fetchUsers(showLocalUsersOnly, token, page = 1) {
export async function fetchUsers(showLocalUsersOnly, authHost, token, page = 1) {
const filteredUsers = showLocalUsersOnly ? users.filter(user => user.local) : users
return Promise.resolve({ data: {
users: filteredUsers,
@ -13,12 +13,12 @@ export async function fetchUsers(showLocalUsersOnly, token, page = 1) {
}})
}
export async function toggleUserActivation(nickname, token) {
export async function toggleUserActivation(nickname, authHost, 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) {
export async function searchUsers(query, showLocalUsersOnly, authHost, token, page = 1) {
const filteredUsers = showLocalUsersOnly ? users.filter(user => user.local) : users
const response = filteredUsers.filter(user => user.nickname === query)
return Promise.resolve({ data: {
@ -28,28 +28,28 @@ export async function searchUsers(query, showLocalUsersOnly, token, page = 1) {
}})
}
export async function addRight(nickname, right, token) {
export async function addRight(nickname, right, authHost, token) {
return Promise.resolve({ data:
{ [`is_${right}`]: true }
})
}
export async function deleteRight(nickname, right, token) {
export async function deleteRight(nickname, right, authHost, token) {
return Promise.resolve({ data:
{ [`is_${right}`]: false }
})
}
export async function deleteUser(nickname, token) {
export async function deleteUser(nickname, authHost, token) {
return Promise.resolve({ data:
nickname
})
}
export async function tagUser(nickname, tag, token) {
export async function tagUser(nickname, tag, authHost, token) {
return Promise.resolve()
}
export async function untagUser(nickname, tag, token) {
export async function untagUser(nickname, tag, authHost, token) {
return Promise.resolve()
}

View file

@ -1,7 +1,9 @@
import request from '@/utils/request'
import { baseName } from './utils'
export async function loginByUsername(username, password) {
export async function loginByUsername(username, password, authHost) {
const appsRequest = await request({
baseURL: baseName(authHost),
url: '/api/v1/apps',
method: 'post',
data: {
@ -14,6 +16,7 @@ export async function loginByUsername(username, password) {
const app = appsRequest.data
return request({
baseURL: baseName(authHost),
url: '/oauth/token',
method: 'post',
data: {
@ -26,8 +29,9 @@ export async function loginByUsername(username, password) {
})
}
export function getUserInfo(token) {
export function getUserInfo(token, authHost) {
return request({
baseURL: baseName(authHost),
url: '/api/account/verify_credentials',
method: 'post',
headers: token ? { 'Authorization': `Bearer ${token}` } : {}

View file

@ -1,56 +1,64 @@
import request from '@/utils/request'
import { getToken } from '@/utils/auth'
import { baseName } from './utils'
export async function fetchUsers(showLocalUsersOnly, token, page = 1) {
export async function fetchUsers(showLocalUsersOnly, authHost, token, page = 1) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/users?page=${page}&local_only=${showLocalUsersOnly}`,
method: 'get',
headers: authHeaders(token)
})
}
export async function toggleUserActivation(nickname, token) {
export async function toggleUserActivation(nickname, authHost, token) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/users/${nickname}/toggle_activation`,
method: 'patch',
headers: authHeaders(token)
})
}
export async function searchUsers(query, showLocalUsersOnly, token, page = 1) {
export async function searchUsers(query, showLocalUsersOnly, authHost, token, page = 1) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/users?query=${query}&page=${page}&local_only=${showLocalUsersOnly}`,
method: 'get',
headers: authHeaders(token)
})
}
export async function addRight(nickname, right, token) {
export async function addRight(nickname, right, authHost, token) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/permission_group/${nickname}/${right}`,
method: 'post',
headers: authHeaders(token)
})
}
export async function deleteRight(nickname, right, token) {
export async function deleteRight(nickname, right, authHost, token) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/permission_group/${nickname}/${right}`,
method: 'delete',
headers: authHeaders(token)
})
}
export async function deleteUser(nickname, token) {
export async function deleteUser(nickname, authHost, token) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/user.json?nickname=${nickname}`,
method: 'delete',
headers: authHeaders(token)
})
}
export async function tagUser(nickname, tag, token) {
export async function tagUser(nickname, tag, authHost, token) {
return await request({
baseURL: baseName(authHost),
url: '/api/pleroma/admin/users/tag',
method: 'put',
headers: authHeaders(token),
@ -58,8 +66,9 @@ export async function tagUser(nickname, tag, token) {
})
}
export async function untagUser(nickname, tag, token) {
export async function untagUser(nickname, tag, authHost, token) {
return await request({
baseURL: baseName(authHost),
url: '/api/pleroma/admin/users/tag',
method: 'delete',
headers: authHeaders(token),

5
src/api/utils.js Normal file
View file

@ -0,0 +1,5 @@
const isLocalhost = (instanceName) =>
instanceName.startsWith('localhost:') || instanceName.startsWith('127.0.0.1:')
export const baseName = (instanceName) =>
isLocalhost(instanceName) ? `http://${instanceName}` : `https://${instanceName}`

View file

@ -76,8 +76,9 @@ export default {
login: {
title: 'Login Form',
logIn: 'Log in',
username: 'Username',
username: 'Username@Host',
password: 'Password',
errorMessage: 'Username must contain username and host, e.g. john@pleroma.social',
any: 'any',
thirdparty: 'Or connect with',
thirdpartyTips: 'Can not be simulated on local, so please combine you own business simulation! ! !'

View file

@ -16,7 +16,7 @@ function hasPermission(roles, permissionRoles) {
const whiteList = ['/login', '/auth-redirect']// no redirect whitelist
router.beforeEach((to, from, next) => {
export const beforeEachRoute = (to, from, next) => {
NProgress.start() // start progress bar
if (getToken()) { // determine if there has token
/* has token*/
@ -24,12 +24,12 @@ router.beforeEach((to, from, next) => {
next({ path: '/' })
NProgress.done() // if current page is dashboard will not trigger afterEach hook, so manually handle it
} else {
if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息
store.dispatch('GetUserInfo').then(res => { // 拉取user_info
if (store.getters.roles.length === 0) {
store.dispatch('GetUserInfo').then(res => {
const roles = res.data.rights.admin ? ['admin'] : []
store.dispatch('GenerateRoutes', { roles }).then(() => { // 根据roles权限生成可访问的路由表
router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
store.dispatch('GenerateRoutes', { roles }).then(() => {
router.addRoutes(store.getters.addRouters)
next({ ...to, replace: true })
})
}).catch((err) => {
store.dispatch('FedLogOut').then(() => {
@ -38,25 +38,24 @@ router.beforeEach((to, from, next) => {
})
})
} else {
// 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓
if (hasPermission(store.getters.roles, to.meta.roles)) {
next()
} else {
next({ path: '/401', replace: true, query: { noGoBack: true }})
}
// 可删 ↑
}
}
} else {
/* has no token*/
if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页
next(`/login?redirect=${to.path}`)
NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it
}
}
})
}
router.beforeEach(beforeEachRoute)
router.afterEach(() => {
NProgress.done() // finish progress bar

View file

@ -15,6 +15,7 @@ const getters = {
permission_routers: state => state.permission.routers,
addRouters: state => state.permission.addRouters,
errorLogs: state => state.errorLog.logs,
users: state => state.users.fetchedUsers
users: state => state.users.fetchedUsers,
authHost: state => state.user.authHost
}
export default getters

View file

@ -1,5 +1,5 @@
import { loginByUsername, getUserInfo } from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { getToken, setToken, removeToken, getAuthHost, setAuthHost, removeAuthHost } from '@/utils/auth'
const user = {
state: {
@ -8,6 +8,7 @@ const user = {
status: '',
code: '',
token: getToken(),
authHost: getAuthHost(),
name: '',
avatar: '',
introduction: '',
@ -44,19 +45,24 @@ const user = {
},
SET_ID: (state, id) => {
state.id = id
},
SET_AUTH_HOST: (state, authHost) => {
state.authHost = authHost
}
},
actions: {
LoginByUsername({ commit }, userInfo) {
const username = userInfo.username.trim()
LoginByUsername({ commit, dispatch }, { username, authHost, password }) {
return new Promise((resolve, reject) => {
loginByUsername(username, userInfo.password).then(response => {
loginByUsername(username, password, authHost).then(response => {
const data = response.data
commit('SET_TOKEN', data.access_token)
commit('SET_AUTH_HOST', authHost)
setToken(data.access_token)
setAuthHost(authHost)
resolve()
}).catch(error => {
dispatch('addErrorLog', { message: error.message })
reject(error)
})
})
@ -64,7 +70,7 @@ const user = {
GetUserInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getUserInfo(state.token).then(response => {
getUserInfo(state.token, state.authHost).then(response => {
const data = response.data
if (!data) {
@ -91,11 +97,13 @@ const user = {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
removeToken()
removeAuthHost()
},
FedLogOut({ commit }) {
return new Promise(resolve => {
commit('SET_TOKEN', '')
removeToken()
removeAuthHost()
resolve()
})
}

View file

@ -43,14 +43,14 @@ const users = {
},
actions: {
async FetchUsers({ commit, state, getters }, { page }) {
const response = await fetchUsers(state.showLocalUsersOnly, getters.token, page)
const response = await fetchUsers(state.showLocalUsersOnly, getters.authHost, getters.token, page)
commit('SET_LOADING', true)
loadUsers(commit, page, response.data)
},
async ToggleUserActivation({ commit, getters }, nickname) {
const response = await toggleUserActivation(nickname, getters.token)
const response = await toggleUserActivation(nickname, getters.authHost, getters.token)
commit('SWAP_USER', response.data)
},
@ -62,7 +62,7 @@ const users = {
commit('SET_LOADING', true)
commit('SET_SEARCH_QUERY', query)
const response = await searchUsers(query, state.showLocalUsersOnly, getters.token, page)
const response = await searchUsers(query, state.showLocalUsersOnly, getters.authHost, getters.token, page)
loadUsers(commit, page, response.data)
}
@ -73,24 +73,24 @@ const users = {
},
async ToggleRight({ commit, getters }, { user, right }) {
user.roles[right]
? await deleteRight(user.nickname, right, getters.token)
: await addRight(user.nickname, right, getters.token)
? await deleteRight(user.nickname, right, getters.authHost, getters.token)
: await addRight(user.nickname, right, getters.authHost, getters.token)
const updatedUser = { ...user, roles: { ...user.roles, [right]: !user.roles[right] }}
commit('SWAP_USER', updatedUser)
},
async DeleteUser({ commit, getters }, user) {
await deleteUser(user.nickname, getters.token)
await deleteUser(user.nickname, getters.authHost, getters.token)
const updatedUser = { ...user, deactivated: true }
commit('SWAP_USER', updatedUser)
},
async ToggleTag({ commit, getters }, { user, tag }) {
if (user.tags.includes(tag)) {
await untagUser(user.nickname, tag, getters.token)
await untagUser(user.nickname, tag, getters.authHost, getters.token)
const updatedUser = { ...user, tags: user.tags.filter(userTag => userTag !== tag) }
commit('SWAP_USER', updatedUser)
} else {
await tagUser(user.nickname, tag, getters.token)
await tagUser(user.nickname, tag, getters.authHost, getters.token)
const updatedUser = { ...user, tags: [...user.tags, tag] }
commit('SWAP_USER', updatedUser)
}

View file

@ -1,6 +1,7 @@
import Cookies from 'js-cookie'
const TokenKey = 'Admin-Token'
const AuthHostKey = 'Auth-Host'
export function getToken() {
return Cookies.get(TokenKey)
@ -13,3 +14,15 @@ export function setToken(token) {
export function removeToken() {
return Cookies.remove(TokenKey)
}
export function getAuthHost() {
return Cookies.get(AuthHostKey)
}
export function setAuthHost(token) {
return Cookies.set(AuthHostKey, token)
}
export function removeAuthHost() {
return Cookies.remove(AuthHostKey)
}

View file

@ -3,7 +3,6 @@ import { Message } from 'element-ui'
// create an axios instance
const service = axios.create({
baseURL: process.env.BASE_API, // api 的 base_url
timeout: 5000 // request timeout
})

View file

@ -1,143 +0,0 @@
<template>
<div>
<el-card class="box-card" style="margin-top:40px;">
<div slot="header" class="clearfix">
<svg-icon icon-class="international" />
<span style="margin-left:10px;">{{ $t('i18nView.title') }}</span>
</div>
<div>
<el-radio-group v-model="lang" size="small">
<el-radio label="zh" border>简体中文</el-radio>
<el-radio label="en" border>English</el-radio>
<el-radio label="es" border>Español</el-radio>
</el-radio-group>
<el-tag style="margin-top:15px;display:block;" type="info">{{ $t('i18nView.note') }}</el-tag>
</div>
</el-card>
<el-row :gutter="20" style="margin:100px 15px 50px;">
<el-col :span="12" :xs="24">
<div class="block">
<el-date-picker v-model="date" :placeholder="$t('i18nView.datePlaceholder')" type="date"/>
</div>
<div class="block">
<el-select v-model="value" :placeholder="$t('i18nView.selectPlaceholder')">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"/>
</el-select>
</div>
<div class="block">
<el-button class="item-btn" size="small">{{ $t('i18nView.default') }}</el-button>
<el-button class="item-btn" size="small" type="primary">{{ $t('i18nView.primary') }}</el-button>
<el-button class="item-btn" size="small" type="success">{{ $t('i18nView.success') }}</el-button>
<el-button class="item-btn" size="small" type="info">{{ $t('i18nView.info') }}</el-button>
<el-button class="item-btn" size="small" type="warning">{{ $t('i18nView.warning') }}</el-button>
<el-button class="item-btn" size="small" type="danger">{{ $t('i18nView.danger') }}</el-button>
</div>
</el-col>
<el-col :span="12" :xs="24">
<el-table :data="tableData" fit highlight-current-row border style="width: 100%">
<el-table-column :label="$t('i18nView.tableName')" prop="name" width="100" align="center"/>
<el-table-column :label="$t('i18nView.tableDate')" prop="date" width="120" align="center"/>
<el-table-column :label="$t('i18nView.tableAddress')" prop="address"/>
</el-table>
</el-col>
</el-row>
</div>
</template>
<script>
import local from './local'
const viewName = 'i18nView'
export default {
name: 'I18n',
data: function() {
return {
date: '',
tableData: [{
date: '2016-05-03',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
},
{
date: '2016-05-02',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
},
{
date: '2016-05-04',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
},
{
date: '2016-05-01',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
}],
options: [],
value: ''
}
},
computed: {
lang: {
get() {
return this.$store.state.app.language
},
set(lang) {
this.$i18n.locale = lang
this.$store.dispatch('setLanguage', lang)
}
}
},
watch: {
lang() {
this.setOptions()
}
},
created() {
if (!this.$i18n.getLocaleMessage('en')[viewName]) {
this.$i18n.mergeLocaleMessage('en', local.en)
this.$i18n.mergeLocaleMessage('zh', local.zh)
this.$i18n.mergeLocaleMessage('es', local.es)
}
this.setOptions() // set default select options
},
methods: {
setOptions() {
this.options = [
{
value: '1',
label: this.$t('i18nView.one')
},
{
value: '2',
label: this.$t('i18nView.two')
},
{
value: '3',
label: this.$t('i18nView.three')
}
]
}
}
}
</script>
<style scoped>
.box-card {
width: 600px;
max-width: 100%;
margin: 20px auto;
}
.item-btn{
margin-bottom: 15px;
margin-left: 0px;
}
.block {
padding: 25px;
}
</style>

View file

@ -1,63 +0,0 @@
export default {
zh: {
i18nView: {
title: '切换语言',
note: '本项目国际化基于 vue-i18n',
datePlaceholder: '请选择日期',
selectPlaceholder: '请选择',
tableDate: '日期',
tableName: '姓名',
tableAddress: '地址',
default: '默认按钮',
primary: '主要按钮',
success: '成功按钮',
info: '信息按钮',
warning: '警告按钮',
danger: '危险按钮',
one: '一',
two: '二',
three: '三'
}
},
en: {
i18nView: {
title: 'Switch Language',
note: 'The internationalization of this project is based on vue-i18n',
datePlaceholder: 'Pick a day',
selectPlaceholder: 'Select',
tableDate: 'tableDate',
tableName: 'tableName',
tableAddress: 'tableAddress',
default: 'default:',
primary: 'primary',
success: 'success',
info: 'info',
warning: 'warning',
danger: 'danger',
one: 'One',
two: 'Two',
three: 'Three'
}
},
es: {
i18nView: {
title: 'Switch Language',
note: 'The internationalization of this project is based on vue-i18n',
datePlaceholder: 'Pick a day',
selectPlaceholder: 'Select',
tableDate: 'tableDate',
tableName: 'tableName',
tableAddress: 'tableAddress',
default: 'default:',
primary: 'primary',
success: 'success',
info: 'info',
warning: 'warning',
danger: 'danger',
one: 'One',
two: 'Two',
three: 'Three'
}
}
}

View file

@ -45,8 +45,13 @@
</template>
<script>
import { Message } from 'element-ui'
import SvgIcon from '@/components/SvgIcon'
import i18n from '@/lang'
export default {
name: 'Login',
components: { 'svg-icon': SvgIcon },
data: function() {
return {
loginForm: {
@ -77,12 +82,35 @@ export default {
},
handleLogin() {
this.loading = true
this.$store.dispatch('LoginByUsername', this.loginForm).then(() => {
if (this.checkUsername()) {
const loginData = this.getLoginData()
this.$store.dispatch('LoginByUsername', loginData).then(() => {
this.loading = false
this.$router.push({ path: this.redirect || '/users/index' })
}).catch(() => {
this.loading = false
})
} else {
Message({
message: i18n.t('login.errorMessage'),
type: 'error',
duration: 7000
})
this.$store.dispatch('addErrorLog', { message: i18n.t('login.errorMessage') })
this.loading = false
this.$router.push({ path: this.redirect || '/users/index' })
}).catch(() => {
this.loading = false
})
}
},
checkUsername() {
return this.loginForm.username.includes('@')
},
getLoginData() {
const [username, authHost] = this.loginForm.username.split('@')
return {
username: username.trim(),
authHost: authHost.trim(),
password: this.loginForm.password
}
}
}
}

View file

@ -0,0 +1,47 @@
import Vuex from 'vuex'
import VueRouter from 'vue-router'
import { mount, createLocalVue, config } from '@vue/test-utils'
import Element from 'element-ui'
import Layout from '@/views/layout/Layout'
import storeConfig from './store.conf'
import routerConfig from './router.conf'
import { cloneDeep } from 'lodash'
import { beforeEachRoute } from '@/permission'
config.mocks["$t"] = () => {}
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(VueRouter)
localVue.use(Element)
describe('Log out', () => {
let store
let router
beforeEach(() => {
store = new Vuex.Store(cloneDeep(storeConfig))
router = new VueRouter(cloneDeep(routerConfig))
router.beforeEach(beforeEachRoute)
window.location.reload = jest.fn()
})
it('logs out user', async (done) => {
const wrapper = mount(Layout, {
store,
router,
localVue
})
const logoutButton = wrapper.find('span')
expect(store.state.user.roles.length).toBe(1)
expect(store.state.user.token).toBe('foo123')
logoutButton.trigger('click')
await wrapper.vm.$nextTick()
expect(store.state.user.roles.length).toBe(0)
expect(store.state.user.token).toBe('')
done()
})
})

View file

@ -0,0 +1,40 @@
import Layout from '@/views/layout/Layout'
export const constantRouterMap = [
{
path: '/redirect',
component: Layout,
hidden: true,
children: [
{
path: '/redirect/:path*',
component: () => import('@/views/redirect/index')
}
]
},
{
path: '/login',
component: () => import('@/views/login/index'),
hidden: true
},
{
path: '/auth-redirect',
component: () => import('@/views/login/authredirect'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/errorPage/404'),
hidden: true
},
{
path: '/401',
component: () => import('@/views/errorPage/401'),
hidden: true
},
{
path: '',
component: Layout,
redirect: '/users/index'
}
]

View file

@ -0,0 +1,34 @@
import app from '@/store/modules/app'
import errorLog from '@/store/modules/errorLog'
import permission from '@/store/modules/permission'
import tagsView from '@/store/modules/tagsView'
import user from '@/store/modules/user'
import users from '@/store/modules/users'
import getters from '@/store/getters'
export default {
modules: {
app,
errorLog,
permission,
tagsView,
user: {
...user,
state: {
id:"10",
status: '',
code: '',
token: "foo123",
authHost:"apple",
name:"bob",
avatar: '',
introduction: '',
roles: ['admin'],
setting: {
articlePlatform: []
}
}
}
},
getters
}

View file

@ -0,0 +1,106 @@
import Vuex from 'vuex'
import VueRouter from 'vue-router'
import { mount, createLocalVue, config } from '@vue/test-utils'
import flushPromises from 'flush-promises'
import Element from 'element-ui'
import Login from '@/views/login/index'
import storeConfig from './store.conf'
import routerConfig from './router.conf'
import { cloneDeep } from 'lodash'
import { beforeEachRoute } from '@/permission'
config.mocks["$t"] = () => {}
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(VueRouter)
localVue.use(Element)
jest.mock('@/api/login')
describe('Login', () => {
let store
let router
const usernameInput = 'input[name="username"]'
const passwordInput = 'input[name="password"]'
beforeEach(() => {
store = new Vuex.Store(cloneDeep(storeConfig))
router = new VueRouter(cloneDeep(routerConfig))
router.beforeEach(beforeEachRoute)
})
it('throws error if username does not contain authHost', () => {
const wrapper = mount(Login, {
store,
router,
localVue
})
const errorLog = store.state.errorLog.logs
expect(errorLog.length).toBe(0)
const submitButton = wrapper.find('button')
wrapper.find(usernameInput).element.value = 'bob'
wrapper.find(usernameInput).trigger('input')
wrapper.find(passwordInput).element.value = '1234'
wrapper.find(passwordInput).trigger('input')
submitButton.trigger('click')
const updatedErrorLog = store.state.errorLog.logs
expect(updatedErrorLog.length).toBe(1)
expect(updatedErrorLog[0].message).toEqual(
'Username must contain username and host, e.g. john@pleroma.social'
)
})
it('throws error if password is incorrect', async (done) => {
const wrapper = mount(Login, {
store,
router,
localVue
})
const errorLog = store.state.errorLog.logs
expect(errorLog.length).toBe(0)
const submitButton = wrapper.find('button')
wrapper.find(usernameInput).element.value = 'bob@apple'
wrapper.find(usernameInput).trigger('input')
wrapper.find(passwordInput).element.value = '1234'
wrapper.find(passwordInput).trigger('input')
submitButton.trigger('click')
await flushPromises()
const updatedErrorLog = store.state.errorLog.logs
expect(updatedErrorLog.length).toBe(1)
expect(updatedErrorLog[0].message).toEqual(
'Invalid credentials'
)
done()
})
it('logs user in', async (done) => {
const wrapper = mount(Login, {
store,
router,
localVue
})
const errorLog = store.state.errorLog.logs
const submitButton = wrapper.find('button')
expect(wrapper.vm.$route.path).toBe('/login')
wrapper.find(usernameInput).element.value = 'bob@apple'
wrapper.find(usernameInput).trigger('input')
wrapper.find(passwordInput).element.value = '123456'
wrapper.find(passwordInput).trigger('input')
submitButton.trigger('click')
await flushPromises()
expect(errorLog.length).toBe(0)
expect(wrapper.vm.$route.path).toBe('/')
done()
})
})

View file

@ -0,0 +1,40 @@
import Layout from '@/views/layout/Layout'
export const constantRouterMap = [
{
path: '/redirect',
component: Layout,
hidden: true,
children: [
{
path: '/redirect/:path*',
component: () => import('@/views/redirect/index')
}
]
},
{
path: '/login',
component: () => import('@/views/login/index'),
hidden: true
},
{
path: '/auth-redirect',
component: () => import('@/views/login/authredirect'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/errorPage/404'),
hidden: true
},
{
path: '/401',
component: () => import('@/views/errorPage/401'),
hidden: true
},
{
path: '',
component: Layout,
redirect: '/users/index'
}
]

View file

@ -0,0 +1,16 @@
import app from '@/store/modules/app'
import errorLog from '@/store/modules/errorLog'
import user from '@/store/modules/user'
import users from '@/store/modules/users'
import getters from '@/store/getters'
export default {
modules: {
app,
errorLog,
user,
users
},
getters
}

View file

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

View file

@ -4181,6 +4181,11 @@ flat-cache@^1.2.1:
rimraf "~2.6.2"
write "^0.2.1"
flush-promises@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/flush-promises/-/flush-promises-1.0.2.tgz#4948fd58f15281fed79cbafc86293d5bb09b2ced"
integrity sha512-G0sYfLQERwKz4+4iOZYQEZVpOt9zQrlItIxQAAYAWpfby3gbHrx0osCHz5RLl/XoXevXk0xoN4hDFky/VV9TrA==
flush-write-stream@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8"
@ -10164,18 +10169,6 @@ vue-jest@4.0.0-beta.2:
source-map "^0.5.6"
ts-jest "^23.10.5"
vue-jest@4.0.0-beta.2:
version "4.0.0-beta.2"
resolved "https://registry.yarnpkg.com/vue-jest/-/vue-jest-4.0.0-beta.2.tgz#f2120ea9d24224aad3a100c2010b0760d47ee6fe"
integrity sha512-SywBIciuIfqsCb8Eb9UQ02s06+NV8Ry8KnbyhAfnvnyFFulIuh7ujtga9eJYq720nCS4Hz4TpVtS4pD1ZbUILQ==
dependencies:
"@babel/plugin-transform-modules-commonjs" "^7.2.0"
"@vue/component-compiler-utils" "^2.4.0"
chalk "^2.1.0"
extract-from-css "^0.4.4"
source-map "^0.5.6"
ts-jest "^23.10.5"
vue-loader@15.3.0:
version "15.3.0"
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.3.0.tgz#b474d10a4e93d934a78c147fc3e314b370e9fc54"