Set up cypress component testing
This commit is contained in:
parent
5576c21cb9
commit
8368050683
|
|
@ -60,7 +60,7 @@ module.exports = {
|
|||
},
|
||||
compilerOptions: {
|
||||
compatConfig: {
|
||||
MODE: 2,
|
||||
MODE: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { resolve } from 'path';
|
|||
|
||||
export default defineConfig({
|
||||
chromeWebSecurity: false,
|
||||
|
||||
e2e: {
|
||||
baseUrl: 'http://localhost:8080',
|
||||
specPattern: 'cypress/e2e/frontend/**/*.{cy,spec}.{js,ts}',
|
||||
|
|
@ -16,14 +17,29 @@ export default defineConfig({
|
|||
});
|
||||
},
|
||||
},
|
||||
|
||||
videoUploadOnPasses: false,
|
||||
reporter: 'junit',
|
||||
|
||||
reporterOptions: {
|
||||
mochaFile: 'cypress/test-reports/frontend/cypress-results-[hash].xml',
|
||||
toConsole: true,
|
||||
},
|
||||
|
||||
projectId: 'msk-fe',
|
||||
|
||||
retries: {
|
||||
runMode: 3,
|
||||
},
|
||||
|
||||
component: {
|
||||
devServer: {
|
||||
framework: 'vue',
|
||||
bundler: 'webpack',
|
||||
webpackConfig: async () => {
|
||||
const webpackConfig = await require('./build/webpack.dev.conf');
|
||||
return webpackConfig;
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
import PageFormInput from '@/components/page-form/PageFormInput.vue';
|
||||
|
||||
describe('<PageFormInput />', () => {
|
||||
it('renders', () => {
|
||||
// see: https://test-utils.vuejs.org/guide/
|
||||
const inputSpy = cy.spy().as('inputSpy');
|
||||
cy.mount(PageFormInput, {
|
||||
props: {
|
||||
label: 'Hi',
|
||||
value: 'Some',
|
||||
onInput: inputSpy,
|
||||
},
|
||||
});
|
||||
cy.get('.page-form-input__label').should('contain.text', 'Hi');
|
||||
cy.getByDataCy('page-form-input-hi').should('have.value', 'Some').type('A');
|
||||
cy.get('@inputSpy').should('have.been.calledWith', 'SomeA');
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import SubmissionForm from '@/components/content-blocks/assignment/SubmissionForm.vue';
|
||||
|
||||
describe('SubmissionForm', () => {
|
||||
it('renders', () => {
|
||||
cy.mount(SubmissionForm, {
|
||||
props: {
|
||||
userInput: {
|
||||
final: false,
|
||||
text: 'userInput',
|
||||
},
|
||||
saved: true,
|
||||
placeholder: 'Placeholder',
|
||||
action: 'Feedback teilen',
|
||||
reopen: () => { },
|
||||
document: '',
|
||||
sharedMsg: 'Shared Message',
|
||||
onTurnIn: () => { },
|
||||
onSaveInput: () => { },
|
||||
onReopen: () => { },
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import SubmissionInput from '@/components/content-blocks/assignment/SubmissionInput.vue';
|
||||
|
||||
describe('SubmissionInput', () => {
|
||||
it('renders', () => {
|
||||
cy.mount(SubmissionInput, {
|
||||
props: {
|
||||
inputText: undefined,
|
||||
saved: true,
|
||||
readonly: false,
|
||||
placeholder: 'Placeholder',
|
||||
},
|
||||
});
|
||||
cy.getByDataCy('submission-textarea')
|
||||
.should('have.attr', 'placeholder', 'Placeholder')
|
||||
.type('Hallo Velo')
|
||||
.should('have.value', 'Hallo Velo');
|
||||
});
|
||||
});
|
||||
|
|
@ -27,124 +27,17 @@
|
|||
//
|
||||
import { makeExecutableSchema } from '@graphql-tools/schema';
|
||||
|
||||
declare global {
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
/**
|
||||
* Login via API call to the GraphQL endpoint, without calling the frontend. Faster than the other login.
|
||||
* @param username
|
||||
* @param password
|
||||
* @example
|
||||
* cy.apolloLogin('ross.geller', 'test')
|
||||
*/
|
||||
apolloLogin(username: string, password: string): Chainable<any>;
|
||||
|
||||
/**
|
||||
* Selects an element based on the `data-cy=xxx` attribute
|
||||
* @param selector - The value of the data-cy attribute to select
|
||||
* @example
|
||||
* cy.getByDataCy('my-new-button')
|
||||
*/
|
||||
getByDataCy(selector: string): Chainable<Element>;
|
||||
|
||||
selectClass(schoolClass: string): void;
|
||||
|
||||
login(username: string, password: string, visitLogin?: boolean): void;
|
||||
|
||||
fakeLogin(username: string, password: string): void;
|
||||
|
||||
isSubmissionReadOnly(myText: string): void;
|
||||
|
||||
openSidebar(): void;
|
||||
|
||||
setup(): void;
|
||||
}
|
||||
}
|
||||
}
|
||||
// installed a fork of the original package, because of this issue:
|
||||
// https://github.com/tgriesser/cypress-graphql-mock/issues/23
|
||||
// todo: once above issue is fixed, go back to the original repo -> npm install cypress-graphql-mock
|
||||
// import 'cypress-graphql-mock';
|
||||
import mocks from '../fixtures/mocks';
|
||||
import { addMocksToSchema } from '@graphql-tools/mock';
|
||||
import { graphql, GraphQLError } from 'graphql';
|
||||
|
||||
Cypress.Commands.add('apolloLogin', (username, password) => {
|
||||
const payload = {
|
||||
operationName: 'BetaLogin',
|
||||
variables: {
|
||||
input: {
|
||||
usernameInput: username,
|
||||
passwordInput: password,
|
||||
},
|
||||
},
|
||||
query:
|
||||
'mutation BetaLogin($input: BetaLoginInput!) {\n betaLogin(input: $input) {\n success\n __typename\n }\n}\n',
|
||||
};
|
||||
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: '/api/graphql-public/',
|
||||
body: payload,
|
||||
});
|
||||
});
|
||||
|
||||
// todo: replace with apollo call
|
||||
Cypress.Commands.add('login', (username, password, visitLogin = false) => {
|
||||
if (visitLogin) {
|
||||
cy.visit('/beta-login');
|
||||
}
|
||||
|
||||
if (username !== '') {
|
||||
cy.get('[data-cy=email-input]').type(username);
|
||||
}
|
||||
|
||||
if (password !== '') {
|
||||
cy.get('[data-cy=password-input]').type(password);
|
||||
}
|
||||
cy.get('[data-cy=login-button]').click();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('getByDataCy', (selector: string) => {
|
||||
return cy.get(`[data-cy=${selector}]`);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('selectClass', (schoolClass) => {
|
||||
cy.getByDataCy('user-widget-avatar').click();
|
||||
cy.getByDataCy('class-selection').click();
|
||||
cy.getByDataCy('class-selection-entry').contains(schoolClass).click();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('fakeLogin', () => {
|
||||
cy.log('Logging in (fake)');
|
||||
cy.setCookie('loginStatus', 'true');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('isSubmissionReadOnly', (myText) => {
|
||||
cy.get('.submission-form__textarea--readonly').as('textarea');
|
||||
|
||||
cy.get('@textarea').invoke('val').should('contain', myText);
|
||||
cy.get('@textarea').should('have.attr', 'readonly');
|
||||
cy.getByDataCy('submission-form-submit').should('not.exist');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('openSidebar', () => {
|
||||
cy.getByDataCy('user-widget-avatar').click();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('setup', () => {
|
||||
cy.fakeLogin('nino.teacher', 'test');
|
||||
cy.viewport('macbook-15');
|
||||
cy.mockGraphql();
|
||||
});
|
||||
|
||||
const typenameResolver = {
|
||||
__resolveType(obj, context, info) {
|
||||
return obj.__typename;
|
||||
},
|
||||
};
|
||||
|
||||
Cypress.Commands.add('mockGraphql', (options?: any) => {
|
||||
const getByDataCy = (selector: string) => {
|
||||
return cy.get(`[data-cy=${selector}]`);
|
||||
};
|
||||
|
||||
const mockGraphql = (options?: any) => {
|
||||
cy.task('getSchema').then((schemaString: string) => {
|
||||
const resolverMap = {
|
||||
DeleteSnapshotResult: typenameResolver,
|
||||
|
|
@ -214,12 +107,129 @@ Cypress.Commands.add('mockGraphql', (options?: any) => {
|
|||
},
|
||||
}).as('mockGraphqlOps');
|
||||
});
|
||||
};
|
||||
|
||||
const mockGraphqlOps = (options) => {
|
||||
cy.get('@mockGraphqlOps').invoke('setOperations' as any, options);
|
||||
};
|
||||
|
||||
declare global {
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
/**
|
||||
* Login via API call to the GraphQL endpoint, without calling the frontend. Faster than the other login.
|
||||
* @param username
|
||||
* @param password
|
||||
* @example
|
||||
* cy.apolloLogin('ross.geller', 'test')
|
||||
*/
|
||||
apolloLogin(username: string, password: string): Chainable<any>;
|
||||
|
||||
/**
|
||||
* Selects an element based on the `data-cy=xxx` attribute
|
||||
* @param selector - The value of the data-cy attribute to select
|
||||
* @example
|
||||
* cy.getByDataCy('my-new-button')
|
||||
*/
|
||||
// getByDataCy(selector: string): Chainable<Element>;
|
||||
getByDataCy: typeof getByDataCy;
|
||||
|
||||
selectClass(schoolClass: string): void;
|
||||
|
||||
login(username: string, password: string, visitLogin?: boolean): void;
|
||||
|
||||
fakeLogin(username: string, password: string): void;
|
||||
|
||||
isSubmissionReadOnly(myText: string): void;
|
||||
|
||||
openSidebar(): void;
|
||||
|
||||
setup(): void;
|
||||
|
||||
mockGraphql: typeof mockGraphql;
|
||||
mockGraphqlOps: typeof mockGraphqlOps;
|
||||
}
|
||||
}
|
||||
}
|
||||
// installed a fork of the original package, because of this issue:
|
||||
// https://github.com/tgriesser/cypress-graphql-mock/issues/23
|
||||
// todo: once above issue is fixed, go back to the original repo -> npm install cypress-graphql-mock
|
||||
// import 'cypress-graphql-mock';
|
||||
import mocks from '../fixtures/mocks';
|
||||
import { addMocksToSchema } from '@graphql-tools/mock';
|
||||
import { graphql, GraphQLError } from 'graphql';
|
||||
|
||||
Cypress.Commands.add('apolloLogin', (username, password) => {
|
||||
const payload = {
|
||||
operationName: 'BetaLogin',
|
||||
variables: {
|
||||
input: {
|
||||
usernameInput: username,
|
||||
passwordInput: password,
|
||||
},
|
||||
},
|
||||
query:
|
||||
'mutation BetaLogin($input: BetaLoginInput!) {\n betaLogin(input: $input) {\n success\n __typename\n }\n}\n',
|
||||
};
|
||||
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: '/api/graphql-public/',
|
||||
body: payload,
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('mockGraphqlOps', (options) => {
|
||||
cy.get('@mockGraphqlOps').invoke('setOperations' as any, options);
|
||||
// todo: replace with apollo call
|
||||
Cypress.Commands.add('login', (username, password, visitLogin = false) => {
|
||||
if (visitLogin) {
|
||||
cy.visit('/beta-login');
|
||||
}
|
||||
|
||||
if (username !== '') {
|
||||
cy.get('[data-cy=email-input]').type(username);
|
||||
}
|
||||
|
||||
if (password !== '') {
|
||||
cy.get('[data-cy=password-input]').type(password);
|
||||
}
|
||||
cy.get('[data-cy=login-button]').click();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('getByDataCy', getByDataCy);
|
||||
|
||||
Cypress.Commands.add('selectClass', (schoolClass) => {
|
||||
cy.getByDataCy('user-widget-avatar').click();
|
||||
cy.getByDataCy('class-selection').click();
|
||||
cy.getByDataCy('class-selection-entry').contains(schoolClass).click();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('fakeLogin', () => {
|
||||
cy.log('Logging in (fake)');
|
||||
cy.setCookie('loginStatus', 'true');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('isSubmissionReadOnly', (myText) => {
|
||||
cy.get('.submission-form__textarea--readonly').as('textarea');
|
||||
|
||||
cy.get('@textarea').invoke('val').should('contain', myText);
|
||||
cy.get('@textarea').should('have.attr', 'readonly');
|
||||
cy.getByDataCy('submission-form-submit').should('not.exist');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('openSidebar', () => {
|
||||
cy.getByDataCy('user-widget-avatar').click();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('setup', () => {
|
||||
cy.fakeLogin('nino.teacher', 'test');
|
||||
cy.viewport('macbook-15');
|
||||
cy.mockGraphql();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('mockGraphql', mockGraphql);
|
||||
|
||||
Cypress.Commands.add('mockGraphqlOps', mockGraphqlOps);
|
||||
|
||||
const getRootValue = (allOperations: any, operationName: string, variables: any) => {
|
||||
const operation = allOperations[operationName];
|
||||
if (typeof operation === 'function') {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Components App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div data-cy-root></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
// ***********************************************************
|
||||
// This example support/component.ts is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands';
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
|
||||
import { mount } from 'cypress/vue';
|
||||
import '@/main.js';
|
||||
|
||||
// Augment the Cypress namespace to include type definitions for
|
||||
// your custom command.
|
||||
// Alternatively, can be defined in cypress/support/component.d.ts
|
||||
// with a <reference path="./component" /> at the top of your spec.
|
||||
declare global {
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
mount: typeof mount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cypress.Commands.add('mount', mount);
|
||||
|
||||
// Example use:
|
||||
// cy.mount(MyComponent)
|
||||
|
|
@ -87,7 +87,7 @@
|
|||
"vee-validate": "^4.5.10",
|
||||
"vue": "3.2.30",
|
||||
"vue-loader": "^16.8.3",
|
||||
"vue-matomo": "^4.1.0",
|
||||
"vue-matomo": "^4.2.0",
|
||||
"vue-router": "^4.0.14",
|
||||
"vue-scrollto": "^2.20.0",
|
||||
"vue-style-loader": "^3.0.1",
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@
|
|||
"vee-validate": "^4.5.10",
|
||||
"vue": "3.2.30",
|
||||
"vue-loader": "^16.8.3",
|
||||
"vue-matomo": "^4.1.0",
|
||||
"vue-matomo": "^4.2.0",
|
||||
"vue-router": "^4.0.14",
|
||||
"vue-scrollto": "^2.20.0",
|
||||
"vue-style-loader": "^3.0.1",
|
||||
|
|
|
|||
|
|
@ -110,7 +110,6 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~styles/main.scss';
|
||||
@import '~styles/helpers';
|
||||
|
||||
body {
|
||||
|
|
|
|||
|
|
@ -1,162 +1,15 @@
|
|||
import 'core-js/stable';
|
||||
import { createApp, configureCompat, h, provide } from 'vue';
|
||||
import VueVimeoPlayer from 'vue-vimeo-player';
|
||||
import apolloClientFactory from './graphql/client';
|
||||
import App from './App.vue';
|
||||
import { postLoginRedirectUrlKey, router } from './router';
|
||||
import { store } from '@/store';
|
||||
import VueScrollTo from 'vue-scrollto';
|
||||
import autoGrow from '@/directives/auto-grow';
|
||||
import clickOutside from '@/directives/click-outside';
|
||||
import ME_QUERY from '@/graphql/gql/queries/meQuery.gql';
|
||||
import VueModal from '@/plugins/modal';
|
||||
import VueRemoveEdges from '@/plugins/edges';
|
||||
import VueMatomo from 'vue-matomo';
|
||||
import { createApolloProvider } from '@vue/apollo-option';
|
||||
import { joiningClass, loginRequired, unauthorizedAccess } from '@/router/guards';
|
||||
import flavorPlugin from '@/plugins/flavor';
|
||||
import { DefaultApolloClient } from '@vue/apollo-composable';
|
||||
import setupApp from './setup/setupApp';
|
||||
import registerPlugins from './setup/plugins';
|
||||
import setupRouteGuards from './setup/router';
|
||||
import registerDirectives from './setup/directives';
|
||||
import '@/styles/main.scss';
|
||||
|
||||
const publicApolloClient = apolloClientFactory('/api/graphql-public/', null);
|
||||
const privateApolloClient = apolloClientFactory('/api/graphql/', networkErrorCallback);
|
||||
const app = setupApp();
|
||||
|
||||
const apolloProvider = createApolloProvider({
|
||||
clients: {
|
||||
publicClient: publicApolloClient,
|
||||
},
|
||||
defaultClient: privateApolloClient,
|
||||
});
|
||||
registerPlugins(app);
|
||||
setupRouteGuards();
|
||||
|
||||
configureCompat({
|
||||
MODE: 3,
|
||||
});
|
||||
|
||||
const app = createApp({
|
||||
setup() {
|
||||
provide(DefaultApolloClient, privateApolloClient);
|
||||
},
|
||||
render: () => h(App),
|
||||
});
|
||||
|
||||
app.use(store);
|
||||
|
||||
app.use(VueModal);
|
||||
app.use(VueRemoveEdges);
|
||||
app.use(VueVimeoPlayer);
|
||||
app.use(apolloProvider);
|
||||
app.use(router);
|
||||
|
||||
// VueScrollTo.setDefaults({
|
||||
// duration: 500,
|
||||
// easing: 'ease-out',
|
||||
// offset: -50,
|
||||
// });
|
||||
|
||||
app.directive('scroll-to', VueScrollTo);
|
||||
|
||||
app.use(flavorPlugin);
|
||||
|
||||
if (process.env.MATOMO_HOST) {
|
||||
app.use(VueMatomo, {
|
||||
host: process.env.MATOMO_HOST,
|
||||
siteId: process.env.MATOMO_SITE_ID,
|
||||
router: router,
|
||||
});
|
||||
}
|
||||
|
||||
app.directive('click-outside', clickOutside);
|
||||
app.directive('auto-grow', autoGrow);
|
||||
|
||||
/* guards */
|
||||
|
||||
function redirectUsersWithoutValidLicense() {
|
||||
return privateApolloClient
|
||||
.query({
|
||||
query: ME_QUERY,
|
||||
})
|
||||
.then(({ data }) => data.me.expiryDate == null);
|
||||
}
|
||||
|
||||
function redirectStudentsWithoutClass() {
|
||||
return privateApolloClient
|
||||
.query({
|
||||
query: ME_QUERY,
|
||||
})
|
||||
.then(({ data }) => data.me.schoolClasses.length === 0 && !data.me.isTeacher);
|
||||
}
|
||||
|
||||
function redirectUsersToOnboarding() {
|
||||
return privateApolloClient
|
||||
.query({
|
||||
query: ME_QUERY,
|
||||
})
|
||||
.then(({ data }) => !data.me.onboardingVisited);
|
||||
}
|
||||
|
||||
function networkErrorCallback(statusCode) {
|
||||
if (statusCode === 402) {
|
||||
router.push({ name: 'licenseActivation' });
|
||||
}
|
||||
}
|
||||
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
// todo: make logger work outside vue app
|
||||
// const logger = inject('vuejs3-logger');
|
||||
// logger.$log.debug('navigation guard called', to, from);
|
||||
if (to.path === '/logout') {
|
||||
await publicApolloClient.resetStore();
|
||||
if (process.env.LOGOUT_REDIRECT_URL) {
|
||||
location.replace(`https://sso.hep-verlag.ch/logout?return_to=${process.env.LOGOUT_REDIRECT_URL}`);
|
||||
next(false);
|
||||
return;
|
||||
} else {
|
||||
next({ name: 'hello' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (unauthorizedAccess(to)) {
|
||||
//logger.$log.debug('unauthorized', to);
|
||||
const postLoginRedirectionUrl = to.path;
|
||||
const redirectUrl = `/hello/`;
|
||||
|
||||
if (window.localStorage) {
|
||||
localStorage.setItem(postLoginRedirectUrlKey, postLoginRedirectionUrl);
|
||||
}
|
||||
|
||||
// logger.$log.debug('redirecting to hello', to);
|
||||
next(redirectUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
if (to.name && to.name !== 'licenseActivation' && loginRequired(to) && (await redirectUsersWithoutValidLicense())) {
|
||||
// logger.$log.debug('redirecting to licenseActivation', to, null);
|
||||
console.log('redirecting to licenseActivation', to, null);
|
||||
next({ name: 'licenseActivation' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!joiningClass(to) && loginRequired(to) && (await redirectStudentsWithoutClass())) {
|
||||
//logger.$log.debug('redirecting to join-class', to);
|
||||
//logger.$log.debug('await redirectStudentsWithoutClass()', await redirectStudentsWithoutClass());
|
||||
next({ name: 'join-class' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
to.name &&
|
||||
to.name.indexOf('onboarding') === -1 &&
|
||||
!joiningClass(to) &&
|
||||
loginRequired(to) &&
|
||||
(await redirectUsersToOnboarding())
|
||||
) {
|
||||
//logger.$log.debug('redirecting to onboarding-start', to);
|
||||
next({ name: 'onboarding-start' });
|
||||
return;
|
||||
}
|
||||
|
||||
//logger.$log.debug('End of Guard reached', to);
|
||||
next();
|
||||
});
|
||||
registerDirectives(app);
|
||||
|
||||
app.mount('#app');
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
import apolloClientFactory from '@/graphql/client';
|
||||
|
||||
import { useRouter } from 'vue-router';
|
||||
const router = useRouter();
|
||||
|
||||
function networkErrorCallback(statusCode: number) {
|
||||
if (statusCode === 402) {
|
||||
router.push({ name: 'licenseActivation' });
|
||||
}
|
||||
}
|
||||
|
||||
const createApolloClients = () => {
|
||||
return {
|
||||
publicApolloClient: apolloClientFactory('/api/graphql-public/', null),
|
||||
privateApolloClient: apolloClientFactory('/api/graphql/', networkErrorCallback),
|
||||
};
|
||||
};
|
||||
|
||||
const apolloClients = createApolloClients();
|
||||
|
||||
export default apolloClients;
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import autoGrow from '@/directives/auto-grow';
|
||||
import clickOutside from '@/directives/click-outside';
|
||||
const registerDirectives = (app: any) => {
|
||||
app.directive('click-outside', clickOutside);
|
||||
app.directive('auto-grow', autoGrow);
|
||||
};
|
||||
|
||||
export default registerDirectives;
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import { router } from '@/router';
|
||||
import VueVimeoPlayer from 'vue-vimeo-player';
|
||||
import { store } from '@/store';
|
||||
import VueModal from '@/plugins/modal';
|
||||
import VueRemoveEdges from '@/plugins/edges';
|
||||
import { createApolloProvider } from '@vue/apollo-option';
|
||||
import apolloClients from './apollo';
|
||||
import flavorPlugin from '@/plugins/flavor';
|
||||
import VueMatomo from 'vue-matomo';
|
||||
|
||||
const { publicApolloClient, privateApolloClient } = apolloClients;
|
||||
|
||||
const apolloProvider = createApolloProvider({
|
||||
clients: {
|
||||
publicClient: publicApolloClient,
|
||||
},
|
||||
defaultClient: privateApolloClient,
|
||||
});
|
||||
const registerPlugins = (app: any) => {
|
||||
app.use(store);
|
||||
app.use(VueModal);
|
||||
app.use(VueRemoveEdges);
|
||||
app.use(VueVimeoPlayer);
|
||||
app.use(apolloProvider);
|
||||
app.use(router);
|
||||
app.use(flavorPlugin);
|
||||
if (process.env.MATOMO_HOST) {
|
||||
app.use(VueMatomo, {
|
||||
host: process.env.MATOMO_HOST,
|
||||
siteId: process.env.MATOMO_SITE_ID,
|
||||
router: router,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default registerPlugins;
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
import apolloClients from './apollo';
|
||||
const { publicApolloClient, privateApolloClient } = apolloClients;
|
||||
import ME_QUERY from '@/graphql/gql/queries/meQuery.gql';
|
||||
import { joiningClass, loginRequired, unauthorizedAccess } from '@/router/guards';
|
||||
import { postLoginRedirectUrlKey, router } from '@/router';
|
||||
|
||||
async function redirectUsersWithoutValidLicense() {
|
||||
const { data } = await privateApolloClient.query({
|
||||
query: ME_QUERY,
|
||||
});
|
||||
return data.me.expiryDate == null;
|
||||
}
|
||||
|
||||
async function redirectStudentsWithoutClass() {
|
||||
const { data } = await privateApolloClient.query({
|
||||
query: ME_QUERY,
|
||||
});
|
||||
return data.me.schoolClasses.length === 0 && !data.me.isTeacher;
|
||||
}
|
||||
|
||||
async function redirectUsersToOnboarding() {
|
||||
const { data } = await privateApolloClient.query({
|
||||
query: ME_QUERY,
|
||||
});
|
||||
return !data.me.onboardingVisited;
|
||||
}
|
||||
|
||||
const setupRouteGuards = () => {
|
||||
router.beforeEach(async (to, _, next) => {
|
||||
// todo: make logger work outside vue app
|
||||
// const logger = inject('vuejs3-logger');
|
||||
// logger.$log.debug('navigation guard called', to, from);
|
||||
if (to.path === '/logout') {
|
||||
await publicApolloClient.resetStore();
|
||||
if (process.env.LOGOUT_REDIRECT_URL) {
|
||||
location.replace(`https://sso.hep-verlag.ch/logout?return_to=${process.env.LOGOUT_REDIRECT_URL}`);
|
||||
next(false);
|
||||
return;
|
||||
} else {
|
||||
next({ name: 'hello' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (unauthorizedAccess(to)) {
|
||||
//logger.$log.debug('unauthorized', to);
|
||||
const postLoginRedirectionUrl = to.path;
|
||||
const redirectUrl = `/hello/`;
|
||||
|
||||
if (window.localStorage) {
|
||||
localStorage.setItem(postLoginRedirectUrlKey, postLoginRedirectionUrl);
|
||||
}
|
||||
|
||||
// logger.$log.debug('redirecting to hello', to);
|
||||
next(redirectUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
if (to.name && to.name !== 'licenseActivation' && loginRequired(to) && (await redirectUsersWithoutValidLicense())) {
|
||||
// logger.$log.debug('redirecting to licenseActivation', to, null);
|
||||
console.log('redirecting to licenseActivation', to, null);
|
||||
next({ name: 'licenseActivation' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!joiningClass(to) && loginRequired(to) && (await redirectStudentsWithoutClass())) {
|
||||
//logger.$log.debug('redirecting to join-class', to);
|
||||
//logger.$log.debug('await redirectStudentsWithoutClass()', await redirectStudentsWithoutClass());
|
||||
next({ name: 'join-class' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
to.name &&
|
||||
(to.name as string).indexOf('onboarding') === -1 &&
|
||||
!joiningClass(to) &&
|
||||
loginRequired(to) &&
|
||||
(await redirectUsersToOnboarding())
|
||||
) {
|
||||
//logger.$log.debug('redirecting to onboarding-start', to);
|
||||
next({ name: 'onboarding-start' });
|
||||
return;
|
||||
}
|
||||
|
||||
//logger.$log.debug('End of Guard reached', to);
|
||||
next();
|
||||
});
|
||||
};
|
||||
|
||||
export default setupRouteGuards;
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { createApp, h, provide } from 'vue';
|
||||
import App from '@/App.vue';
|
||||
|
||||
import apolloClients from './apollo';
|
||||
import { DefaultApolloClient } from '@vue/apollo-composable';
|
||||
const { privateApolloClient } = apolloClients;
|
||||
|
||||
const setupApp = () => {
|
||||
const app = createApp({
|
||||
setup() {
|
||||
provide(DefaultApolloClient, privateApolloClient);
|
||||
},
|
||||
render: () => h(App),
|
||||
});
|
||||
return app;
|
||||
};
|
||||
|
||||
export default setupApp;
|
||||
|
|
@ -10,3 +10,13 @@ declare module '*.vue' {
|
|||
const component: DefineComponent<{}, {}, any>;
|
||||
export default component;
|
||||
}
|
||||
|
||||
// ugly hack to make types for those two packages
|
||||
declare module 'vue-vimeo-player' {
|
||||
const plugin: any;
|
||||
export default plugin;
|
||||
}
|
||||
declare module 'vue-matomo' {
|
||||
const plugin: any;
|
||||
export default plugin;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue