feat: refs #8534 implement navigation and state query guards for improved routing control #1621
|
@ -0,0 +1,36 @@
|
|||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { ref, nextTick } from 'vue';
|
||||
import { stateQueryGuard } from 'src/router/hooks';
|
||||
import { useStateQueryStore } from 'src/stores/useStateQueryStore';
|
||||
|
||||
|
||||
vi.mock('src/stores/useStateQueryStore', () => {
|
||||
const isLoading = ref(true);
|
||||
return {
|
||||
useStateQueryStore: () => ({
|
||||
isLoading: () => isLoading,
|
||||
setLoading: isLoading,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe('hooks', () => {
|
||||
describe('stateQueryGuard', () => {
|
||||
const foo = { name: 'foo' };
|
||||
it('should wait until the state query is not loading and then call next()', async () => {
|
||||
const next = vi.fn();
|
||||
|
||||
stateQueryGuard(foo, { name: 'bar' }, next);
|
||||
expect(next).not.toHaveBeenCalled();
|
||||
|
||||
useStateQueryStore().setLoading.value = false;
|
||||
await nextTick();
|
||||
expect(next).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should ignore if both routes are the same', () => {
|
||||
const next = vi.fn();
|
||||
stateQueryGuard(foo, foo, next);
|
||||
expect(next).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,95 @@
|
|||
import { useRole } from 'src/composables/useRole';
|
||||
alexm
commented
He separado las funciones del router asi se puede testear las funciones sin pasar por el router real de vue He separado las funciones del router asi se puede testear las funciones sin pasar por el router real de vue
|
||||
import { useUserConfig } from 'src/composables/useUserConfig';
|
||||
import { useTokenConfig } from 'src/composables/useTokenConfig';
|
||||
import { useAcl } from 'src/composables/useAcl';
|
||||
import { isLoggedIn } from 'src/utils/session';
|
||||
import { useSession } from 'src/composables/useSession';
|
||||
import { useStateQueryStore } from 'src/stores/useStateQueryStore';
|
||||
import { watch } from 'vue';
|
||||
import { i18n } from 'src/boot/i18n';
|
||||
|
||||
let session = null;
|
||||
const { t, te } = i18n.global;
|
||||
|
||||
export async function navigationGuard(to, from, next, Router, state) {
|
||||
if (!session) session = useSession();
|
||||
const outLayout = Router.options.routes[0].children.map((r) => r.name);
|
||||
if (!session.isLoggedIn() && !outLayout.includes(to.name)) {
|
||||
return next({ name: 'Login', query: { redirect: to.fullPath } });
|
||||
}
|
||||
|
||||
if (isLoggedIn()) {
|
||||
const stateRoles = state.getRoles().value;
|
||||
if (stateRoles.length === 0) {
|
||||
await useRole().fetch();
|
||||
await useAcl().fetch();
|
||||
await useUserConfig().fetch();
|
||||
await useTokenConfig().fetch();
|
||||
}
|
||||
const matches = to.matched;
|
||||
const hasRequiredAcls = matches.every((route) => {
|
||||
const meta = route.meta;
|
||||
if (!meta?.acls) return true;
|
||||
return useAcl().hasAny(meta.acls);
|
||||
});
|
||||
if (!hasRequiredAcls) return next({ path: '/' });
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
export async function stateQueryGuard(to, from, next) {
|
||||
alexm
commented
Esta es la nueva Esta es la nueva
alexm
commented
Esta es la nueva, todo lo demás está movido Esta es la nueva, todo lo demás está movido
|
||||
if (to.name !== from.name) {
|
||||
const stateQuery = useStateQueryStore();
|
||||
await waitUntilFalse(stateQuery.isLoading());
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
export function setPageTitle(to) {
|
||||
let title = t(`login.title`);
|
||||
|
||||
const matches = to.matched;
|
||||
if (matches && matches.length > 1) {
|
||||
const module = matches[1];
|
||||
const moduleTitle = module.meta?.title;
|
||||
if (moduleTitle) {
|
||||
title = t(`globals.pageTitles.${moduleTitle}`);
|
||||
}
|
||||
}
|
||||
|
||||
const childPage = to.meta;
|
||||
const childPageTitle = childPage?.title;
|
||||
if (childPageTitle && matches.length > 2) {
|
||||
if (title != '') title += ': ';
|
||||
|
||||
const moduleLocale = `globals.pageTitles.${childPageTitle}`;
|
||||
const pageTitle = te(moduleLocale)
|
||||
? t(moduleLocale)
|
||||
: t(`globals.pageTitles.${childPageTitle}`);
|
||||
const idParam = to.params?.id;
|
||||
const idPageTitle = `${idParam} - ${pageTitle}`;
|
||||
const builtTitle = idParam ? idPageTitle : pageTitle;
|
||||
|
||||
title += builtTitle;
|
||||
}
|
||||
|
||||
document.title = title;
|
||||
}
|
||||
|
||||
function waitUntilFalse(ref) {
|
||||
return new Promise((resolve) => {
|
||||
if (!ref.value) return resolve();
|
||||
const stop = watch(
|
||||
ref,
|
||||
(val) => {
|
||||
if (!val) {
|
||||
stop();
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
});
|
||||
}
|
|
@ -6,101 +6,25 @@ import {
|
|||
createWebHashHistory,
|
||||
} from 'vue-router';
|
||||
import routes from './routes';
|
||||
import { i18n } from 'src/boot/i18n';
|
||||
import { useState } from 'src/composables/useState';
|
||||
import { useRole } from 'src/composables/useRole';
|
||||
import { useUserConfig } from 'src/composables/useUserConfig';
|
||||
import { useTokenConfig } from 'src/composables/useTokenConfig';
|
||||
import { useAcl } from 'src/composables/useAcl';
|
||||
import { isLoggedIn } from 'src/utils/session';
|
||||
import { useSession } from 'src/composables/useSession';
|
||||
import { navigationGuard, setPageTitle, stateQueryGuard } from './hooks';
|
||||
|
||||
let session = null;
|
||||
const { t, te } = i18n.global;
|
||||
|
||||
const createHistory = process.env.SERVER
|
||||
? createMemoryHistory
|
||||
: process.env.VUE_ROUTER_MODE === 'history'
|
||||
? createWebHistory
|
||||
: createWebHashHistory;
|
||||
const webHistory =
|
||||
process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory;
|
||||
const createHistory = process.env.SERVER ? createMemoryHistory : webHistory;
|
||||
|
||||
const Router = createRouter({
|
||||
scrollBehavior: () => ({ left: 0, top: 0 }),
|
||||
routes,
|
||||
|
||||
// Leave this as is and make changes in quasar.conf.js instead!
|
||||
// quasar.conf.js -> build -> vueRouterMode
|
||||
// quasar.conf.js -> build -> publicPath
|
||||
history: createHistory(process.env.VUE_ROUTER_BASE),
|
||||
});
|
||||
|
||||
/*
|
||||
* If not building with SSR mode, you can
|
||||
* directly export the Router instantiation;
|
||||
*
|
||||
* The function below can be async too; either use
|
||||
* async/await or return a Promise which resolves
|
||||
* with the Router instance.
|
||||
*/
|
||||
export { Router };
|
||||
export default defineRouter(function (/* { store, ssrContext } */) {
|
||||
export default defineRouter(() => {
|
||||
const state = useState();
|
||||
Router.beforeEach(async (to, from, next) => {
|
||||
if (!session) session = useSession();
|
||||
const outLayout = Router.options.routes[0].children.map((r) => r.name);
|
||||
if (!session.isLoggedIn() && !outLayout.includes(to.name)) {
|
||||
return next({ name: 'Login', query: { redirect: to.fullPath } });
|
||||
}
|
||||
|
||||
if (isLoggedIn()) {
|
||||
const stateRoles = state.getRoles().value;
|
||||
if (stateRoles.length === 0) {
|
||||
await useRole().fetch();
|
||||
await useAcl().fetch();
|
||||
await useUserConfig().fetch();
|
||||
await useTokenConfig().fetch();
|
||||
}
|
||||
const matches = to.matched;
|
||||
const hasRequiredAcls = matches.every((route) => {
|
||||
const meta = route.meta;
|
||||
if (!meta?.acls) return true;
|
||||
return useAcl().hasAny(meta.acls);
|
||||
});
|
||||
if (!hasRequiredAcls) return next({ path: '/' });
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
Router.afterEach((to) => {
|
||||
let title = t(`login.title`);
|
||||
|
||||
const matches = to.matched;
|
||||
if (matches && matches.length > 1) {
|
||||
const module = matches[1];
|
||||
const moduleTitle = module.meta && module.meta.title;
|
||||
if (moduleTitle) {
|
||||
title = t(`globals.pageTitles.${moduleTitle}`);
|
||||
}
|
||||
}
|
||||
|
||||
const childPage = to.meta;
|
||||
const childPageTitle = childPage && childPage.title;
|
||||
if (childPageTitle && matches.length > 2) {
|
||||
if (title != '') title += ': ';
|
||||
|
||||
const moduleLocale = `globals.pageTitles.${childPageTitle}`;
|
||||
const pageTitle = te(moduleLocale)
|
||||
? t(moduleLocale)
|
||||
: t(`globals.pageTitles.${childPageTitle}`);
|
||||
const idParam = to.params && to.params.id;
|
||||
const idPageTitle = `${idParam} - ${pageTitle}`;
|
||||
const builtTitle = idParam ? idPageTitle : pageTitle;
|
||||
|
||||
title += builtTitle;
|
||||
}
|
||||
document.title = title;
|
||||
});
|
||||
Router.beforeEach((to, from, next) => navigationGuard(to, from, next, Router, state));
|
||||
jsegarra
commented
esta es una lastima que aqui no esta es una lastima que aqui no
|
||||
Router.beforeEach(stateQueryGuard);
|
||||
jsegarra
commented
Router.beforeEach( stateQueryGuard); Router.beforeEach( stateQueryGuard);
|
||||
Router.afterEach(setPageTitle);
|
||||
jsegarra
commented
Router.afterEach(setPageTitle); Router.afterEach(setPageTitle);
|
||||
|
||||
Router.onError(({ message }) => {
|
||||
const errorMessages = [
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
Cypress.Commands.add('selectTravel', (warehouse = '1') => {
|
||||
cy.get('i[data-cy="Travel_icon"]').click();
|
||||
cy.get('input[data-cy="Warehouse Out_select"]').type(warehouse);
|
||||
cy.selectOption('input[data-cy="Warehouse Out_select"]', warehouse);
|
||||
cy.get('div[role="listbox"] > div > div[role="option"]').eq(0).click();
|
||||
cy.get('button[data-cy="save-filter-travel-form"]').click();
|
||||
cy.get('tr').eq(1).click();
|
||||
|
@ -9,7 +9,6 @@ Cypress.Commands.add('selectTravel', (warehouse = '1') => {
|
|||
Cypress.Commands.add('deleteEntry', () => {
|
||||
cy.get('[data-cy="descriptor-more-opts"]').should('be.visible').click();
|
||||
cy.waitForElement('div[data-cy="delete-entry"]').click();
|
||||
cy.url().should('include', 'list');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('createEntry', () => {
|
||||
|
|
|
@ -28,12 +28,8 @@ describe('EntryDescriptor', () => {
|
|||
cy.get('.q-notification__message')
|
||||
.eq(2)
|
||||
.should('have.text', 'Entry prices recalculated');
|
||||
|
||||
cy.get('[data-cy="descriptor-more-opts"]').click();
|
||||
alexm
commented
deleteEntry ya tiene deleteEntry ya tiene
`cy.get('[data-cy="descriptor-more-opts"]').click();` dentro
|
||||
cy.deleteEntry();
|
||||
|
||||
cy.log(previousUrl);
|
||||
|
||||
cy.visit(previousUrl);
|
||||
|
||||
cy.waitForElement('[data-cy="entry-buys"]');
|
||||
|
|
Loading…
Reference in New Issue
esto es nuevo?