0
0
Fork 0

Merge branch 'salix-front-js' of https://gitea.verdnatura.es/verdnatura/salix-front into router-navigation

This commit is contained in:
Joan Sanchez 2022-03-28 09:05:56 +02:00
commit 6141f9a136
63 changed files with 2114 additions and 2644 deletions

View File

@ -1,23 +1,19 @@
{
"plugins": [
"@babel/plugin-syntax-dynamic-import"
],
"env": {
"test": {
"plugins": [
"dynamic-import-node"
],
"presets": [
[
"@babel/preset-env",
{
"modules": "commonjs",
"targets": {
"node": "current"
}
}
]
]
}
"plugins": ["@babel/plugin-syntax-dynamic-import"],
"env": {
"test": {
"plugins": ["dynamic-import-node"],
"presets": [
[
"@babel/preset-env",
{
"modules": "commonjs",
"targets": {
"node": "current"
}
}
]
]
}
}
}
}

View File

@ -3,7 +3,7 @@ root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
indent_size = 4
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

View File

@ -6,4 +6,3 @@
/node_modules
.eslintrc.js
babel.config.js
/src-ssr

View File

@ -1,21 +1,11 @@
const { resolve } = require('path');
module.exports = {
// https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy
// This option interrupts the configuration hierarchy at this file
// Remove this if you have an higher level ESLint config file (it usually happens into a monorepos)
root: true,
// https://eslint.vuejs.org/user-guide/#how-to-use-a-custom-parser
// Must use parserOptions instead of "parser" to allow vue-eslint-parser to keep working
// `parser: 'vue-eslint-parser'` is already included with any 'plugin:vue/**' config and should be omitted
parserOptions: {
// https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/parser#configuration
// https://github.com/TypeStrong/fork-ts-checker-webpack-plugin#eslint
// Needed to make the parser take into account 'vue' files
extraFileExtensions: ['.vue'],
parser: '@typescript-eslint/parser',
project: resolve(__dirname, './tsconfig.json'),
tsconfigRootDir: __dirname,
parser: '@babel/eslint-parser',
ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
sourceType: 'module', // Allows for the use of imports
},
@ -27,18 +17,12 @@ module.exports = {
// Rules order is important, please avoid shuffling them
extends: [
// Base ESLint recommended rules
// 'eslint:recommended',
// https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#usage
// ESLint typescript rules
'plugin:@typescript-eslint/recommended',
// consider disabling this class of rules if linting takes too long
'plugin:@typescript-eslint/recommended-requiring-type-checking',
'eslint:recommended',
// Uncomment any of the lines below to choose desired strictness,
// but leave only one uncommented!
// See https://eslint.vuejs.org/rules/#available-rules
// 'plugin:vue/vue3-essential', // Priority A: Essential (Error Prevention)
//'plugin:vue/vue3-essential', // Priority A: Essential (Error Prevention)
'plugin:vue/vue3-strongly-recommended', // Priority B: Strongly Recommended (Improving Readability)
// 'plugin:vue/vue3-recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead)
@ -48,9 +32,6 @@ module.exports = {
],
plugins: [
// required to apply rules which need type information
'@typescript-eslint',
// https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-files
// required to lint *.vue files
'vue',
@ -71,25 +52,19 @@ module.exports = {
process: 'readonly',
Capacitor: 'readonly',
chrome: 'readonly',
defineProps: 'readonly', // Vue SFC setup compiler macro
defineEmits: 'readonly', // Vue SFC setup compiler macro
defineExpose: 'readonly', // Vue SFC setup compiler macro
},
// add your custom rules here
rules: {
'prefer-promise-reject-errors': 'off',
// TypeScript
quotes: ['warn', 'single', { avoidEscape: true }],
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/unbound-method': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-floating-promises': 'off',
// allow debugger during development only
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
},
overrides: [
{
files: ['**/*.spec.{js,ts}'],

1
.gitignore vendored
View File

@ -26,7 +26,6 @@ yarn-debug.log*
yarn-error.log*
# Editor directories and files
.vscode
.idea
*.suo
*.ntvs*

View File

@ -1,3 +1,4 @@
/* eslint-disable */
// https://github.com/michael-ciniawsky/postcss-load-config
module.exports = {

View File

@ -1,7 +0,0 @@
{
"singleQuote": true,
"printWidth": 120,
"tabWidth": 4,
"semi": true,
"endOfLine": "auto"
}

7
.prettierrc.js Normal file
View File

@ -0,0 +1,7 @@
module.exports = {
singleQuote: true,
printWidth: 120,
tabWidth: 4,
semi: true,
endOfLine: 'auto',
};

15
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,15 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"editorconfig.editorconfig",
"johnsoncodehk.volar",
"wayou.vscode-todo-highlight"
],
"unwantedRecommendations": [
"octref.vetur",
"hookyqr.beautify",
"dbaeumer.jshint",
"ms-vscode.vscode-typescript-tslint-plugin"
]
}

13
.vscode/settings.json vendored
View File

@ -2,6 +2,17 @@
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": true,
"editor.formatOnSave": true,
"editor.defaultFormatter": "johnsoncodehk.volar",
"editor.codeActionsOnSave": ["source.fixAll.eslint"],
"eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"]
"eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"],
"jest.jestCommandLine": "jest",
"json.schemas": [
{
"fileMatch": ["cypress.json"],
"url": "https://on.cypress.io/cypress.schema.json"
}
],
"[javascript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
}
}

View File

@ -5,6 +5,8 @@ Salix front-end
## Install the dependencies
```bash
yarn
# or
npm install
```
@ -17,12 +19,16 @@ quasar dev
### Lint the files
```bash
yarn lint
# or
npm run lint
```
### Format the files
```bash
yarn format
# or
npm run format
```
@ -34,4 +40,4 @@ quasar build
### Customize the configuration
See [Configuring quasar.conf.js](https://quasar.dev/quasar-cli/quasar-conf-js).
See [Configuring quasar.config.js](https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js).

View File

@ -1,6 +1,5 @@
const esModules = ['quasar', 'quasar/lang', 'lodash-es'].join('|');
/* eslint-env node */
module.exports = {
globals: {
__DEV__: true,
@ -9,12 +8,9 @@ module.exports = {
'vue-jest': {
pug: { doctype: 'html' },
},
// Remove if using `const enums`
// See https://huafu.github.io/ts-jest/user/config/isolatedModules#example
'ts-jest': {
isolatedModules: true,
},
},
// Jest assumes we are testing in node environment, specify jsdom environment instead
testEnvironment: 'jsdom',
// noStackTrace: true,
// bail: true,
// cache: false,
@ -22,13 +18,8 @@ module.exports = {
// watch: true,
collectCoverage: false,
coverageDirectory: '<rootDir>/test/jest/coverage',
collectCoverageFrom: [
'<rootDir>/src/**/*.vue',
'<rootDir>/src/**/*.js',
'<rootDir>/src/**/*.ts',
'<rootDir>/src/**/*.jsx',
'<rootDir>/src/**/*.tsx',
],
collectCoverageFrom: ['<rootDir>/src/**/*.vue', '<rootDir>/src/**/*.js', '<rootDir>/src/**/*.jsx'],
// Needed in JS codebases too because of feature flags
coveragePathIgnorePatterns: ['/node_modules/', '.d.ts$'],
coverageThreshold: {
global: {
@ -38,27 +29,14 @@ module.exports = {
// statements: 50
},
},
testMatch: [
// Matches tests in any subfolder of 'src' or into 'test/jest/__tests__'
// Matches all files with extension 'js', 'jsx', 'ts' and 'tsx'
'<rootDir>/test/jest/__tests__/**/*.(spec|test).+(ts|js)?(x)',
//'<rootDir>/src/**/*.jest.(spec|test).+(ts|js)?(x)',
],
// Extension-less imports of components are resolved to .ts files by TS,
// grating correct type-checking in test files.
// Being 'vue' the first moduleFileExtension option, the very same imports
// will be resolved to .vue files by Jest, if both .vue and .ts files are
// in the same folder.
// This guarantee a great dev experience both for testing and type-checking.
// See https://github.com/vuejs/vue-jest/issues/188#issuecomment-620750728
moduleFileExtensions: ['vue', 'js', 'jsx', 'json', 'ts', 'tsx'],
testMatch: ['<rootDir>/src/**/__tests__/*.(spec|test).+(ts|js)?(x)'],
moduleFileExtensions: ['vue', 'js', 'jsx', 'json'],
moduleNameMapper: {
'^quasar$': 'quasar/dist/quasar.esm.prod.js',
'^~/(.*)$': '<rootDir>/$1',
'^src/(.*)$': '<rootDir>/src/$1',
'^app/(.*)$': '<rootDir>/$1',
'^components/(.*)$': '<rootDir>/src/components/$1',
'^composables/(.*)$': '<rootDir>/src/composables/$1',
'^layouts/(.*)$': '<rootDir>/src/layouts/$1',
'^pages/(.*)$': '<rootDir>/src/pages/$1',
'^assets/(.*)$': '<rootDir>/src/assets/$1',
@ -66,16 +44,13 @@ module.exports = {
'.*css$': '@quasar/quasar-app-extension-testing-unit-jest/stub.css',
},
transform: {
// See https://jestjs.io/docs/en/configuration.html#transformignorepatterns-array-string
[`^(${esModules}).+\\.js$`]: 'babel-jest',
'^.+\\.(ts|js|html)$': 'ts-jest',
// vue-jest uses find-babel-file, which searches by this order:
// (async) .babelrc, .babelrc.js, package.json, babel.config.js
// (sync) .babelrc, .babelrc.js, babel.config.js, package.json
// https://github.com/tleunen/find-babel-config/issues/33
'.*\\.vue$': 'vue-jest',
'.*\\.js$': 'babel-jest',
'.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',
// use these if NPM is being flaky, care as hosting could interfere with these
// '.*\\.vue$': '@quasar/quasar-app-extension-testing-unit-jest/node_modules/vue-jest',
// '.*\\.js$': '@quasar/quasar-app-extension-testing-unit-jest/node_modules/babel-jest'
},
transformIgnorePatterns: [`node_modules/(?!(${esModules}))`],
snapshotSerializers: ['<rootDir>/node_modules/jest-serializer-vue'],
snapshotSerializers: ['jest-serializer-vue'],
};

17
jsconfig.json Normal file
View File

@ -0,0 +1,17 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"src/*": ["src/*"],
"app/*": ["*"],
"components/*": ["src/components/*"],
"layouts/*": ["src/layouts/*"],
"pages/*": ["src/pages/*"],
"assets/*": ["src/assets/*"],
"boot/*": ["src/boot/*"],
"stores/*": ["src/stores/*"],
"vue$": ["node_modules/vue/dist/vue.runtime.esm-bundler.js"]
}
},
"exclude": ["dist", ".quasar", "node_modules"]
}

3231
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,41 +3,39 @@
"version": "0.0.1",
"description": "Salix front-end",
"productName": "Salix",
"author": "Salix",
"author": "Verdnatura",
"private": true,
"scripts": {
"lint": "eslint --ext .js,.ts,.vue ./",
"format": "prettier --write \"**/*.{js,ts,vue,scss,html,md,json}\" --ignore-path .gitignore",
"lint": "eslint --ext .js,.vue ./",
"format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore",
"test": "echo \"See package.json => scripts for available tests.\" && exit 0",
"test:unit:coverage": "jest --coverage",
"test:unit": "jest --watchAll",
"test:e2e": "cross-env E2E_TEST=true start-test \"quasar dev\" http-get://localhost:8080 \"cypress open --config-file cypress.json\"",
"test:e2e:ci": "cross-env E2E_TEST=true start-test \"quasar dev\" http-get://localhost:8080 \"cypress run\"",
"test:unit:ci": "jest --ci",
"test:unit:coverage": "jest --coverage",
"serve:test:coverage": "quasar serve test/jest/coverage/lcov-report/ --port 8788",
"concurrently:dev:jest": "concurrently \"quasar dev\" \"jest --watch\""
"concurrently:dev:jest": "concurrently \"quasar dev\" \"jest --watch\"",
"test:e2e": "cross-env E2E_TEST=true start-test \"quasar dev\" http-get://localhost:8080 \"cypress open\"",
"test:e2e:ci": "cross-env E2E_TEST=true start-test \"quasar dev\" http-get://localhost:8080 \"cypress run\""
},
"dependencies": {
"@quasar/extras": "^1.0.0",
"axios": "^0.21.1",
"core-js": "^3.6.5",
"quasar": "^2.0.0",
"quasar": "^2.6.0",
"vue": "^3.0.0",
"vue-i18n": "^9.0.0",
"vue-router": "^4.0.0"
},
"devDependencies": {
"@babel/eslint-parser": "^7.13.14",
"@intlify/vue-i18n-loader": "^4.1.0",
"@quasar/app": "^3.0.0",
"@quasar/app-webpack": "^3.0.0",
"@quasar/quasar-app-extension-testing-e2e-cypress": "^4.0.1",
"@quasar/quasar-app-extension-testing-unit-jest": "^3.0.0-alpha.8",
"@types/node": "^12.20.21",
"@typescript-eslint/eslint-plugin": "^5.10.0",
"@typescript-eslint/parser": "^5.10.0",
"eslint": "^7.14.0",
"@quasar/quasar-app-extension-testing-unit-jest": "^3.0.0-alpha.9",
"eslint": "^8.10.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-jest": "^25.2.2",
"eslint-plugin-vue": "^7.0.0",
"eslint-plugin-vue": "^8.5.0",
"eslint-webpack-plugin": "^3.1.1",
"prettier": "^2.5.1",
"eslint-plugin-cypress": "^2.11.3"
},

View File

@ -1,43 +1,38 @@
/* eslint-env node */
/*
* This file runs in a Node context (it's NOT transpiled by Babel), so use only
* the ES6 features that are supported by your Node version. https://node.green/
*/
// Configuration for your app
// https://quasar.dev/quasar-cli/quasar-conf-js
// https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js
const ESLintPlugin = require('eslint-webpack-plugin');
/* eslint-env node */
/* eslint-disable @typescript-eslint/no-var-requires */
const { configure } = require('quasar/wrappers');
module.exports = configure(function (ctx) {
return {
// https://quasar.dev/quasar-cli/supporting-ts
supportTS: {
tsCheckerConfig: {
eslint: {
enabled: true,
files: './src/**/*.{ts,tsx,js,jsx,vue}',
},
},
},
// https://v2.quasar.dev/quasar-cli-webpack/supporting-ts
supportTS: false,
// https://quasar.dev/quasar-cli/prefetch-feature
// https://v2.quasar.dev/quasar-cli-webpack/prefetch-feature
// preFetch: true,
// app boot file (/src/boot)
// --> boot files are part of "main.js"
// https://quasar.dev/quasar-cli/boot-files
// https://v2.quasar.dev/quasar-cli-webpack/boot-files
boot: ['i18n', 'axios'],
// https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-css
// https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-css
css: ['app.scss'],
// https://github.com/quasarframework/quasar/tree/dev/extras
extras: [
// 'ionicons-v4',
// 'mdi-v5',
// 'fontawesome-v5',
// 'fontawesome-v6',
// 'eva-icons',
// 'themify',
// 'line-awesome',
@ -47,7 +42,7 @@ module.exports = configure(function (ctx) {
'material-icons', // optional, you are not bound to it
],
// Full list of options: https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-build
// Full list of options: https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-build
build: {
vueRouterMode: 'hash', // available values: 'hash', 'history'
@ -68,14 +63,15 @@ module.exports = configure(function (ctx) {
// Options below are automatically set depending on the env, set them if you want to override
// extractCSS: false,
// https://quasar.dev/quasar-cli/handling-webpack
// https://v2.quasar.dev/quasar-cli-webpack/handling-webpack
// "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain
chainWebpack(/* chain */) {
//
chainWebpack(chain) {
chain.plugin('eslint-webpack-plugin').use(ESLintPlugin, [{ extensions: ['js', 'vue'] }]);
},
},
// Full list of options: https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-devServer
// Full list of options: https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-devServer
devServer: {
server: {
type: 'http',
@ -92,7 +88,7 @@ module.exports = configure(function (ctx) {
},
},
// https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-framework
// https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-framework
framework: {
config: {},
@ -107,14 +103,14 @@ module.exports = configure(function (ctx) {
// directives: [],
// Quasar plugins
plugins: ['Notify'],
plugins: ['Notify', 'Dialog'],
},
// animations: 'all', // --- includes all animations
// https://quasar.dev/options/animations
animations: [],
// https://quasar.dev/quasar-cli/developing-ssr/configuring-ssr
// https://v2.quasar.dev/quasar-cli-webpack/developing-ssr/configuring-ssr
ssr: {
pwa: false,
@ -127,8 +123,8 @@ module.exports = configure(function (ctx) {
maxAge: 1000 * 60 * 60 * 24 * 30,
// Tell browser when a file from the server should expire from cache (in ms)
chainWebpackWebserver(/* chain */) {
//
chainWebpackWebserver(chain) {
chain.plugin('eslint-webpack-plugin').use(ESLintPlugin, [{ extensions: ['js'] }]);
},
middlewares: [
@ -137,21 +133,22 @@ module.exports = configure(function (ctx) {
],
},
// https://quasar.dev/quasar-cli/developing-pwa/configuring-pwa
// https://v2.quasar.dev/quasar-cli-webpack/developing-pwa/configuring-pwa
pwa: {
workboxPluginMode: 'GenerateSW', // 'GenerateSW' or 'InjectManifest'
workboxOptions: {}, // only for GenerateSW
// for the custom service worker ONLY (/src-pwa/custom-service-worker.[js|ts])
// if using workbox in InjectManifest mode
chainWebpackCustomSW(/* chain */) {
//
chainWebpackCustomSW(chain) {
chain.plugin('eslint-webpack-plugin').use(ESLintPlugin, [{ extensions: ['js'] }]);
},
manifest: {
name: 'Salix',
short_name: 'Salix',
description: 'Salix front-end',
name: `Salix`,
short_name: `Salix`,
description: `Salix front-end`,
display: 'standalone',
orientation: 'portrait',
background_color: '#ffffff',
@ -186,17 +183,17 @@ module.exports = configure(function (ctx) {
},
},
// Full list of options: https://quasar.dev/quasar-cli/developing-cordova-apps/configuring-cordova
// Full list of options: https://v2.quasar.dev/quasar-cli-webpack/developing-cordova-apps/configuring-cordova
cordova: {
// noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing
},
// Full list of options: https://quasar.dev/quasar-cli/developing-capacitor-apps/configuring-capacitor
// Full list of options: https://v2.quasar.dev/quasar-cli-webpack/developing-capacitor-apps/configuring-capacitor
capacitor: {
hideSplashscreen: true,
},
// Full list of options: https://quasar.dev/quasar-cli/developing-electron-apps/configuring-electron
// Full list of options: https://v2.quasar.dev/quasar-cli-webpack/developing-electron-apps/configuring-electron
electron: {
bundler: 'packager', // 'packager' or 'builder'
@ -218,15 +215,13 @@ module.exports = configure(function (ctx) {
},
// "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain
chainWebpack(/* chain */) {
// do something with the Electron main process Webpack cfg
// extendWebpackMain also available besides this chainWebpackMain
chainWebpackMain(chain) {
chain.plugin('eslint-webpack-plugin').use(ESLintPlugin, [{ extensions: ['js'] }]);
},
// "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain
chainWebpackPreload(/* chain */) {
// do something with the Electron main process Webpack cfg
// extendWebpackPreload also available besides this chainWebpackPreload
chainWebpackPreload(chain) {
chain.plugin('eslint-webpack-plugin').use(ESLintPlugin, [{ extensions: ['js'] }]);
},
},
};

View File

@ -1,7 +1,7 @@
{
"@quasar/testing-unit-jest": {
"babel": "babelrc",
"options": ["scripts", "typescript"]
"options": ["scripts"]
},
"@quasar/testing-e2e-cypress": {
"options": ["scripts"]

View File

@ -1,4 +1,4 @@
<script lang="ts" setup>
<script setup>
import { useQuasar } from 'quasar';
const quasar = useQuasar();

View File

@ -1,14 +1,7 @@
import { boot } from 'quasar/wrappers';
import axios, { AxiosInstance } from 'axios';
import axios from 'axios';
import { useSession } from 'src/composables/useSession';
const { getToken } = useSession();
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$axios: AxiosInstance;
}
}
// Be careful when using SSR for cross-request state pollution
// due to creating a Singleton instance here;
@ -17,6 +10,7 @@ declare module '@vue/runtime-core' {
// "export default () => {}" function below (which runs individually
// for each client)
const api = axios.create({ baseURL: 'https://api.example.com' });
const { getToken } = useSession();
axios.interceptors.request.use(
function (context) {

View File

@ -1,6 +1,5 @@
import { boot } from 'quasar/wrappers';
import { createI18n } from 'vue-i18n';
import messages from 'src/i18n';
const i18n = createI18n({
@ -13,4 +12,4 @@ export default boot(({ app }) => {
app.use(i18n);
});
export { i18n };
export { i18n };

View File

@ -1,3 +1,19 @@
<script setup>
import { useState } from 'src/composables/useState';
import { useSession } from 'src/composables/useSession';
import UserPanel from 'src/components/UserPanel.vue';
const session = useSession();
const state = useState();
const user = state.getUser();
const token = session.getToken();
defineEmits(['toggle-drawer']);
</script>
<template>
<q-header class="bg-dark" color="white" elevated bordered>
<q-toolbar class="q-py-sm q-px-md">
@ -41,18 +57,3 @@
</q-toolbar>
</q-header>
</template>
<script lang="ts" setup>
import { useState } from 'src/composables/useState';
import { useSession } from 'src/composables/useSession';
import UserPanel from 'src/components/UserPanel.vue';
const session = useSession();
const state = useState();
const user = state.getUser();
const token = session.getToken();
defineEmits<{
(event: 'toggle-drawer'): void;
}>();
</script>

View File

@ -1,3 +1,51 @@
<script setup>
import { onMounted, computed } from 'vue';
import { Dark, useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import axios from 'axios';
import { useState } from 'src/composables/useState';
import { useSession } from 'src/composables/useSession';
const quasar = useQuasar();
const state = useState();
const session = useSession();
const router = useRouter();
const { t, locale } = useI18n();
const darkMode = computed({
get() {
return Dark.isActive;
},
set(value) {
Dark.set(value);
},
});
const user = state.getUser();
const token = session.getToken();
onMounted(async () => {
try {
const roles = await axios.get('/api/accounts/acl')
state.setUser(roles.user);
} catch (error) {
quasar.notify({
message: t('errors.statusUnauthorized'),
type: 'negative',
});
logout();
}
});
function logout() {
session.destroy();
router.push('/login');
}
</script>
<template>
<q-menu>
<div class="row no-wrap q-pa-md">
@ -34,7 +82,7 @@
<div class="text-subtitle1 q-mt-md">
<strong>{{ user.nickname }}</strong>
</div>
<div class="text-subtitle3 text-grey-7 q-mb-xs">@{{ user.username }}</div>
<div class="text-subtitle3 text-grey-7 q-mb-xs">@{{ user.name }}</div>
<q-btn
color="orange"
@ -49,72 +97,3 @@
</div>
</q-menu>
</template>
<script lang="ts" setup>
import { onMounted, computed } from 'vue';
import { Dark, useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import axios from 'axios';
import { useState } from 'src/composables/useState';
import { useSession } from 'src/composables/useSession';
const quasar = useQuasar();
const state = useState();
const session = useSession();
const router = useRouter();
const { t, locale } = useI18n();
const darkMode = computed({
get(): boolean {
return Dark.isActive;
},
set(value: boolean): void {
Dark.set(value);
},
});
const user = state.getUser();
const token = session.getToken();
interface Role {
name: string
}
interface RoleList {
roleId: number;
role: Role
}
onMounted(() => {
axios
.get('/api/accounts/acl')
.then(({ data }) => {
state.setUser({
id: data.user.id,
username: data.user.name,
nickname: data.user.nickname,
});
if (data.roles) {
const roleList: RoleList[] = data.roles;
const roles: string[] = roleList.map(item => item.role.name);
state.setRoles(roles);
}
})
.catch(() => {
quasar.notify({
message: t('errors.statusUnauthorized'),
type: 'negative',
});
logout();
});
});
function logout(): void {
session.destroy();
router.push('/login');
}
</script>

View File

@ -1,42 +0,0 @@
import { mount } from '@cypress/vue';
import QuasarButton from '../QuasarButton.vue';
describe('QuasarButton', () => {
it('renders a message', () => {
const label = 'Hello there';
mount(QuasarButton, {
props: {
label,
},
});
cy.dataCy('button').should('contain', label);
});
it('renders another message', () => {
const label = 'Will this work?';
mount(QuasarButton, {
props: {
label,
},
});
cy.dataCy('button').should('contain', label);
});
it('should have a `positive` color', () => {
mount(QuasarButton);
cy.dataCy('button').should('have.backgroundColor', 'var(--q-positive)').should('have.color', 'white');
});
it('should emit `test` upon click', () => {
mount(QuasarButton);
cy.dataCy('button')
.click()
.should(() => {
expect(Cypress.vueWrapper.emitted('test')).to.have.length(1);
});
});
});

View File

@ -1,24 +0,0 @@
import { mount } from '@cypress/vue';
import DialogWrapper from 'app/test/cypress/wrappers/DialogWrapper.vue';
import QuasarDialog from '../QuasarDialog.vue';
describe('QuasarDialog', () => {
it('should show a dialog with a message', () => {
const message = 'Hello, I am a dialog';
mount(DialogWrapper, {
props: {
component: QuasarDialog,
componentProps: {
message,
},
},
});
cy.dataCy('dialog').should('exist').should('contain', message);
});
it('should close a dialog when clikcing ok', () => {
// The dialog is still visible from the previous test
cy.dataCy('dialog').should('exist').dataCy('ok-button').click();
cy.dataCy('dialog').should('not.exist');
});
});

View File

@ -1,15 +0,0 @@
import { mount } from '@cypress/vue';
import LayoutContainer from 'app/test/cypress/wrappers/LayoutContainer.vue';
import QuasarDrawer from '../QuasarDrawer.vue';
describe('QuasarDrawer', () => {
it('should show a drawer', () => {
mount(LayoutContainer, {
props: {
component: QuasarDrawer,
},
});
cy.dataCy('drawer').should('exist').dataCy('button').should('not.be.visible');
cy.get('.q-scrollarea .scroll').scrollTo('bottom', { duration: 500 }).dataCy('button').should('be.visible');
});
});

View File

@ -1,22 +0,0 @@
import { mount } from '@cypress/vue';
import LayoutContainer from 'app/test/cypress/wrappers/LayoutContainer.vue';
import QuasarPageSticky from '../QuasarPageSticky.vue';
describe('QuasarPageSticky', () => {
it('should show a sticky at the bottom-right of the page', () => {
mount(LayoutContainer, {
props: {
component: QuasarPageSticky,
title: 'Test',
},
});
cy.dataCy('button')
.should('be.visible')
.should(($el) => {
const rect = $el[0].getBoundingClientRect();
expect(rect.bottom).to.equal(window.innerHeight - 18);
expect(rect.right).to.equal(window.innerWidth - 18);
});
});
});

View File

@ -1,11 +0,0 @@
import { mount } from '@cypress/vue';
import QuasarTooltip from '../QuasarTooltip.vue';
describe('QuasarTooltip', () => {
it('should show a tooltip', () => {
mount(QuasarTooltip);
cy.dataCy('button').trigger('mouseover');
cy.dataCy('tooltip').contains('Here I am!');
});
});

View File

@ -0,0 +1,46 @@
import { describe, expect, it, jest } from '@jest/globals';
import { createWrapper, axios, flushPromises } from 'app/test/jest/jestHelpers';
import UserPanel from '../UserPanel.vue';
const mockPush = jest.fn();
jest.mock('vue-router', () => ({
useRouter: () => ({
push: mockPush,
}),
}));
describe('UserPanel', () => {
describe('onMounted', () => {
it('should define the user into state', async () => {
const userMock = {
user: {
id: 1,
name: 'myName',
nickname: 'myNickName'
}
}
jest.spyOn(axios, 'get').mockResolvedValue(userMock);
const { vm } = createWrapper(UserPanel);
await flushPromises()
const expectedUser = expect.objectContaining(userMock.user)
expect(vm.state.getUser().value).toEqual(expectedUser);
});
it('should logout and notify the expected error', async () => {
jest.spyOn(axios, 'get').mockRejectedValue(new Error('error'));
const { vm } = createWrapper(UserPanel);
jest.spyOn(vm.quasar, 'notify');
await flushPromises()
expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining(
{ 'type': 'negative' }
));
});
});
});

View File

@ -1,8 +0,0 @@
export interface Todo {
id: number;
content: string;
}
export interface Meta {
totalCount: number;
}

View File

@ -1,26 +1,14 @@
import { useState } from './useState';
interface Session {
token: string;
keepLogin: boolean;
}
interface useSession {
getToken: () => string;
setToken: (data: Session) => void;
destroy: () => void;
isLoggedIn: () => boolean;
}
export function useSession(): useSession {
function getToken(): string {
export function useSession() {
function getToken() {
const localToken = localStorage.getItem('token');
const sessionToken = sessionStorage.getItem('token');
return localToken || sessionToken || '';
}
function setToken(data: Session): void {
function setToken(data) {
if (data.keepLogin) {
localStorage.setItem('token', data.token);
} else {
@ -28,7 +16,7 @@ export function useSession(): useSession {
}
}
function destroy(): void {
function destroy() {
localStorage.removeItem('token');
sessionStorage.getItem('token');

View File

@ -0,0 +1,46 @@
import { ref, computed } from 'vue';
const user = ref({
id: 0,
name: '',
nickname: '',
});
const roles = ref([]);
export function useState() {
function getUser() {
return computed(() => {
return {
id: user.value.id,
name: user.value.name,
nickname: user.value.nickname,
};
});
}
function setUser(data) {
user.value = {
id: data.id,
name: data.name,
nickname: data.nickname,
};
}
function getRoles() {
return computed(() => {
return roles.value;
});
}
function setRoles(data) {
roles.value = data;
}
return {
getUser,
setUser,
getRoles,
setRoles,
};
}

View File

@ -1,59 +0,0 @@
import { ref, Ref, computed, ComputedRef } from 'vue';
interface User {
id: number;
username: string;
nickname: string;
}
interface useState {
getUser: () => ComputedRef<User>;
setUser: (data: User) => void;
getRoles: () => ComputedRef<string[]>;
setRoles: (data: string[]) => void;
}
const user: Ref<User> = ref({
id: 0,
username: '',
nickname: '',
});
const roles: Ref<string[]> = ref([]);
export function useState(): useState {
function getUser(): ComputedRef<User> {
return computed(() => {
return {
id: user.value.id,
username: user.value.username,
nickname: user.value.nickname,
};
});
}
function setUser(data: User): void {
user.value = {
id: data.id,
username: data.username,
nickname: data.nickname,
};
}
function getRoles(): ComputedRef<string[]> {
return computed(() => {
return roles.value;
});
}
function setRoles(data: string[]): void {
roles.value = data;
}
return {
getUser,
setUser,
getRoles,
setRoles,
};
}

7
src/env.d.ts vendored
View File

@ -1,7 +0,0 @@
declare namespace NodeJS {
interface ProcessEnv {
NODE_ENV: string;
VUE_ROUTER_MODE: 'hash' | 'history' | 'abstract' | undefined;
VUE_ROUTER_BASE: string | undefined;
}
}

View File

@ -0,0 +1,3 @@
export default function toLowerCase(value) {
return value.toLowerCase();
}

View File

@ -1,3 +0,0 @@
export default function toLowerCase(value: string): string {
return value.toLowerCase();
}

View File

@ -18,7 +18,6 @@
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png" />
<link rel="icon" type="image/ico" href="favicon.ico" />
</head>
<body>
<!-- DO NOT touch the following DIV -->
<div id="q-app"></div>

View File

@ -1,3 +1,15 @@
<script setup>
import { ref } from 'vue';
import Navbar from 'src/components/Navbar.vue';
import LeftMenu from 'src/components/LeftMenu.vue';
const drawer = ref(false);
const miniState = ref(true);
function onToggleDrawer() {
drawer.value = !drawer.value;
}
</script>
<template>
<q-layout view="hHh lpR fFf">
<Navbar @toggle-drawer="onToggleDrawer()" />
@ -21,16 +33,4 @@
</q-layout>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import Navbar from 'src/components/Navbar.vue';
import LeftMenu from 'src/components/LeftMenu.vue';
const drawer = ref(false);
const miniState = ref(true);
function onToggleDrawer(): void {
drawer.value = !drawer.value;
}
</script>
<style lang="scss" scoped></style>

View File

@ -1,3 +1,32 @@
<script setup>
import { ref } from 'vue';
import { useRouter } from 'vue-router';
const router = useRouter();
const customers = [
{
id: 1101,
name: 'Bruce Wayne',
username: 'batman',
email: 'batman@gotham',
phone: '555-555-5555',
expanded: ref(false),
},
{
id: 1102,
name: 'James Gordon',
username: 'jamesgordon',
email: 'jamesgordon@gotham',
phone: '555-555-1111',
expanded: ref(false),
},
];
function navigate(id) {
router.push({ path: `/customer/${id}` });
}
</script>
<template>
<q-page class="q-pa-md">
<div class="column items-center q-gutter-y-md">
@ -18,7 +47,7 @@
<q-item class="q-pa-none">
<q-item-section>
<q-item-label caption>Phone</q-item-label>
<q-item-label>{{ customer.phone }} </q-item-label>
<q-item-label>{{ customer.phone }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
@ -38,7 +67,13 @@
</q-btn>
<q-separator vertical />
<q-card-actions vertical class="justify-between">
<q-btn flat round color="orange" icon="arrow_circle_right" @click="navigate(customer.id)" />
<q-btn
flat
round
color="orange"
icon="arrow_circle_right"
@click="navigate(customer.id)"
/>
<q-btn flat round color="accent" icon="preview" />
<q-btn flat round color="accent" icon="vn:ticket" />
<q-card-actions>
@ -94,49 +129,12 @@
</q-menu>
</q-btn>
</q-card-actions>
</q-card-section> -->
</q-card-section>-->
</q-card>
</div>
</q-page>
</template>
<script lang="ts" setup>
import { ref, Ref } from 'vue';
import { useRouter } from 'vue-router';
const router = useRouter();
interface Customer {
id: number;
name: string;
username: string;
email: string;
phone: string;
expanded: Ref<boolean>;
}
const customers: Customer[] = [
{
id: 1101,
name: 'Bruce Wayne',
username: 'batman',
email: 'batman@gotham',
phone: '555-555-5555',
expanded: ref(false),
},
{
id: 1102,
name: 'James Gordon',
username: 'jamesgordon',
email: 'jamesgordon@gotham',
phone: '555-555-1111',
expanded: ref(false),
},
];
function navigate(id: number) {
router.push({ path: `/customer/${id}` });
}
</script>
<style lang="scss" scoped>
.card {
width: 100%;

View File

@ -1,6 +1,84 @@
<script setup>
import { ref } from 'vue';
const slide = ref('style');
const slideText = 'Description text';
</script>
<template>
<q-page class="q-pa-md">
<q-card class="q-pa-md"> Dashboard page.. </q-card>
<q-banner
v-if="$q.screen.gt.xs"
inline-actions
rounded
class="bg-orange text-white q-mb-lg"
>
You have lost connection to the internet. This app is offline.
<template #action>
<q-btn flat label="Turn ON Wifi" />
<q-btn flat label="Dismiss" />
</template>
</q-banner>
<div class="row items-start wrap q-col-gutter-md q-mb-lg">
<div class="col-12 col-md">
<div class="text-h6 text-grey-8 q-mb-sm">Responsive monitor</div>
<q-carousel
v-model="slide"
transition-prev="scale"
transition-next="scale"
swipeable
animated
control-color="white"
navigation
padding
arrows
height="300px"
class="bg-orange-3 text-white shadow-1 rounded-borders"
>
<q-carousel-slide name="style" class="column no-wrap flex-center">
<q-icon name="style" size="56px" />
<div class="q-mt-md text-center">{{ slideText }}</div>
</q-carousel-slide>
<q-carousel-slide name="tv" class="column no-wrap flex-center">
<q-icon name="live_tv" size="56px" />
<div class="q-mt-md text-center">{{ slideText }}</div>
</q-carousel-slide>
<q-carousel-slide name="layers" class="column no-wrap flex-center">
<q-icon name="layers" size="56px" />
<div class="q-mt-md text-center">{{ slideText }}</div>
</q-carousel-slide>
<q-carousel-slide name="map" class="column no-wrap flex-center">
<q-icon name="terrain" size="56px" />
<div class="q-mt-md text-center">{{ slideText }}</div>
</q-carousel-slide>
</q-carousel>
</div>
<div class="col-12 col-md">
<div class="text-h6 text-grey-8 q-mb-sm">Responsive monitor</div>
<q-card class="q-pa-md">Dashboard page..</q-card>
</div>
<div class="col-12 col-md">
<div class="text-h6 text-grey-8 q-mb-sm">Responsive monitor</div>
<q-card class="q-pa-md">Dashboard page..</q-card>
</div>
</div>
<div class="row items-start q-col-gutter-md q-mb-lg">
<div class="col-12 col-md">
<div class="text-h6 text-grey-8 q-mb-sm">Responsive monitor</div>
<q-card class="q-pa-md">Dashboard page..</q-card>
</div>
<div class="col-12 col-md">
<div class="text-h6 text-grey-8 q-mb-sm">Responsive monitor</div>
<q-card class="q-pa-md">Dashboard page..</q-card>
</div>
</div>
<div class="row items-start q-col-gutter-md q-mb-lg">
<div class="col">
<div class="text-h6 text-grey-8 q-mb-sm">Responsive monitor</div>
<q-card class="q-pa-md">Dashboard page..</q-card>
</div>
</div>
</q-page>
</template>

View File

@ -1,60 +1,4 @@
<template>
<q-layout>
<q-header class="transparent">
<q-toolbar>
<q-space></q-space>
<q-toggle
:label="t(`globals.lang['${locale}']`)"
icon="public"
color="orange"
false-value="es"
true-value="en"
v-model="locale"
/>
<q-toggle v-model="darkMode" checked-icon="dark_mode" color="orange" unchecked-icon="light_mode" />
</q-toolbar>
</q-header>
<q-page-container>
<q-page>
<div id="login">
<q-card class="login q-pa-xl">
<q-img src="~/assets/logo.svg" alt="Logo" fit="contain" :ratio="16 / 9" class="q-mb-md" />
<q-form @submit="onSubmit" class="q-gutter-md">
<q-input
filled
color="orange"
v-model="username"
:label="t('login.username')"
lazy-rules
:rules="[
(val: string) => (val && val.length > 0) || 'Please type something',
]"
/>
<q-input
filled
color="orange"
type="password"
v-model="password"
:label="t('login.password')"
lazy-rules
:rules="[
(val: string) => (val && val.length > 0) || 'Please type something',
]"
/>
<q-toggle v-model="keepLogin" :label="t('login.keepLogin')" color="orange" />
<div>
<q-btn :label="t('login.submit')" type="submit" color="orange" />
</div>
</q-form>
</q-card>
</div>
</q-page>
</q-page-container>
</q-layout>
</template>
<script lang="ts" setup>
<script setup>
import { computed, ref } from 'vue';
import { Dark, useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
@ -73,20 +17,21 @@ let password = ref('');
let keepLogin = ref(true);
const darkMode = computed({
get(): boolean {
get() {
return Dark.isActive;
},
set(value: boolean): void {
set(value) {
Dark.set(value);
},
});
async function onSubmit(): Promise<void> {
async function onSubmit() {
try {
const { data } = await axios.post('/api/accounts/login', {
user: username.value,
password: password.value,
});
session.setToken({
token: data.token,
keepLogin: keepLogin.value,
@ -96,6 +41,7 @@ async function onSubmit(): Promise<void> {
message: t('login.loginSuccess'),
type: 'positive',
});
await router.push({ path: '/dashboard' });
} catch (error) {
if (axios.isAxiosError(error)) {
@ -116,6 +62,77 @@ async function onSubmit(): Promise<void> {
}
</script>
<template>
<q-layout>
<q-header class="transparent">
<q-toolbar>
<q-space></q-space>
<q-toggle
:label="t(`globals.lang['${locale}']`)"
icon="public"
color="orange"
false-value="es"
true-value="en"
v-model="locale"
/>
<q-toggle
v-model="darkMode"
checked-icon="dark_mode"
color="orange"
unchecked-icon="light_mode"
/>
</q-toolbar>
</q-header>
<q-page-container>
<q-page>
<div id="login">
<q-card class="login q-pa-xl">
<q-img
src="~/assets/logo.svg"
alt="Logo"
fit="contain"
:ratio="16 / 9"
class="q-mb-md"
/>
<q-form @submit="onSubmit" class="q-gutter-md">
<q-input
filled
color="orange"
v-model="username"
:label="t('login.username')"
lazy-rules
:rules="[
(val) => (val && val.length > 0) || 'Please type something',
]"
/>
<q-input
filled
color="orange"
type="password"
v-model="password"
:label="t('login.password')"
lazy-rules
:rules="[
(val) => (val && val.length > 0) || 'Please type something',
]"
/>
<q-toggle
v-model="keepLogin"
:label="t('login.keepLogin')"
color="orange"
/>
<div>
<q-btn :label="t('login.submit')" type="submit" color="orange" />
</div>
</q-form>
</q-card>
</div>
</q-page>
</q-page-container>
</q-layout>
</template>
<style lang="scss" scoped>
#login {
display: flex;

View File

@ -0,0 +1,46 @@
import { jest, describe, expect, it, beforeAll } from '@jest/globals';
import { createWrapper, axios } from 'app/test/jest/jestHelpers';
import Login from '../Login.vue';
const mockPush = jest.fn();
jest.mock('vue-router', () => ({
useRouter: () => ({
push: mockPush,
}),
}));
describe('Login', () => {
let vm;
beforeAll(() => {
vm = createWrapper(Login).vm;
});
it('should successfully set the token into session', async () => {
jest.spyOn(axios, 'post').mockResolvedValue({ data: { token: 'token' } });
jest.spyOn(vm.quasar, 'notify')
expect(vm.session.getToken()).toEqual('');
await vm.onSubmit();
expect(vm.session.getToken()).toEqual('token');
expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining(
{ 'type': 'positive' }
));
vm.session.destroy();
});
it('should not set the token into session if any error occurred', async () => {
jest.spyOn(axios, 'post').mockRejectedValue(new Error('error'));
jest.spyOn(vm.quasar, 'notify')
expect(vm.session.getToken()).toEqual('');
await vm.onSubmit();
expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining(
{ 'type': 'negative' }
));
});
});

View File

@ -5,15 +5,16 @@
<div class="text-h2" style="opacity: 0.4">Oops. Nothing here...</div>
<q-btn class="q-mt-xl" color="white" text-color="blue" unelevated to="/" label="Go Home" no-caps />
<q-btn
class="q-mt-xl"
color="white"
text-color="blue"
unelevated
to="/"
label="Go Home"
no-caps
/>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'Error404',
});
</script>

7
src/quasar.d.ts vendored
View File

@ -1,7 +0,0 @@
// Forces TS to apply `@quasar/app` augmentations of `quasar` package
// Removing this would break `quasar/wrappers` imports as those typings are declared
// into `@quasar/app`
// As a side effect, since `@quasar/app` reference `quasar` to augment it,
// this declaration also apply `quasar` own
// augmentations (eg. adds `$q` into Vue component context)
/// <reference types="@quasar/app" />

32
src/router/index.js Normal file
View File

@ -0,0 +1,32 @@
import { route } from 'quasar/wrappers';
import { createRouter, createMemoryHistory, createWebHistory, createWebHashHistory } from 'vue-router';
import routes from './routes';
/*
* 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 default route(function (/* { store, ssrContext } */) {
const createHistory = process.env.SERVER
? createMemoryHistory
: process.env.VUE_ROUTER_MODE === 'history'
? createWebHistory
: createWebHashHistory;
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.MODE === 'ssr' ? void 0 : process.env.VUE_ROUTER_BASE),
});
return Router;
});

View File

@ -1,104 +0,0 @@
import { route } from 'quasar/wrappers';
import { createMemoryHistory, createRouter, createWebHashHistory, createWebHistory } from 'vue-router';
import routes from './routes';
import { i18n } from 'src/boot/i18n';
import { useSession } from 'src/composables/useSession';
import { useRole } from 'src/composables/useRole';
const session = useSession();
const role = useRole();
/*
* 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.
*/
interface ItemMeta {
title?: string;
icon?: string;
roles?: string[];
}
export default route(function (/* { store, ssrContext } */) {
const createHistory = process.env.SERVER
? createMemoryHistory
: process.env.VUE_ROUTER_MODE === 'history'
? createWebHistory
: createWebHashHistory;
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.MODE === 'ssr' ? void 0 : process.env.VUE_ROUTER_BASE),
});
Router.beforeEach((to, from, next) => {
const { isLoggedIn } = session;
if (!isLoggedIn && to.name !== 'Login') {
next({ path: '/login', query: { redirect: to.fullPath } });
} else {
const pathRoutes = to.matched;
const droles = pathRoutes.every(route => {
const meta = route.meta as ItemMeta;
if (meta.roles)
return role.hasAny(meta.roles)
return true;
});
if (droles) {
next();
} else {
next({ path: '/' });
}
}
});
Router.afterEach((to) => {
interface Meta {
title?: string;
}
const { t } = i18n.global;
let title = '';
if (to.matched && to.matched.length > 2) {
const parent = to.matched[1];
const parentMeta: Meta = parent.meta;
if (parentMeta && parentMeta.title) {
title += t(`pages.${parentMeta.title}`);
}
}
const childMeta: Meta = to.meta;
if (childMeta && childMeta.title) {
if (title != '') title += ': ';
const childTitle = t(`pages.${childMeta.title}`);
if (to.params && to.params.id) {
const idParam = to.params.id;
if (idParam instanceof Array) {
title += `${idParam[0]} - ${childTitle}`
} else {
title += `${idParam} - ${childTitle}`;
}
} else {
title += t(`pages.${childMeta.title}`);
}
}
document.title = title;
});
return Router;
});

View File

@ -1,8 +1,7 @@
import { RouteRecordRaw } from 'vue-router';
import customer from './modules/customer';
import ticket from './modules/ticket';
const routes: RouteRecordRaw[] = [
const routes = [
{
path: '/login',
name: 'Login',
@ -12,7 +11,7 @@ const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'Main',
component: () => import('../layouts/Main.vue'),
component: () => import('../layouts/MainLayout.vue'),
redirect: { name: 'Dashboard' },
children: [
{
@ -21,11 +20,11 @@ const routes: RouteRecordRaw[] = [
meta: { title: 'dashboard', icon: 'dashboard' },
component: () => import('../pages/Dashboard/Dashboard.vue'),
},
/* {
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import('../pages/NotFound.vue'),
}, */
/* {
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import('../pages/NotFound.vue'),
}, */
// Module routes
customer,
ticket,
@ -33,4 +32,4 @@ const routes: RouteRecordRaw[] = [
},
];
export default routes;
export default routes;

7
src/shims-vue.d.ts vendored
View File

@ -1,7 +0,0 @@
// Mocks all files ending in `.vue` showing them as plain Vue instances
/* eslint-disable */
declare module '*.vue' {
import type { DefineComponent } from 'vue';
const component: DefineComponent<{}, {}, any>;
export default component;
}

View File

@ -1,40 +0,0 @@
/// <reference types="cypress" />
// Use `cy.dataCy` custom command for more robust tests
// See https://docs.cypress.io/guides/references/best-practices.html#Selecting-Elements
// ** This file is an example of how to write Cypress tests, you can safely delete it **
// This test will pass when run against a clean Quasar project
describe('Landing', () => {
beforeEach(() => {
cy.visit('/');
});
it('.should() - assert that <title> is correct', () => {
cy.title().should('include', 'Salix');
});
});
// ** The following code is an example to show you how to write some tests for your home page **
//
// describe('Home page tests', () => {
// beforeEach(() => {
// cy.visit('/');
// });
// it('has pretty background', () => {
// cy.dataCy('landing-wrapper')
// .should('have.css', 'background').and('match', /(".+(\/img\/background).+\.png)/);
// });
// it('has pretty logo', () => {
// cy.dataCy('landing-wrapper img')
// .should('have.class', 'logo-main')
// .and('have.attr', 'src')
// .and('match', /^(data:image\/svg\+xml).+/);
// });
// it('has very important information', () => {
// cy.dataCy('instruction-wrapper')
// .should('contain', 'SETUP INSTRUCTIONS')
// .and('contain', 'Configure Authentication')
// .and('contain', 'Database Configuration and CRUD operations')
// .and('contain', 'Continuous Integration & Continuous Deployment CI/CD');
// });
// });

View File

@ -0,0 +1,13 @@
/// <reference types="cypress" />
describe('Login', () => {
beforeEach(() => {
cy.visit('/#/login');
});
it('should log in', () => {
cy.get('input[aria-label="Username"]').type('developer')
cy.get('input[aria-label="Password"]').type('nightmare')
cy.get('button[type="submit"]').click()
});
});

View File

@ -15,17 +15,17 @@
// cypress/plugins/index.js
const { injectDevServer } = require('@quasar/quasar-app-extension-testing-e2e-cypress/cct-dev-server');
// const {injectDevServer} = require('@quasar/quasar-app-extension-testing-e2e-cypress/cct-dev-server');
/**
* @type {Cypress.PluginConfig}
*/
// /**
// * @type {Cypress.PluginConfig}
// */
module.exports = async (on, config) => {
// Enable component testing, you can safely remove this
// if you don't plan to use Cypress for unit tests
// if (config.testingType === 'component') {
// await injectDevServer(on, config);
// }
// // Enable component testing, you can safely remove this
// // if you don't plan to use Cypress for unit tests
// if (config.testingType === 'component') {
// await injectDevServer(on, config);
// }
return config;
};

View File

@ -1,41 +0,0 @@
import { describe, expect, it } from '@jest/globals';
import { installQuasarPlugin } from '@quasar/quasar-app-extension-testing-unit-jest';
import { mount, shallowMount } from '@vue/test-utils';
import { QBtn } from 'quasar';
import MyButton from './demo/MyButton.vue';
// Specify here Quasar config you'll need to test your component
installQuasarPlugin();
describe('MyButton', () => {
it('has increment method', () => {
const wrapper = mount(MyButton);
const { vm } = wrapper;
expect(typeof vm.increment).toBe('function');
});
it('can check the inner text content', () => {
const wrapper = mount(MyButton);
const { vm } = wrapper;
expect(vm.$el.textContent).toContain('rocket muffin');
expect(wrapper.find('.content').text()).toContain('rocket muffin');
});
it('sets the correct default data', () => {
const wrapper = mount(MyButton);
const { vm } = wrapper;
expect(typeof vm.counter).toBe('number');
expect(vm.counter).toBe(0);
});
it('correctly updates counter when button is pressed', async () => {
const wrapper = shallowMount(MyButton);
const { vm } = wrapper;
const button = wrapper.findComponent(QBtn);
await button.trigger('click');
expect(vm.counter).toBe(1);
});
});

View File

@ -1,18 +0,0 @@
import { installQuasarPlugin } from '@quasar/quasar-app-extension-testing-unit-jest';
import { describe, expect, it } from '@jest/globals';
import { mount } from '@vue/test-utils';
import MyDialog from './demo/MyDialog.vue';
installQuasarPlugin();
describe('MyDialog', () => {
it('should mount MyDialog', () => {
const wrapper = mount(MyDialog, {
data: () => ({
isDialogOpen: true,
}),
});
expect(wrapper.exists()).toBe(true);
});
});

View File

@ -1,23 +0,0 @@
<script lang="ts" setup>
import { ref } from 'vue';
const props = defineProps({
incrementStep: {
type: Number,
default: 1,
},
});
const counter = ref(0);
const input = ref('rocket muffin');
function increment() {
counter.value += props.incrementStep;
}
</script>
<template>
<div>
<p class="content">{{ input }}</p>
<span>{{ counter }}</span>
<q-btn class="button" @click="increment()"></q-btn>
</div>
</template>

View File

@ -1,12 +0,0 @@
<script lang="ts" setup>
import { ref } from 'vue';
const isDialogOpen = ref(false);
</script>
<template>
<q-dialog v-model="isDialogOpen">
<q-card>
<q-card-section>Custom dialog which should be tested</q-card-section>
</q-card>
</q-dialog>
</template>

28
test/jest/jestHelpers.js Normal file
View File

@ -0,0 +1,28 @@
import { mount, flushPromises } from '@vue/test-utils';
import { installQuasarPlugin } from '@quasar/quasar-app-extension-testing-unit-jest';
import { i18n } from 'src/boot/i18n';
import { Notify } from 'quasar';
import axios from 'axios';
// Specify here Quasar config you'll need to test your component
installQuasarPlugin({
plugins: {
Notify
}
});
export function createWrapper(component) {
const wrapper = mount(component, {
global: {
plugins: [i18n]
}
});
const vm = wrapper.vm;
return { vm, wrapper };
}
export {
axios,
flushPromises
}

View File

@ -1,6 +0,0 @@
{
"extends": "@quasar/app/tsconfig-preset",
"compilerOptions": {
"baseUrl": "."
}
}