mirror of
https://github.com/directus/directus.git
synced 2026-01-27 21:17:56 -05:00
Logout route (#238)
* Add logout route * Don't render spinner on top of content * Use logout route instead of onBeforeEnterLogout handler * Remove onBeforeLogoutRouteEnter test * Fix warnings in tests
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<transition name="fade">
|
||||
<div class="hydrating" v-show="hydrating">
|
||||
<div class="hydrating" v-if="hydrating">
|
||||
<v-progress-circular indeterminate />
|
||||
</div>
|
||||
</transition>
|
||||
<router-view />
|
||||
<router-view v-if="!hydrating" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import CollectionsNavigation from './navigation.vue';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import useNavigation from '../../compositions/use-navigation';
|
||||
import * as useNavigation from '../../compositions/use-navigation';
|
||||
import VList, {
|
||||
VListItem,
|
||||
VListItemContent,
|
||||
@@ -10,8 +10,6 @@ import VList, {
|
||||
} from '@/components/v-list';
|
||||
import VIcon from '@/components/v-icon';
|
||||
|
||||
jest.mock('../../compositions/use-navigation');
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-list', VList);
|
||||
@@ -22,16 +20,14 @@ localVue.component('v-list-item-icon', VListItemIcon);
|
||||
localVue.component('v-icon', VIcon);
|
||||
|
||||
describe('Modules / Collections / Components / CollectionsNavigation', () => {
|
||||
beforeEach(() => {
|
||||
(useNavigation as jest.Mock).mockImplementation(() => ({
|
||||
navItems: {
|
||||
value: []
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
it('Uses useNavigation to get navigation links', () => {
|
||||
jest.spyOn(useNavigation, 'default').mockImplementation(
|
||||
() =>
|
||||
({
|
||||
navItems: []
|
||||
} as any)
|
||||
);
|
||||
shallowMount(CollectionsNavigation, { localVue });
|
||||
expect(useNavigation).toHaveBeenCalled();
|
||||
expect(useNavigation.default).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import Vue from 'vue';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import { Route } from 'vue-router';
|
||||
import {
|
||||
onBeforeEach,
|
||||
onBeforeEnterProjectChooser,
|
||||
replaceRoutes,
|
||||
defaultRoutes,
|
||||
onBeforeEnterLogout
|
||||
} from './router';
|
||||
import { onBeforeEach, onBeforeEnterProjectChooser, replaceRoutes, defaultRoutes } from './router';
|
||||
import api from '@/api';
|
||||
import * as auth from '@/auth';
|
||||
import { useProjectsStore } from '@/stores/projects';
|
||||
@@ -256,17 +250,6 @@ describe('Router', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('onBeforeEnterLogout', () => {
|
||||
it('Calls logout and redirects to login page', async () => {
|
||||
const to = { ...route, path: '/my-project/logout', params: { project: 'my-project' } };
|
||||
const from = route;
|
||||
const next = jest.fn();
|
||||
await onBeforeEnterLogout(to, from, next);
|
||||
expect(auth.logout).toHaveBeenCalled();
|
||||
expect(next).toHaveBeenCalledWith('/my-project/login');
|
||||
});
|
||||
});
|
||||
|
||||
describe('replaceRoutes', () => {
|
||||
it('Calls the handler with the default routes', async () => {
|
||||
const handler = jest.fn(() => []);
|
||||
|
||||
@@ -2,8 +2,9 @@ import VueRouter, { NavigationGuard, RouteConfig } from 'vue-router';
|
||||
import Debug from '@/routes/debug.vue';
|
||||
import { useProjectsStore } from '@/stores/projects';
|
||||
import LoginRoute from '@/routes/login';
|
||||
import LogoutRoute from '@/routes/logout';
|
||||
import ProjectChooserRoute from '@/routes/project-chooser';
|
||||
import { checkAuth, logout } from '@/auth';
|
||||
import { checkAuth } from '@/auth';
|
||||
import { hydrate, dehydrate } from '@/hydrate';
|
||||
import useAppStore from '@/stores/app';
|
||||
|
||||
@@ -13,12 +14,6 @@ export const onBeforeEnterProjectChooser: NavigationGuard = (to, from, next) =>
|
||||
next();
|
||||
};
|
||||
|
||||
export const onBeforeEnterLogout: NavigationGuard = async (to, from, next) => {
|
||||
const currentProjectKey = to.params.project;
|
||||
await logout({ navigate: false });
|
||||
next(`/${currentProjectKey}/login`);
|
||||
};
|
||||
|
||||
export const defaultRoutes: RouteConfig[] = [
|
||||
{
|
||||
name: 'project-chooser',
|
||||
@@ -52,7 +47,7 @@ export const defaultRoutes: RouteConfig[] = [
|
||||
{
|
||||
name: 'logout',
|
||||
path: '/:project/logout',
|
||||
beforeEnter: onBeforeEnterLogout
|
||||
component: LogoutRoute
|
||||
},
|
||||
/**
|
||||
* @NOTE
|
||||
|
||||
4
src/routes/logout/index.ts
Normal file
4
src/routes/logout/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import LogoutRoute from './logout.vue';
|
||||
|
||||
export { LogoutRoute };
|
||||
export default LogoutRoute;
|
||||
23
src/routes/logout/logout.test.ts
Normal file
23
src/routes/logout/logout.test.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import LogoutRoute from './logout.vue';
|
||||
import { logout } from '@/auth';
|
||||
import VCircularProgress from '@/components/v-progress/circular/';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
|
||||
jest.mock('@/auth');
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.component('v-progress-circular', VCircularProgress);
|
||||
localVue.use(VueCompositionAPI);
|
||||
|
||||
describe('Routes / Logout', () => {
|
||||
it('Calls logout on mount', async () => {
|
||||
const component = shallowMount(LogoutRoute, {
|
||||
localVue
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(logout).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
27
src/routes/logout/logout.vue
Normal file
27
src/routes/logout/logout.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<div class="logout">
|
||||
<v-progress-circular indeterminate />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted } from '@vue/composition-api';
|
||||
import { logout } from '@/auth';
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
onMounted(() => {
|
||||
logout();
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.logout {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
7
src/routes/logout/readme.md
Normal file
7
src/routes/logout/readme.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Logout View
|
||||
|
||||
Renders an empty page with a spinner, and logs the user out. Once the user is logged out, it will redirect to login.
|
||||
|
||||
## Why a separate route?
|
||||
|
||||
If we were to logout on any other route that uses the private view, it would mean that there will be a bunch of errors in the devtools, as a lot of things will be trying to read from the store, which will be cleared. This extra route makes sure we can safely empty the store without any component still reading from it.
|
||||
@@ -23,7 +23,7 @@ describe('Stores / User', () => {
|
||||
});
|
||||
|
||||
describe('Hydrate', () => {
|
||||
it('Calls the right endpoint', () => {
|
||||
it('Calls the right endpoint', async () => {
|
||||
(api.get as jest.Mock).mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
@@ -36,8 +36,12 @@ describe('Stores / User', () => {
|
||||
projectsStore.state.currentProjectKey = 'my-project';
|
||||
const userStore = useUserStore(req);
|
||||
|
||||
userStore.hydrate().then(() => {
|
||||
expect(api.get).toHaveBeenCalledWith('/my-project/users/me');
|
||||
userStore.hydrate();
|
||||
|
||||
expect(api.get).toHaveBeenCalledWith('/my-project/users/me', {
|
||||
params: {
|
||||
fields: '*,avatar.data'
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import HeaderBar from './header-bar.vue';
|
||||
import PortalVue from 'portal-vue';
|
||||
|
||||
import VButton from '@/components/v-button';
|
||||
import VIcon from '@/components/v-icon';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.use(PortalVue);
|
||||
localVue.component('v-button', VButton);
|
||||
localVue.component('v-icon', VIcon);
|
||||
|
||||
|
||||
@@ -3,9 +3,11 @@ import VueCompositionAPI from '@vue/composition-api';
|
||||
import PrivateView from './private-view.vue';
|
||||
import VOverlay from '@/components/v-overlay';
|
||||
import VProgressCircular from '@/components/v-progress/circular';
|
||||
import PortalVue from 'portal-vue';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.use(PortalVue);
|
||||
localVue.component('v-overlay', VOverlay);
|
||||
localVue.component('v-progress-circular', VProgressCircular);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user