0
0
Fork 0

Merge branch 'dev' into 4834-create-worker-module

This commit is contained in:
Pau 2023-01-09 09:25:17 +01:00
commit 7ef50bcbe9
66 changed files with 3893 additions and 27606 deletions

View File

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

View File

@ -1,8 +1,6 @@
/dist /dist
/src-bex/www
/src-capacitor /src-capacitor
/src-cordova /src-cordova
/.quasar /.quasar
/node_modules /node_modules
.eslintrc.js .eslintrc.js
babel.config.js

View File

@ -5,13 +5,13 @@ module.exports = {
root: true, root: true,
parserOptions: { parserOptions: {
parser: '@babel/eslint-parser', ecmaVersion: '2021', // Allows for the parsing of modern ECMAScript features
ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
sourceType: 'module', // Allows for the use of imports
}, },
env: { env: {
node: true,
browser: true, browser: true,
'vue/setup-compiler-macros': true,
}, },
// Rules order is important, please avoid shuffling them // Rules order is important, please avoid shuffling them
@ -22,7 +22,7 @@ module.exports = {
// Uncomment any of the lines below to choose desired strictness, // Uncomment any of the lines below to choose desired strictness,
// but leave only one uncommented! // but leave only one uncommented!
// See https://eslint.vuejs.org/rules/#available-rules // 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-strongly-recommended', // Priority B: Strongly Recommended (Improving Readability)
// 'plugin:vue/vue3-recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead) // 'plugin:vue/vue3-recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead)
@ -52,9 +52,6 @@ module.exports = {
process: 'readonly', process: 'readonly',
Capacitor: 'readonly', Capacitor: 'readonly',
chrome: '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 // add your custom rules here
@ -64,10 +61,9 @@ module.exports = {
// allow debugger during development only // allow debugger during development only
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
}, },
overrides: [ overrides: [
{ {
files: ['**/*.spec.{js,ts}'], files: ['test/cypress/**/*.spec.{js,ts}'],
extends: [ extends: [
// Add Cypress-specific lint rules, globals and Cypress plugin // Add Cypress-specific lint rules, globals and Cypress plugin
// See https://github.com/cypress-io/eslint-plugin-cypress#rules // See https://github.com/cypress-io/eslint-plugin-cypress#rules

9
.gitignore vendored
View File

@ -1,7 +1,6 @@
.DS_Store .DS_Store
.thumbs.db .thumbs.db
node_modules node_modules
junit.xml
# Quasar core related directories # Quasar core related directories
.quasar .quasar
@ -17,10 +16,6 @@ junit.xml
/src-capacitor/www /src-capacitor/www
/src-capacitor/node_modules /src-capacitor/node_modules
# BEX related directories and files
/src-bex/www
/src-bex/js/core
# Log files # Log files
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
@ -32,3 +27,7 @@ yarn-error.log*
*.ntvs* *.ntvs*
*.njsproj *.njsproj
*.sln *.sln
# Cypress directories and files
/tests/cypress/videos
/tests/cypress/screenshots

3
.npmrc Normal file
View File

@ -0,0 +1,3 @@
# pnpm-related options
shamefully-hoist=true
strict-peer-dependencies=false

View File

@ -1,9 +0,0 @@
/* eslint-disable */
// https://github.com/michael-ciniawsky/postcss-load-config
module.exports = {
plugins: [
// to edit target browsers: use "browserslist" field in package.json
require('autoprefixer'),
],
};

View File

@ -3,7 +3,7 @@
"dbaeumer.vscode-eslint", "dbaeumer.vscode-eslint",
"esbenp.prettier-vscode", "esbenp.prettier-vscode",
"editorconfig.editorconfig", "editorconfig.editorconfig",
"Vue.volar", "vue.volar",
"wayou.vscode-todo-highlight" "wayou.vscode-todo-highlight"
], ],
"unwantedRecommendations": [ "unwantedRecommendations": [

16
CHANGELOG.md Normal file
View File

@ -0,0 +1,16 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2253.01] - 2023-01-05
### Added
- Added...
### Changed
- Changed...

View File

@ -1,12 +1,10 @@
# Salix (salix-front) # Lilium (lilium-front)
Salix front-end Lilium frontend
## Install the dependencies ## Install the dependencies
```bash ```bash
yarn
# or
npm install npm install
``` ```
@ -22,20 +20,16 @@ sudo npm install -g @quasar/cli
quasar dev quasar dev
``` ```
### Lint the files ### Run unit tests
```bash ```bash
yarn lint npm run test:unit
# or
npm run lint
``` ```
### Format the files ### Run e2e tests
```bash ```bash
yarn format npm run test:e2e
# or
npm run format
``` ```
### Build the app for production ### Build the app for production
@ -43,7 +37,3 @@ npm run format
```bash ```bash
quasar build quasar build
``` ```
### Customize the configuration
See [Configuring quasar.config.js](https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js).

View File

@ -1,18 +0,0 @@
/* eslint-env node */
// eslint-disable-next-line @typescript-eslint/no-var-requires
const fs = require('fs-extra');
let extend = undefined;
/**
* The .babelrc file has been created to assist Jest for transpiling.
* You should keep your application's babel rules in this file.
*/
if (fs.existsSync('./.babelrc')) {
extend = './.babelrc';
}
module.exports = {
presets: ['@quasar/babel-preset-app'],
extends: extend,
};

22
cypress.config.js Normal file
View File

@ -0,0 +1,22 @@
const { defineConfig } = require('cypress');
module.exports = defineConfig({
e2e: {
baseUrl: 'http://localhost:8080/',
fixturesFolder: 'test/cypress/fixtures',
screenshotsFolder: 'test/cypress/screenshots',
supportFile: 'test/cypress/support/index.js',
videosFolder: 'test/cypress/videos',
video: true,
specPattern: 'test/cypress/integration/*.spec.js',
experimentalRunAllSpecs: true,
component: {
componentFolder: 'src',
testFiles: '**/*.spec.js',
supportFile: 'test/cypress/support/unit.js',
},
setupNodeEvents(on, config) {
// implement node event listeners here
},
},
});

View File

@ -1,15 +0,0 @@
{
"baseUrl": "http://localhost:8080/",
"fixturesFolder": "tests/cypress/fixtures",
"integrationFolder": "tests/cypress/integration",
"pluginsFile": "tests/cypress/plugins/index.js",
"screenshotsFolder": "tests/cypress/screenshots",
"supportFile": "tests/cypress/support/index.js",
"videosFolder": "tests/cypress/videos",
"video": true,
"component": {
"componentFolder": "src",
"testFiles": "**/*.spec.js",
"supportFile": "tests/cypress/support/unit.js"
}
}

View File

@ -19,7 +19,6 @@
<link rel="icon" type="image/ico" href="favicon.ico" /> <link rel="icon" type="image/ico" href="favicon.ico" />
</head> </head>
<body> <body>
<!-- DO NOT touch the following DIV --> <!-- quasar:entry-point -->
<div id="q-app"></div>
</body> </body>
</html> </html>

View File

@ -1,59 +0,0 @@
const esModules = ['quasar', 'quasar/lang', 'lodash-es'].join('|');
module.exports = {
globals: {
__DEV__: true,
// TODO: Remove if resolved natively
// See https://github.com/vuejs/vue-jest/issues/175
'vue-jest': {
pug: { doctype: 'html' },
},
},
// Jest assumes we are testing in node environment, specify jsdom environment instead
testEnvironment: 'jsdom',
// noStackTrace: true,
// bail: true,
// cache: false,
// verbose: true,
// watch: true,
reporters: ['default', 'jest-junit'],
collectCoverage: false,
coverageDirectory: '<rootDir>/tests/jest/coverage',
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: {
// branches: 50,
// functions: 50,
// lines: 50,
// statements: 50
},
},
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',
'^filters/(.*)$': '<rootDir>/src/filters/$1',
'^layouts/(.*)$': '<rootDir>/src/layouts/$1',
'^pages/(.*)$': '<rootDir>/src/pages/$1',
'^assets/(.*)$': '<rootDir>/src/assets/$1',
'^boot/(.*)$': '<rootDir>/src/boot/$1',
'.*css$': '@quasar/quasar-app-extension-testing-unit-jest/stub.css',
},
transform: {
'.*\\.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: ['jest-serializer-vue'],
};

View File

@ -14,8 +14,5 @@
"vue$": ["node_modules/vue/dist/vue.runtime.esm-bundler.js"] "vue$": ["node_modules/vue/dist/vue.runtime.esm-bundler.js"]
} }
}, },
"exclude": ["dist", ".quasar", "node_modules"], "exclude": ["dist", ".quasar", "node_modules"]
"vueCompilerOptions": {
"experimentalDisableTemplateSupport": true
}
} }

29747
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,63 +1,54 @@
{ {
"name": "salix-front", "name": "salix-front",
"version": "0.0.1", "version": "0.0.1",
"description": "Salix front-end", "description": "Salix frontend",
"productName": "Salix", "productName": "Salix",
"author": "Verdnatura", "author": "Verdnatura",
"private": true, "private": true,
"scripts": { "scripts": {
"lint": "eslint --ext .js,.vue ./", "lint": "eslint --ext .js,.vue ./",
"format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore", "format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore",
"test:e2e": "cypress open",
"test:e2e:ci": "cypress run --browser chromium",
"test": "echo \"See package.json => scripts for available tests.\" && exit 0", "test": "echo \"See package.json => scripts for available tests.\" && exit 0",
"test:unit": "jest --reporters=default --watchAll", "test:unit": "vitest",
"test:unit:ci": "jest --ci --reporters=default --reporters=jest-junit --maxWorkers=2", "test:unit:ci": "vitest run"
"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\"",
"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": { "dependencies": {
"@quasar/extras": "^1.15.8", "@quasar/extras": "^1.15.8",
"axios": "^1.2.1", "axios": "^1.2.1",
"core-js": "^3.6.5",
"pinia": "^2.0.28", "pinia": "^2.0.28",
"quasar": "^2.11.1", "quasar": "^2.11.1",
"validator": "^13.7.0", "validator": "^13.7.0",
"vue": "^3.2.45", "vue": "^3.2.45",
"vue-i18n": "^9.2.2", "vue-i18n": "^9.2.2",
"vue-router": "^4.1.6" "vue-router": "^4.1.6",
"vue-router-mock": "^0.1.9"
}, },
"devDependencies": { "devDependencies": {
"@babel/eslint-parser": "^7.13.14", "@intlify/unplugin-vue-i18n": "^0.8.1",
"@intlify/vue-i18n-loader": "^4.1.0",
"@pinia/testing": "^0.0.14", "@pinia/testing": "^0.0.14",
"@quasar/app-webpack": "^3.6.2", "@quasar/app-vite": "^1.1.3",
"@quasar/quasar-app-extension-testing-e2e-cypress": "^4.2.2", "@quasar/quasar-app-extension-testing-unit-vitest": "^0.1.2",
"@quasar/quasar-app-extension-testing-unit-jest": "^3.0.0-beta.5", "@vue/test-utils": "^2.0.0",
"autoprefixer": "^10.4.13",
"cypress": "^12.2.0",
"eslint": "^8.30.0", "eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-cypress": "^2.12.1", "eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-jest": "^27.1.7", "eslint-plugin-vue": "^9.8.0",
"eslint-plugin-vue": "^8.7.1", "postcss": "^8.4.20",
"eslint-webpack-plugin": "^3.2.0", "prettier": "^2.8.1",
"jest-junit": "^13.0.0", "vitest": "^0.26.3"
"prettier": "^2.5.1"
}, },
"browserslist": [
"last 10 Chrome versions",
"last 10 Firefox versions",
"last 4 Edge versions",
"last 7 Safari versions",
"last 8 Android versions",
"last 8 ChromeAndroid versions",
"last 8 FirefoxAndroid versions",
"last 10 iOS versions",
"last 5 Opera versions"
],
"engines": { "engines": {
"node": ">= 12.22.1", "node": "^18 || ^16 || ^14.19",
"npm": ">= 6.13.4", "npm": ">= 6.13.4",
"yarn": ">= 1.21.1" "yarn": ">= 1.21.1"
},
"overrides": {
"@vitejs/plugin-vue": "^4.0.0",
"vite": "^4.0.3",
"vitest": "^0.26.3"
} }
} }

27
postcss.config.js Normal file
View File

@ -0,0 +1,27 @@
/* eslint-disable */
// https://github.com/michael-ciniawsky/postcss-load-config
module.exports = {
plugins: [
// https://github.com/postcss/autoprefixer
require('autoprefixer')({
overrideBrowserslist: [
'last 4 Chrome versions',
'last 4 Firefox versions',
'last 4 Edge versions',
'last 4 Safari versions',
'last 4 Android versions',
'last 4 ChromeAndroid versions',
'last 4 FirefoxAndroid versions',
'last 4 iOS versions',
],
}),
// https://github.com/elchininet/postcss-rtlcss
// If you want to support RTL css, then
// 1. yarn/npm install postcss-rtlcss
// 2. optionally set quasar.config.js > framework > lang to an RTL language
// 3. uncomment the following line:
// require('postcss-rtlcss')
],
};

View File

@ -6,26 +6,32 @@
*/ */
// Configuration for your app // Configuration for your app
// https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js
const ESLintPlugin = require('eslint-webpack-plugin');
const { configure } = require('quasar/wrappers'); const { configure } = require('quasar/wrappers');
const VueI18nPlugin = require('@intlify/unplugin-vue-i18n/vite')
const path = require('path'); const path = require('path');
module.exports = configure(function (ctx) { module.exports = configure(function (/* ctx */) {
return { return {
// https://v2.quasar.dev/quasar-cli-webpack/supporting-ts eslint: {
supportTS: false, // fix: true,
// include = [],
// exclude = [],
// rawOptions = {},
warnings: true,
errors: true,
},
// https://v2.quasar.dev/quasar-cli-webpack/prefetch-feature // https://v2.quasar.dev/quasar-cli/prefetch-feature
// preFetch: true, // preFetch: true,
// app boot file (/src/boot) // app boot file (/src/boot)
// --> boot files are part of "main.js" // --> boot files are part of "main.js"
// https://v2.quasar.dev/quasar-cli-webpack/boot-files // https://v2.quasar.dev/quasar-cli/boot-files
boot: ['i18n', 'axios', 'pinia'], boot: ['i18n', 'axios'],
// https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-css // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
css: ['app.scss'], css: ['app.scss'],
// https://github.com/quasarframework/quasar/tree/dev/extras // https://github.com/quasarframework/quasar/tree/dev/extras
@ -43,58 +49,63 @@ module.exports = configure(function (ctx) {
'material-symbols-outlined', 'material-symbols-outlined',
], ],
// Full list of options: https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-build // Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#build
build: { build: {
vueRouterMode: 'hash', // available values: 'hash', 'history' target: {
browser: ['es2022', 'edge88', 'firefox78', 'chrome87', 'safari13.1'],
// transpile: false, node: 'node18',
// publicPath: '/',
// Add dependencies for transpiling with Babel (Array of string/regex)
// (from node_modules, which are by default not transpiled).
// Applies only if "transpile" is set to true.
// transpileDependencies: [],
// rtl: true, // https://quasar.dev/options/rtl-support
// preloadChunks: true,
// showProgress: false,
// gzip: true,
// analyze: true,
// Options below are automatically set depending on the env, set them if you want to override
// extractCSS: false,
// https://v2.quasar.dev/quasar-cli-webpack/handling-webpack
// "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain
chainWebpack(chain) {
chain.module
.rule('i18n')
.resourceQuery(/blockType=i18n/)
.type('javascript/auto')
.use('i18n')
.loader('@intlify/vue-i18n-loader')
.end();
chain.plugin('eslint-webpack-plugin').use(ESLintPlugin, [{ extensions: ['js', 'vue'] }]);
}, },
extendWebpack(cfg) {
cfg.resolve.alias = {
...cfg.resolve.alias, // This adds the existing alias
// Add your own alias like this vueRouterMode: 'hash', // available values: 'hash', 'history'
composables: path.resolve(__dirname, './src/composables'), // vueRouterBase,
filters: path.resolve(__dirname, './src/filters'), // vueDevtools,
// vueOptionsAPI: false,
// rebuildCache: true, // rebuilds Vite/linter/etc cache on startup
// publicPath: '/',
// analyze: true,
// env: {},
// rawDefine: {}
// ignorePublicFolder: true,
// minify: false,
// polyfillModulePreload: true,
// distDir
extendViteConf(viteConf) {
// FIXME: Delete deprecated property polyfillModulePreload
// that is set by Quasar by default
delete viteConf.build.polyfillModulePreload;
viteConf.build.modulePreload = {
polyfill: false,
}; };
}, },
// viteVuePluginOptions: {},
alias: {
composables: path.join(__dirname, './src/composables'),
filters: path.join(__dirname, './src/filters'),
},
vitePlugins: [
[
VueI18nPlugin,
{
// if you want to use Vue I18n Legacy API, you need to set `compositionOnly: false`
// compositionOnly: false,
// you need to set i18n resource including paths !
include: path.resolve(__dirname, './src/i18n/**'),
},
],
],
}, },
// Full list of options: https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-devServer // Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
devServer: { devServer: {
server: { server: {
type: 'http', type: 'http',
}, },
port: 8080,
proxy: { proxy: {
'/api': { '/api': {
target: 'http://0.0.0.0:3000', target: 'http://0.0.0.0:3000',
@ -105,15 +116,17 @@ module.exports = configure(function (ctx) {
}, },
}, },
// https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-framework // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#framework
framework: { framework: {
config: { config: {
brand: { config: {
primary: 'orange', brand: {
primary: 'orange',
},
dark: 'auto',
}, },
dark: 'auto',
}, },
lang: 'es', lang: 'en-GB',
// iconSet: 'material-icons', // Quasar icon set // iconSet: 'material-icons', // Quasar icon set
// lang: 'en-US', // Quasar language pack // lang: 'en-US', // Quasar language pack
@ -130,11 +143,29 @@ module.exports = configure(function (ctx) {
}, },
// animations: 'all', // --- includes all animations // animations: 'all', // --- includes all animations
// https://quasar.dev/options/animations // https://v2.quasar.dev/options/animations
animations: [], animations: [],
// https://v2.quasar.dev/quasar-cli-webpack/developing-ssr/configuring-ssr // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#property-sourcefiles
// sourceFiles: {
// rootComponent: 'src/App.vue',
// router: 'src/router/index',
// store: 'src/store/index',
// registerServiceWorker: 'src-pwa/register-service-worker',
// serviceWorker: 'src-pwa/custom-service-worker',
// pwaManifestFile: 'src-pwa/manifest.json',
// electronMain: 'src-electron/electron-main',
// electronPreload: 'src-electron/electron-preload'
// },
// https://v2.quasar.dev/quasar-cli/developing-ssr/configuring-ssr
ssr: { ssr: {
// ssrPwaHtmlFilename: 'offline.html', // do NOT use index.html as name!
// will mess up SSR
// extendSSRWebserverConf (esbuildConf) {},
// extendPackageJson (json) {},
pwa: false, pwa: false,
// manualStoreHydration: true, // manualStoreHydration: true,
@ -143,81 +174,42 @@ module.exports = configure(function (ctx) {
prodPort: 3000, // The default port that the production server should use prodPort: 3000, // The default port that the production server should use
// (gets superseded if process.env.PORT is specified at runtime) // (gets superseded if process.env.PORT is specified at runtime)
maxAge: 1000 * 60 * 60 * 24 * 30,
// Tell browser when a file from the server should expire from cache (in ms)
chainWebpackWebserver(chain) {
chain.plugin('eslint-webpack-plugin').use(ESLintPlugin, [{ extensions: ['js'] }]);
},
middlewares: [ middlewares: [
ctx.prod ? 'compression' : '',
'render', // keep this as last one 'render', // keep this as last one
], ],
}, },
// https://v2.quasar.dev/quasar-cli-webpack/developing-pwa/configuring-pwa // https://v2.quasar.dev/quasar-cli/developing-pwa/configuring-pwa
pwa: { pwa: {
workboxPluginMode: 'GenerateSW', // 'GenerateSW' or 'InjectManifest' workboxMode: 'generateSW', // or 'injectManifest'
workboxOptions: {}, // only for GenerateSW injectPwaMetaTags: true,
swFilename: 'sw.js',
// for the custom service worker ONLY (/src-pwa/custom-service-worker.[js|ts]) manifestFilename: 'manifest.json',
// if using workbox in InjectManifest mode useCredentialsForManifestTag: false,
// useFilenameHashes: true,
chainWebpackCustomSW(chain) { // extendGenerateSWOptions (cfg) {}
chain.plugin('eslint-webpack-plugin').use(ESLintPlugin, [{ extensions: ['js'] }]); // extendInjectManifestOptions (cfg) {},
}, // extendManifestJson (json) {}
// extendPWACustomSWConf (esbuildConf) {}
manifest: {
name: `Salix`,
short_name: `Salix`,
description: `Salix front-end`,
display: 'standalone',
orientation: 'portrait',
background_color: '#ffffff',
theme_color: '#027be3',
icons: [
{
src: 'icons/icon-128x128.png',
sizes: '128x128',
type: 'image/png',
},
{
src: 'icons/icon-192x192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: 'icons/icon-256x256.png',
sizes: '256x256',
type: 'image/png',
},
{
src: 'icons/icon-384x384.png',
sizes: '384x384',
type: 'image/png',
},
{
src: 'icons/icon-512x512.png',
sizes: '512x512',
type: 'image/png',
},
],
},
}, },
// Full list of options: https://v2.quasar.dev/quasar-cli-webpack/developing-cordova-apps/configuring-cordova // Full list of options: https://v2.quasar.dev/quasar-cli/developing-cordova-apps/configuring-cordova
cordova: { cordova: {
// noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing // noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing
}, },
// Full list of options: https://v2.quasar.dev/quasar-cli-webpack/developing-capacitor-apps/configuring-capacitor // Full list of options: https://v2.quasar.dev/quasar-cli/developing-capacitor-apps/configuring-capacitor
capacitor: { capacitor: {
hideSplashscreen: true, hideSplashscreen: true,
}, },
// Full list of options: https://v2.quasar.dev/quasar-cli-webpack/developing-electron-apps/configuring-electron // Full list of options: https://v2.quasar.dev/quasar-cli/developing-electron-apps/configuring-electron
electron: { electron: {
// extendElectronMainConf (esbuildConf)
// extendElectronPreloadConf (esbuildConf)
inspectPort: 5858,
bundler: 'packager', // 'packager' or 'builder' bundler: 'packager', // 'packager' or 'builder'
packager: { packager: {
@ -234,18 +226,16 @@ module.exports = configure(function (ctx) {
builder: { builder: {
// https://www.electron.build/configuration/configuration // https://www.electron.build/configuration/configuration
appId: 'salix-front', appId: 'salix-frontend',
}, },
},
// "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain // Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-browser-extensions/configuring-bex
bex: {
contentScripts: ['my-content-script'],
chainWebpackMain(chain) { // extendBexScriptsConf (esbuildConf) {}
chain.plugin('eslint-webpack-plugin').use(ESLintPlugin, [{ extensions: ['js'] }]); // extendBexManifestJson (json) {}
},
chainWebpackPreload(chain) {
chain.plugin('eslint-webpack-plugin').use(ESLintPlugin, [{ extensions: ['js'] }]);
},
}, },
}; };
}); });

View File

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

View File

@ -1,11 +1,5 @@
{ {
"unit-jest": { "unit-vitest": {
"runnerCommand": "jest --ci" "runnerCommand": "vitest run"
}, }
"e2e-cypress": {
"runnerCommand": "cross-env E2E_TEST=true start-test \"quasar dev\" http-get://localhost:8080 \"cypress run\""
},
"unit-cypress": {
"runnerCommand": "cypress run-ct"
}
} }

View File

@ -1,21 +1,24 @@
import { boot } from 'quasar/wrappers';
import axios from 'axios'; import axios from 'axios';
import { useSession } from 'src/composables/useSession'; import { useSession } from 'src/composables/useSession';
const { getToken } = useSession(); export default boot(() => {
const { getToken } = useSession();
axios.defaults.baseURL = '/api/'; axios.defaults.baseURL = '/api/';
axios.interceptors.request.use( axios.interceptors.request.use(
function (context) { function (context) {
const token = getToken(); const token = getToken();
if (token.length && context.headers) { if (token.length && context.headers) {
context.headers.Authorization = token; context.headers.Authorization = token;
}
return context;
},
function (error) {
return Promise.reject(error);
} }
);
return context; });
},
function (error) {
return Promise.reject(error);
}
);

View File

@ -5,9 +5,10 @@ import messages from 'src/i18n';
const i18n = createI18n({ const i18n = createI18n({
locale: 'en', locale: 'en',
fallbackLocale: 'en', fallbackLocale: 'en',
globalInjection: true,
messages, messages,
missingWarn: false,
legacy: false, legacy: false,
missingWarn: false
}); });
export default boot(({ app }) => { export default boot(({ app }) => {

View File

@ -1,8 +0,0 @@
import { boot } from 'quasar/wrappers';
import { createPinia } from 'pinia';
export default boot(({ app }) => {
const pinia = createPinia();
app.use(pinia);
});

View File

@ -14,30 +14,31 @@ const pinnedModules = computed(() => navigation.getPinnedModules());
</script> </script>
<template> <template>
<q-menu <q-menu anchor="bottom left" class="row q-pa-md q-col-gutter-lg" max-width="350px" max-height="400px">
anchor="bottom left" <template v-if="pinnedModules.length">
class="row q-pa-md q-col-gutter-lg" <div v-for="item of pinnedModules" :key="item.title" class="row no-wrap q-pa-xs flex-item">
max-width="350px" <q-btn
max-height="400px" align="evenly"
v-if="pinnedModules.length" padding="16px"
> flat
<div v-for="item of pinnedModules" :key="item.title" class="row no-wrap q-pa-xs flex-item"> stack
<q-btn size="lg"
align="evenly" :icon="item.icon"
padding="16px" color="primary"
flat class="col-4 button"
stack :to="{ name: item.name }"
size="lg" >
:icon="item.icon" <div class="text-center text-primary button-text">
color="primary" {{ t(item.title) }}
class="col-4 button" </div>
:to="{ name: item.name }" </q-btn>
> </div>
<div class="text-center text-primary button-text"> </template>
{{ t(item.title) }} <template v-else>
</div> <div class="row no-wrap q-pa-xs flex-item text-center text-grey-5" style="min-width: 200px">
</q-btn> {{ t('globals.noPinnedModules') }}
</div> </div>
</template>
</q-menu> </q-menu>
</template> </template>

View File

@ -22,9 +22,16 @@ const userLocale = computed({
if (value === 'en') value = 'en-GB'; if (value === 'en') value = 'en-GB';
import(`quasar/lang/${value}`).then((language) => { // FIXME: Dynamic imports from absolute paths are not compatible with vite:
Quasar.lang.set(language.default); // https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars#limitations
}); try {
const langList = import.meta.glob('../../node_modules/quasar/lang/*.mjs');
langList[`../../node_modules/quasar/lang/${value}.mjs`]().then((lang) => {
Quasar.lang.set(lang.default);
});
} catch (error) {
//
}
}, },
}); });

View File

@ -1,104 +0,0 @@
import { jest, describe, expect, it, beforeAll } from '@jest/globals';
import { createWrapper } from 'app/tests/jest/jestHelpers';
import Leftmenu from '../LeftMenu.vue';
import { createTestingPinia } from '@pinia/testing';
import { useNavigationStore } from 'src/stores/useNavigationStore';
const mockPush = jest.fn();
jest.mock('vue-router', () => ({
useRouter: () => ({
push: mockPush,
currentRoute: { value: 'myCurrentRoute' },
}),
useRoute: () => ({
matched: [],
}),
}));
jest.mock('src/router/modules', () => [
{
path: '/customer',
name: 'Customer',
meta: {
title: 'customers',
icon: 'vn:client',
},
menus: {
main: ['CustomerList', 'CustomerCreate'],
card: ['CustomerBasicData'],
},
children: [
{
path: '',
name: 'CustomerMain',
children: [
{
path: 'list',
name: 'CustomerList',
meta: {
title: 'list',
icon: 'view_list',
},
},
{
path: 'create',
name: 'CustomerCreate',
meta: {
title: 'createCustomer',
icon: 'vn:addperson',
},
},
],
},
],
},
]);
describe('Leftmenu', () => {
let vm;
let navigation;
beforeAll(async () => {
vm = createWrapper(Leftmenu, {
propsData: {
source: 'main',
},
global: {
plugins: [createTestingPinia({ stubActions: false })],
},
}).vm;
navigation = useNavigationStore();
navigation.modules = ['customer']; // I should mock to have just one module but isn´t working
navigation.fetchPinned = jest.fn().mockReturnValue(Promise.resolve(true));
navigation.getModules = jest.fn().mockReturnValue({
value: [
{
name: 'customer',
title: 'customer.pageTitles.customers',
icon: 'vn:customer',
module: 'customer',
},
],
});
});
it('should return a proper formated object with two child items', async () => {
const expectedMenuItem = [
{
name: 'CustomerList',
title: 'customer.pageTitles.list',
icon: 'view_list',
},
{
name: 'CustomerCreate',
title: 'customer.pageTitles.createCustomer',
icon: 'vn:addperson',
},
];
const firstMenuItem = vm.items[0];
expect(firstMenuItem.children).toEqual(expect.arrayContaining(expectedMenuItem));
});
});

View File

@ -0,0 +1,49 @@
<script setup>
import { ref } from 'vue';
import { useDialogPluginComponent } from 'quasar';
import { useI18n } from 'vue-i18n';
const $props = defineProps({
question: {
type: String,
default: '',
},
message: {
type: String,
default: '',
},
});
defineEmits(['confirm', ...useDialogPluginComponent.emits]);
const { dialogRef, onDialogOK } = useDialogPluginComponent();
const { t } = useI18n();
const question = ref($props.question);
const message = ref($props.question);
const isLoading = ref(false);
</script>
<template>
<q-dialog ref="dialogRef" persistent>
<q-card class="q-pa-sm">
<q-card-section class="row items-center q-pb-none">
<span class="text-h6 text-grey">{{ message }}</span>
<q-space />
<q-btn icon="close" flat round dense v-close-popup />
</q-card-section>
<q-card-section class="row items-center">
{{ question }}
</q-card-section>
<q-card-actions align="right">
<q-btn :label="t('globals.cancel')" color="primary" flat v-close-popup />
<q-btn :label="t('globals.confirm')" color="primary" :loading="isLoading" @click="onDialogOK" />
</q-card-actions>
</q-card>
</q-dialog>
</template>
<style lang="scss" scoped>
.q-card {
min-width: 350px;
}
</style>

View File

@ -1,112 +0,0 @@
import * as validator from 'validator';
export const validators = {
presence: ($translate, value) => {
if (validator.isEmpty(value ? String(value) : ''))
throw new Error(_($translate, `Value can't be empty`));
},
absence: ($translate, value) => {
if (!validator.isEmpty(value))
throw new Error(_($translate, `Value should be empty`));
},
length: ($translate, value, conf) => {
let options = {
min: conf.min || conf.is,
max: conf.max || conf.is
};
let val = value ? String(value) : '';
if (!validator.isLength(val, options)) {
if (conf.is) {
throw new Error(_($translate,
`Value should be %s characters long`, [conf.is]));
} else if (conf.min && conf.max) {
throw new Error(_($translate,
`Value should have a length between %s and %s`, [conf.min, conf.max]));
} else if (conf.min) {
throw new Error(_($translate,
`Value should have at least %s characters`, [conf.min]));
} else {
throw new Error(_($translate,
`Value should have at most %s characters`, [conf.max]));
}
}
},
numericality: ($translate, value, conf) => {
if (conf.int) {
if (!validator.isInt(value))
throw new Error(_($translate, `Value should be integer`));
} else if (!validator.isNumeric(value))
throw new Error(_($translate, `Value should be a number`));
},
inclusion: ($translate, value, conf) => {
if (!validator.isIn(value, conf.in))
throw new Error(_($translate, `Invalid value`));
},
exclusion: ($translate, value, conf) => {
if (validator.isIn(value, conf.in))
throw new Error(_($translate, `Invalid value`));
},
format: ($translate, value, conf) => {
if (!validator.matches(value, conf.with))
throw new Error(_($translate, `Invalid value`));
},
custom: ($translate, value, conf) => {
if (!conf.bindedFunction(value))
throw new Error(_($translate, `Invalid value`));
}
};
/**
* Checks if value satisfies a set of validations.
*
* @param {*} value The value
* @param {Array} validations Array with validations
*/
export function validateAll($translate, value, validations) {
for (let conf of validations)
validate($translate, value, conf);
}
/**
* Checks if value satisfies a validation.
*
* @param {*} value The value
* @param {Object} conf The validation configuration
*/
export function validate($translate, value, conf) {
let validator = validators[conf.validation];
try {
let isEmpty = value == null || value === '';
if (isEmpty)
checkNull($translate, value, conf);
if (validator && (!isEmpty || conf.validation == 'presence'))
validator($translate, value, conf);
} catch (e) {
let message = conf.message ? conf.message : e.message;
throw new Error(_($translate, message));
}
}
/**
* Checks if value satisfies a blank or not null validation.
*
* @param {*} value The value
* @param {Object} conf The validation configuration
*/
export function checkNull($translate, value, conf) {
if (conf.allowBlank === false && value === '')
throw new Error(_($translate, `Value can't be blank`));
else if (conf.allowNull === false && value == null)
throw new Error(_($translate, `Value can't be null`));
}
export function _($translate, text, params = []) {
text = $translate.instant(text);
for (let i = 0; i < params.length; i++) {
text = text.replace('%s', params[i]);
}
return text;
}

View File

@ -1,14 +1,8 @@
// app global css in SCSS form // app global css in SCSS form
@import './icons.scss'; @import './icons.scss';
.body--dark { a {
.q-card--dark { text-decoration: none;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2), 0 2px 2px rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.12);
}
.q-layout__shadow::after {
box-shadow: 0 0 10px 2px rgba(0, 0, 0, 0.2), 0 0px 10px rgba(0, 0, 0, 0.24) !important;
}
} }
.link { .link {

View File

@ -23,8 +23,11 @@ $negative: #c10015;
$info: #31ccec; $info: #31ccec;
$warning: #f2c037; $warning: #f2c037;
$color-spacer-light: rgba(255, 255, 255, .12); $color-spacer-light: rgba(255, 255, 255, 0.12);
$color-spacer:rgba(255, 255, 255, .3); $color-spacer: rgba(255, 255, 255, 0.3);
$border-thin-light: 1px solid $color-spacer-light; $border-thin-light: 1px solid $color-spacer-light;
$dark-shadow-color: #000;
$layout-shadow-dark: 0 0 10px 2px rgba(0, 0, 0, 0.2), 0 0px 10px rgba(0, 0, 0, 0.24);
$spacing-md: 16px; $spacing-md: 16px;

View File

@ -30,6 +30,7 @@ export default {
rowAdded: 'Row added', rowAdded: 'Row added',
rowRemoved: 'Row removed', rowRemoved: 'Row removed',
pleaseWait: 'Please wait...', pleaseWait: 'Please wait...',
noPinnedModules: 'You have dont have any pinned modules',
}, },
moduleIndex: { moduleIndex: {
allModules: 'All modules', allModules: 'All modules',
@ -296,7 +297,7 @@ export default {
}, },
invoiceOut: { invoiceOut: {
pageTitles: { pageTitles: {
invoiceOuts: 'InvoiceOuts', invoiceOuts: 'Invoices Out',
list: 'List', list: 'List',
createInvoiceOut: 'Create invoice out', createInvoiceOut: 'Create invoice out',
summary: 'Summary', summary: 'Summary',

View File

@ -30,6 +30,7 @@ export default {
rowAdded: 'Fila añadida', rowAdded: 'Fila añadida',
rowRemoved: 'Fila eliminada', rowRemoved: 'Fila eliminada',
pleaseWait: 'Por favor, espera...', pleaseWait: 'Por favor, espera...',
noPinnedModules: 'No has fijado ningún módulo',
}, },
moduleIndex: { moduleIndex: {
allModules: 'Todos los módulos', allModules: 'Todos los módulos',

View File

@ -1,48 +0,0 @@
import { jest, describe, expect, it, beforeAll } from '@jest/globals';
import { createWrapper, axios } from 'app/tests/jest/jestHelpers';
import ClaimDescriptorMenu from '../Card/ClaimDescriptorMenu.vue';
const mockPush = jest.fn();
jest.mock('vue-router', () => ({
useRouter: () => ({
push: mockPush,
currentRoute: {
value: {
params: {
id: 1,
},
},
},
}),
}));
describe('ClaimDescriptorMenu', () => {
let vm;
beforeAll(() => {
vm = createWrapper(ClaimDescriptorMenu, {
propsData: {
claim: {
id: 1
}
}
}).vm;
});
afterEach(() => {
jest.clearAllMocks();
});
describe('deleteClaim()', () => {
it('should delete the claim', async () => {
jest.spyOn(axios, 'delete').mockResolvedValue({ data: true });
jest.spyOn(vm.quasar, 'notify');
await vm.deleteClaim();
expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining(
{ 'type': 'positive' }
));
});
});
});

View File

@ -21,9 +21,16 @@ const userLocale = computed({
if (value === 'en') value = 'en-GB'; if (value === 'en') value = 'en-GB';
import(`quasar/lang/${value}`).then((language) => { // FIXME: Dynamic imports from absolute paths are not compatible with vite:
Quasar.lang.set(language.default); // https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars#limitations
}); try {
const langList = import.meta.glob('../../node_modules/quasar/lang/*.mjs');
langList[`../../node_modules/quasar/lang/${value}.mjs`]().then((lang) => {
Quasar.lang.set(lang.default);
});
} catch (error) {
//
}
}, },
}); });
@ -41,25 +48,29 @@ const password = ref('');
const keepLogin = ref(true); const keepLogin = ref(true);
async function onSubmit() { async function onSubmit() {
const { data } = await axios.post('Accounts/login', { try {
user: username.value, const { data } = await axios.post('Accounts/login', {
password: password.value, user: username.value,
}); password: password.value,
});
if (!data) return; if (!data) return;
await session.login(data.token, keepLogin.value); await session.login(data.token, keepLogin.value);
quasar.notify({ quasar.notify({
message: t('login.loginSuccess'), message: t('login.loginSuccess'),
type: 'positive', type: 'positive',
}); });
const currentRoute = router.currentRoute.value; const currentRoute = router.currentRoute.value;
if (currentRoute.query && currentRoute.query.redirect) { if (currentRoute.query && currentRoute.query.redirect) {
router.push(currentRoute.query.redirect); router.push(currentRoute.query.redirect);
} else { } else {
router.push({ name: 'Dashboard' }); router.push({ name: 'Dashboard' });
}
} catch (error) {
//
} }
} }
</script> </script>
@ -70,7 +81,15 @@ async function onSubmit() {
<q-page id="login"> <q-page id="login">
<q-page-sticky position="top-right"> <q-page-sticky position="top-right">
<q-toolbar> <q-toolbar>
<q-btn :label="t('globals.language')" icon="translate" color="primary" size="sm" flat rounded> <q-btn
id="switchLanguage"
:label="t('globals.language')"
icon="translate"
color="primary"
size="sm"
flat
rounded
>
<q-menu auto-close> <q-menu auto-close>
<q-list dense> <q-list dense>
<q-item @click="userLocale = 'en'" :active="userLocale == 'en'" v-ripple clickable> <q-item @click="userLocale = 'en'" :active="userLocale == 'en'" v-ripple clickable>

View File

@ -1,71 +0,0 @@
import { jest, describe, expect, it, beforeAll } from '@jest/globals';
import { createWrapper, axios } from 'app/tests/jest/jestHelpers';
import TicketBoxing from '../TicketBoxing.vue';
const mockPush = jest.fn();
jest.mock('vue-router', () => ({
useRouter: () => ({
push: mockPush,
currentRoute: {
value: {
params: {
id: 1
}
}
}
}),
}));
// #4836 - Investigate how to test q-drawer outside
// q-layout or how to teleport q-drawer inside
xdescribe('TicketBoxing', () => {
let vm;
beforeAll(() => {
vm = createWrapper(TicketBoxing).vm;
});
afterEach(() => {
jest.clearAllMocks();
});
describe('getVideoList()', () => {
it('should when response videoList use to list', async () => {
const expeditionId = 1;
const timed = {
min: 1,
max: 2
}
const videoList = [
"2022-01-01T01-01-00.mp4",
"2022-02-02T02-02-00.mp4",
"2022-03-03T03-03-00.mp4",
]
jest.spyOn(axios, 'get').mockResolvedValue({ data: videoList });
jest.spyOn(vm.quasar, 'notify');
await vm.getVideoList(expeditionId, timed);
expect(vm.videoList.length).toEqual(videoList.length);
expect(vm.slide).toEqual(videoList.reverse()[0]);
});
it('should if not have video show notify', async () => {
const expeditionId = 1;
const timed = {
min: 1,
max: 2
}
jest.spyOn(axios, 'get').mockResolvedValue({ data: [] });
jest.spyOn(vm.quasar, 'notify')
await vm.getVideoList(expeditionId, timed);
expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining(
{ 'type': 'negative' }
));
});
});
});

View File

@ -35,7 +35,7 @@ export default route(function (/* { store, ssrContext } */) {
// Leave this as is and make changes in quasar.conf.js instead! // Leave this as is and make changes in quasar.conf.js instead!
// quasar.conf.js -> build -> vueRouterMode // quasar.conf.js -> build -> vueRouterMode
// quasar.conf.js -> build -> publicPath // quasar.conf.js -> build -> publicPath
history: createHistory(process.env.MODE === 'ssr' ? void 0 : process.env.VUE_ROUTER_BASE), history: createHistory(process.env.VUE_ROUTER_BASE),
}); });
Router.beforeEach(async (to, from, next) => { Router.beforeEach(async (to, from, next) => {

View File

@ -30,7 +30,7 @@ const routes = [
worker, worker,
invoiceOut, invoiceOut,
{ {
path: '/:pathMatch(.*)*', path: '/:catchAll(.*)*',
name: 'NotFound', name: 'NotFound',
component: () => import('../pages/NotFound.vue'), component: () => import('../pages/NotFound.vue'),
}, },

20
src/stores/index.js Normal file
View File

@ -0,0 +1,20 @@
import { store } from 'quasar/wrappers';
import { createPinia } from 'pinia';
/*
* If not building with SSR mode, you can
* directly export the Store instantiation;
*
* The function below can be async too; either use
* async/await or return a Promise which resolves
* with the Store instance.
*/
export default store((/* { ssrContext } */) => {
const pinia = createPinia();
// You can add Pinia plugins here
// pinia.use(SomePiniaPlugin)
return pinia;
});

10
src/stores/store-flag.d.ts vendored Normal file
View File

@ -0,0 +1,10 @@
/* eslint-disable */
// THIS FEATURE-FLAG FILE IS AUTOGENERATED,
// REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING
import "quasar/dist/types/feature-flag";
declare module "quasar/dist/types/feature-flag" {
interface QuasarFeatureFlags {
store: true;
}
}

View File

@ -2,6 +2,8 @@
describe('Login', () => { describe('Login', () => {
beforeEach(() => { beforeEach(() => {
cy.visit('/#/login'); cy.visit('/#/login');
cy.get('#switchLanguage').click();
cy.get('div.q-menu div.q-item:nth-child(1)').click();
}); });
it('should fail to log in using wrong user', () => { it('should fail to log in using wrong user', () => {
@ -36,7 +38,7 @@ describe('Login', () => {
cy.get('#logout').click(); cy.get('#logout').click();
cy.window().its('localStorage').invoke('getItem', 'token').should('not.exist'); cy.window().its('localStorage').invoke('getItem', 'token').should('not.exist');
cy.url().should('contain', '/login'); cy.url().should('contain', '/login');
}) });
it(`should get redirected to dashboard since employee can't create tickets`, () => { it(`should get redirected to dashboard since employee can't create tickets`, () => {
cy.visit('/#/ticket/create', { failOnStatusCode: false }); cy.visit('/#/ticket/create', { failOnStatusCode: false });
@ -45,7 +47,7 @@ describe('Login', () => {
cy.get('input[aria-label="Password"]').type('nightmare'); cy.get('input[aria-label="Password"]').type('nightmare');
cy.get('button[type="submit"]').click(); cy.get('button[type="submit"]').click();
cy.url().should('contain', '/dashboard'); cy.url().should('contain', '/dashboard');
}) });
// ticket creation is not yet implemented, use this test once it is // ticket creation is not yet implemented, use this test once it is
// it(`should get redirected to ticket creation after login since salesPerson can do it`, () => { // it(`should get redirected to ticket creation after login since salesPerson can do it`, () => {

View File

@ -1,8 +1,9 @@
describe('TicketBoxing', () => { /// <reference types="cypress" />
xdescribe('TicketBoxing', () => {
beforeEach(() => { beforeEach(() => {
const ticketId = 1; const ticketId = 1;
cy.viewport(1280, 720) cy.viewport(1280, 720);
cy.login('developer') cy.login('developer');
cy.visit(`/#/ticket/${ticketId}/boxing`); cy.visit(`/#/ticket/${ticketId}/boxing`);
}); });
@ -23,16 +24,11 @@ describe('TicketBoxing', () => {
method: 'GET', method: 'GET',
url: '/api/Boxings/*', url: '/api/Boxings/*',
}, },
[ ['2022-01-01T01-01-00.mp4', '2022-02-02T02-02-00.mp4', '2022-03-03T03-03-00.mp4']
"2022-01-01T01-01-00.mp4",
"2022-02-02T02-02-00.mp4",
"2022-03-03T03-03-00.mp4",
]
).as('getVideoList'); ).as('getVideoList');
cy.get('.q-list > :nth-child(3)').click(); cy.get('.q-list > :nth-child(3)').click();
cy.get('.q-list > :nth-child(1)').should('be.visible'); cy.get('.q-list > :nth-child(1)').should('be.visible');
cy.get('.q-list > :nth-child(2)').should('be.visible'); cy.get('.q-list > :nth-child(2)').should('be.visible');
}); });
}); });

View File

@ -26,7 +26,7 @@
// DO NOT REMOVE // DO NOT REMOVE
// Imports Quasar Cypress AE predefined commands // Imports Quasar Cypress AE predefined commands
import { registerCommands } from '@quasar/quasar-app-extension-testing-e2e-cypress'; // import { registerCommands } from '@quasar/quasar-app-extension-testing-e2e-cypress';
Cypress.Commands.add('login', (user) => { Cypress.Commands.add('login', (user) => {
cy.visit('/#/login'); cy.visit('/#/login');
cy.request({ cy.request({
@ -34,10 +34,10 @@ Cypress.Commands.add('login', (user) => {
url: '/api/accounts/login', url: '/api/accounts/login',
body: { body: {
user: user, user: user,
password: 'nightmare' password: 'nightmare',
} },
}).then(response => { }).then((response) => {
window.localStorage.setItem('token', response.body.token); window.localStorage.setItem('token', response.body.token);
}) });
}) });
registerCommands(); // registerCommands();

View File

@ -14,3 +14,4 @@
// *********************************************************** // ***********************************************************
import './commands'; import './commands';

View File

@ -1,21 +1,13 @@
import { jest, describe, expect, it, beforeAll } from '@jest/globals'; import { vi, describe, expect, it, beforeAll } from 'vitest';
import { createWrapper } from 'app/tests/jest/jestHelpers'; import { createWrapper } from 'app/test/vitest/helper';
import App from '../App.vue'; import App from 'src/App.vue';
import { useSession } from 'src/composables/useSession'; import { useSession } from 'src/composables/useSession';
const mockPush = jest.fn(); const mockLoggedIn = vi.fn();
const mockLoggedIn = jest.fn(); const mockDestroy = vi.fn();
const mockDestroy = jest.fn();
const session = useSession(); const session = useSession();
jest.mock('vue-router', () => ({ vi.mock('src/composables/useSession', () => ({
useRouter: () => ({
push: mockPush,
currentRoute: { value: 'myCurrentRoute' },
}),
}));
jest.mock('src/composables/useSession', () => ({
useSession: () => ({ useSession: () => ({
isLoggedIn: mockLoggedIn, isLoggedIn: mockLoggedIn,
destroy: mockDestroy, destroy: mockDestroy,
@ -24,6 +16,7 @@ jest.mock('src/composables/useSession', () => ({
describe('App', () => { describe('App', () => {
let vm; let vm;
beforeAll(() => { beforeAll(() => {
const options = { const options = {
global: { global: {
@ -34,7 +27,7 @@ describe('App', () => {
}); });
it('should return a login error message', async () => { it('should return a login error message', async () => {
jest.spyOn(vm.quasar, 'notify'); vi.spyOn(vm.quasar, 'notify');
session.isLoggedIn.mockReturnValue(false); session.isLoggedIn.mockReturnValue(false);
@ -54,7 +47,7 @@ describe('App', () => {
}); });
it('should return an unauthorized error message', async () => { it('should return an unauthorized error message', async () => {
jest.spyOn(vm.quasar, 'notify'); vi.spyOn(vm.quasar, 'notify');
session.isLoggedIn.mockReturnValue(true); session.isLoggedIn.mockReturnValue(true);

View File

@ -0,0 +1,92 @@
import { vi, describe, expect, it, beforeAll } from 'vitest';
import { createWrapper, axios } from 'app/test/vitest/helper';
import Leftmenu from 'components/LeftMenu.vue';
import { useNavigationStore } from 'src/stores/useNavigationStore';
vi.mock('src/router/modules', () => ({
default: [
{
path: '/customer',
name: 'Customer',
meta: {
title: 'customers',
icon: 'vn:client',
},
menus: {
main: ['CustomerList', 'CustomerCreate'],
card: ['CustomerBasicData'],
},
children: [
{
path: '',
name: 'CustomerMain',
children: [
{
path: 'list',
name: 'CustomerList',
meta: {
title: 'list',
icon: 'view_list',
},
},
{
path: 'create',
name: 'CustomerCreate',
meta: {
title: 'createCustomer',
icon: 'vn:addperson',
},
},
],
},
],
},
],
}));
describe('Leftmenu', () => {
let vm;
let navigation;
beforeAll(() => {
vi.spyOn(axios, 'get').mockResolvedValue({
data: [],
});
vm = createWrapper(Leftmenu, {
propsData: {
source: 'main',
},
}).vm;
navigation = useNavigationStore();
navigation.fetchPinned = vi.fn().mockReturnValue(Promise.resolve(true));
navigation.getModules = vi.fn().mockReturnValue({
value: [
{
name: 'customer',
title: 'customer.pageTitles.customers',
icon: 'vn:customer',
module: 'customer',
},
],
});
});
it('should return a proper formated object with two child items', async () => {
const expectedMenuItem = [
{
name: 'CustomerList',
title: 'customer.pageTitles.list',
icon: 'view_list',
},
{
name: 'CustomerCreate',
title: 'customer.pageTitles.createCustomer',
icon: 'vn:addperson',
},
];
const firstMenuItem = vm.items[0];
expect(firstMenuItem.children).toEqual(expect.arrayContaining(expectedMenuItem));
});
});

View File

@ -1,15 +1,6 @@
import { jest, describe, expect, it, beforeAll } from '@jest/globals'; import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest';
import { createWrapper, axios } from 'app/tests/jest/jestHelpers'; import { createWrapper, axios } from 'app/test/vitest/helper';
import Paginate from '../PaginateData.vue'; import Paginate from 'components/PaginateData.vue';
const mockPush = jest.fn();
jest.mock('vue-router', () => ({
useRouter: () => ({
push: mockPush,
currentRoute: { value: 'myCurrentRoute' }
}),
}));
describe('Paginate', () => { describe('Paginate', () => {
const expectedUrl = '/api/customers'; const expectedUrl = '/api/customers';
@ -19,17 +10,17 @@ describe('Paginate', () => {
attrs: { attrs: {
url: expectedUrl, url: expectedUrl,
sortBy: 'id DESC', sortBy: 'id DESC',
rowsPerPage: 3 rowsPerPage: 3,
} },
}; };
vm = createWrapper(Paginate, options).vm; vm = createWrapper(Paginate, options).vm;
jest.spyOn(axios, 'get').mockResolvedValue({ vi.spyOn(axios, 'get').mockResolvedValue({
data: [ data: [
{ id: 1, name: 'Tony Stark' }, { id: 1, name: 'Tony Stark' },
{ id: 2, name: 'Jessica Jones' }, { id: 2, name: 'Jessica Jones' },
{ id: 3, name: 'Bruce Wayne' }, { id: 3, name: 'Bruce Wayne' },
] ],
}); });
}); });
@ -37,7 +28,7 @@ describe('Paginate', () => {
vm.rows = []; vm.rows = [];
vm.pagination.page = 1; vm.pagination.page = 1;
vm.hasMoreData = true; vm.hasMoreData = true;
}) });
describe('paginate()', () => { describe('paginate()', () => {
it('should call to the paginate() method and set the data on the rows property', async () => { it('should call to the paginate() method and set the data on the rows property', async () => {
@ -46,9 +37,9 @@ describe('Paginate', () => {
filter: { filter: {
order: 'id DESC', order: 'id DESC',
limit: 3, limit: 3,
skip: 0 skip: 0,
} },
} },
}; };
await vm.paginate(); await vm.paginate();
@ -63,9 +54,9 @@ describe('Paginate', () => {
filter: { filter: {
order: 'id DESC', order: 'id DESC',
limit: 3, limit: 3,
skip: 0 skip: 0,
} },
} },
}; };
await vm.paginate(); await vm.paginate();
@ -78,9 +69,9 @@ describe('Paginate', () => {
filter: { filter: {
order: 'id DESC', order: 'id DESC',
limit: 3, limit: 3,
skip: 3 skip: 3,
} },
} },
}; };
vm.pagination.page = 2; vm.pagination.page = 2;
@ -95,7 +86,7 @@ describe('Paginate', () => {
describe('onLoad()', () => { describe('onLoad()', () => {
it('should call to the done() callback and not increment the pagination', async () => { it('should call to the done() callback and not increment the pagination', async () => {
const index = 1; const index = 1;
const done = jest.fn(); const done = vi.fn();
await vm.onLoad(index, done); await vm.onLoad(index, done);
@ -113,7 +104,7 @@ describe('Paginate', () => {
expect(vm.pagination.page).toEqual(1); expect(vm.pagination.page).toEqual(1);
const index = 1; const index = 1;
const done = jest.fn(); const done = vi.fn();
await vm.onLoad(index, done); await vm.onLoad(index, done);
@ -122,11 +113,11 @@ describe('Paginate', () => {
}); });
it('should call to the done() callback with true as argument to finish pagination', async () => { it('should call to the done() callback with true as argument to finish pagination', async () => {
jest.spyOn(axios, 'get').mockResolvedValue({ vi.spyOn(axios, 'get').mockResolvedValue({
data: [ data: [
{ id: 1, name: 'Tony Stark' }, { id: 1, name: 'Tony Stark' },
{ id: 2, name: 'Jessica Jones' } { id: 2, name: 'Jessica Jones' },
] ],
}); });
vm.rows = [ vm.rows = [
@ -138,7 +129,7 @@ describe('Paginate', () => {
expect(vm.pagination.page).toEqual(1); expect(vm.pagination.page).toEqual(1);
const index = 1; const index = 1;
const done = jest.fn(); const done = vi.fn();
vm.hasMoreData = false; vm.hasMoreData = false;

View File

@ -1,23 +1,22 @@
import { describe, expect, it, jest } from '@jest/globals'; import { vi, describe, expect, it } from 'vitest';
import { axios, flushPromises } from 'app/tests/jest/jestHelpers'; import { axios, flushPromises } from 'app/test/vitest/helper';
import { useRole } from '../useRole'; import { useRole } from 'composables/useRole';
const role = useRole(); const role = useRole();
describe('useRole', () => { describe('useRole', () => {
describe('fetch', () => { describe('fetch', () => {
it('should call setUser and setRoles of the state with the expected data', async () => { it('should call setUser and setRoles of the state with the expected data', async () => {
const rolesData = [ const rolesData = [
{ {
role: { role: {
name: 'salesPerson' name: 'salesPerson',
} },
}, },
{ {
role: { role: {
name: 'admin' name: 'admin',
} },
} },
]; ];
const fetchedUser = { const fetchedUser = {
id: 999, id: 999,
@ -26,22 +25,22 @@ describe('useRole', () => {
lang: 'en', lang: 'en',
userConfig: { userConfig: {
darkMode: false, darkMode: false,
} },
} };
const expectedUser = { const expectedUser = {
id: 999, id: 999,
name: `T'Challa`, name: `T'Challa`,
nickname: 'Black Panther', nickname: 'Black Panther',
lang: 'en', lang: 'en',
darkMode: false, darkMode: false,
} };
const expectedRoles = ['salesPerson', 'admin'] const expectedRoles = ['salesPerson', 'admin'];
jest.spyOn(axios, 'get').mockResolvedValue({ vi.spyOn(axios, 'get').mockResolvedValue({
data: { roles: rolesData, user: fetchedUser } data: { roles: rolesData, user: fetchedUser },
}); });
jest.spyOn(role.state, 'setUser'); vi.spyOn(role.state, 'setUser');
jest.spyOn(role.state, 'setRoles'); vi.spyOn(role.state, 'setRoles');
role.fetch(); role.fetch();
@ -50,19 +49,20 @@ describe('useRole', () => {
expect(role.state.setUser).toHaveBeenCalledWith(expectedUser); expect(role.state.setUser).toHaveBeenCalledWith(expectedUser);
expect(role.state.setRoles).toHaveBeenCalledWith(expectedRoles); expect(role.state.setRoles).toHaveBeenCalledWith(expectedRoles);
role.state.setRoles([]) role.state.setRoles([]);
}); });
}); });
describe('hasAny', () => { describe('hasAny', () => {
it('should return true if a role matched', async () => { it('should return true if a role matched', async () => {
role.state.setRoles(['admin']) role.state.setRoles(['admin']);
const hasRole = role.hasAny(['admin']); const hasRole = role.hasAny(['admin']);
await flushPromises(); await flushPromises();
expect(hasRole).toBe(true); expect(hasRole).toBe(true);
role.state.setRoles([]) role.state.setRoles([]);
}); });
it('should return false if no roles matched', async () => { it('should return false if no roles matched', async () => {

View File

@ -1,7 +1,7 @@
import { describe, expect, it, jest } from '@jest/globals'; import { vi, describe, expect, it } from 'vitest';
import { useSession } from '../useSession'; import { axios } from 'app/test/vitest/helper';
import { useState } from '../useState'; import { useSession } from 'composables/useSession';
import { axios } from 'app/tests/jest/jestHelpers'; import { useState } from 'composables/useState';
const session = useSession(); const session = useSession();
const state = useState(); const state = useState();
@ -9,7 +9,7 @@ const state = useState();
describe('session', () => { describe('session', () => {
describe('getToken / setToken', () => { describe('getToken / setToken', () => {
it('should return an empty string if no token is found in local or session storage', async () => { it('should return an empty string if no token is found in local or session storage', async () => {
const expectedToken = '' const expectedToken = '';
const token = session.getToken(); const token = session.getToken();
@ -17,11 +17,11 @@ describe('session', () => {
}); });
it('should return the token stored in local or session storage', async () => { it('should return the token stored in local or session storage', async () => {
const expectedToken = 'myToken' const expectedToken = 'myToken';
const data = { const data = {
token: expectedToken, token: expectedToken,
keepLogin: false keepLogin: false,
} };
session.setToken(data); session.setToken(data);
const token = session.getToken(); const token = session.getToken();
@ -38,23 +38,22 @@ describe('session', () => {
nickname: 'Black Panther', nickname: 'Black Panther',
lang: 'en', lang: 'en',
darkMode: false, darkMode: false,
} };
const expectedUser = { const expectedUser = {
id: 0, id: 0,
name: '', name: '',
nickname: '', nickname: '',
lang: '', lang: '',
darkMode: null, darkMode: null,
} };
let user = state.getUser(); let user = state.getUser();
localStorage.setItem('token', 'tokenToBeGone'); localStorage.setItem('token', 'tokenToBeGone');
state.setUser(previousUser) state.setUser(previousUser);
expect(localStorage.getItem('token')).toEqual('tokenToBeGone'); expect(localStorage.getItem('token')).toEqual('tokenToBeGone');
expect(user.value).toEqual(previousUser); expect(user.value).toEqual(previousUser);
session.destroy(); session.destroy();
user = state.getUser(); user = state.getUser();
@ -71,29 +70,29 @@ describe('session', () => {
lang: 'en', lang: 'en',
userConfig: { userConfig: {
darkMode: false, darkMode: false,
} },
} };
const rolesData = [ const rolesData = [
{ {
role: { role: {
name: 'salesPerson' name: 'salesPerson',
} },
}, },
{ {
role: { role: {
name: 'admin' name: 'admin',
} },
} },
]; ];
it('should fetch the user roles and then set token in the sessionStorage', async () => { it('should fetch the user roles and then set token in the sessionStorage', async () => {
const expectedRoles = ['salesPerson', 'admin'] const expectedRoles = ['salesPerson', 'admin'];
jest.spyOn(axios, 'get').mockResolvedValue({ vi.spyOn(axios, 'get').mockResolvedValue({
data: { roles: rolesData, user: expectedUser } data: { roles: rolesData, user: expectedUser },
}); });
const expectedToken = 'mySessionToken' const expectedToken = 'mySessionToken';
const keepLogin = false const keepLogin = false;
await session.login(expectedToken, keepLogin); await session.login(expectedToken, keepLogin);
@ -105,17 +104,17 @@ describe('session', () => {
expect(localToken).toBeNull(); expect(localToken).toBeNull();
expect(sessionToken).toEqual(expectedToken); expect(sessionToken).toEqual(expectedToken);
session.destroy() // this clears token and user for any other test session.destroy(); // this clears token and user for any other test
}); });
it('should fetch the user roles and then set token in the localStorage', async () => { it('should fetch the user roles and then set token in the localStorage', async () => {
const expectedRoles = ['salesPerson', 'admin'] const expectedRoles = ['salesPerson', 'admin'];
jest.spyOn(axios, 'get').mockResolvedValue({ vi.spyOn(axios, 'get').mockResolvedValue({
data: { roles: rolesData, user: expectedUser } data: { roles: rolesData, user: expectedUser },
}); });
const expectedToken = 'myLocalToken' const expectedToken = 'myLocalToken';
const keepLogin = true const keepLogin = true;
await session.login(expectedToken, keepLogin); await session.login(expectedToken, keepLogin);
@ -127,7 +126,7 @@ describe('session', () => {
expect(localToken).toEqual(expectedToken); expect(localToken).toEqual(expectedToken);
expect(sessionToken).toBeNull(); expect(sessionToken).toBeNull();
session.destroy() // this clears token and user for any other test session.destroy(); // this clears token and user for any other test
}); });
}); });
}); });

View File

@ -0,0 +1,31 @@
import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest';
import { createWrapper, axios } from 'app/test/vitest/helper';
import ClaimDescriptorMenu from 'pages/Claim/Card/ClaimDescriptorMenu.vue';
describe('ClaimDescriptorMenu', () => {
let vm;
beforeAll(() => {
vm = createWrapper(ClaimDescriptorMenu, {
propsData: {
claim: {
id: 1,
},
},
}).vm;
});
afterEach(() => {
vi.clearAllMocks();
});
describe('deleteClaim()', () => {
it('should delete the claim', async () => {
vi.spyOn(axios, 'delete').mockResolvedValue({ data: true });
vi.spyOn(vm.quasar, 'notify');
await vm.deleteClaim();
expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining({ type: 'positive' }));
});
});
});

View File

@ -1,15 +1,6 @@
import { jest, describe, expect, it, beforeAll } from '@jest/globals'; import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest';
import { createWrapper, axios } from 'app/tests/jest/jestHelpers'; import { createWrapper, axios } from 'app/test/vitest/helper';
import Login from '../LoginMain.vue'; import Login from 'pages/Login/LoginMain.vue';
const mockPush = jest.fn();
jest.mock('vue-router', () => ({
useRouter: () => ({
push: mockPush,
currentRoute: { value: 'myCurrentRoute' }
}),
}));
describe('Login', () => { describe('Login', () => {
let vm; let vm;
@ -18,7 +9,7 @@ describe('Login', () => {
}); });
afterEach(() => { afterEach(() => {
jest.clearAllMocks(); vi.clearAllMocks();
}); });
it('should successfully set the token into session', async () => { it('should successfully set the token into session', async () => {
@ -29,26 +20,24 @@ describe('Login', () => {
lang: 'en', lang: 'en',
userConfig: { userConfig: {
darkMode: false, darkMode: false,
} },
} };
jest.spyOn(axios, 'post').mockResolvedValue({ data: { token: 'token' } }); vi.spyOn(axios, 'post').mockResolvedValue({ data: { token: 'token' } });
jest.spyOn(axios, 'get').mockResolvedValue({ data: { roles: [], user: expectedUser } }); vi.spyOn(axios, 'get').mockResolvedValue({ data: { roles: [], user: expectedUser } });
jest.spyOn(vm.quasar, 'notify'); vi.spyOn(vm.quasar, 'notify');
expect(vm.session.getToken()).toEqual(''); expect(vm.session.getToken()).toEqual('');
await vm.onSubmit(); await vm.onSubmit();
expect(vm.session.getToken()).toEqual('token'); expect(vm.session.getToken()).toEqual('token');
expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining( expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining({ type: 'positive' }));
{ 'type': 'positive' }
));
vm.session.destroy(); vm.session.destroy();
}); });
it('should not set the token into session if any error occurred', async () => { it('should not set the token into session if any error occurred', async () => {
jest.spyOn(axios, 'post').mockReturnValue({ data: null }); vi.spyOn(axios, 'post').mockReturnValue({ data: null });
jest.spyOn(vm.quasar, 'notify'); vi.spyOn(vm.quasar, 'notify');
await vm.onSubmit(); await vm.onSubmit();

View File

@ -0,0 +1,50 @@
import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest';
import { createWrapper, axios } from 'app/test/vitest/helper';
import TicketBoxing from 'pages/Ticket/Card/TicketBoxing.vue';
// #4836 - Investigate how to test q-drawer outside
// q-layout or how to teleport q-drawer inside
describe.skip('TicketBoxing', () => {
let vm;
beforeAll(() => {
vm = createWrapper(TicketBoxing).vm;
});
afterEach(() => {
vi.clearAllMocks();
});
describe('getVideoList()', () => {
it('should when response videoList use to list', async () => {
const expeditionId = 1;
const timed = {
min: 1,
max: 2,
};
const videoList = ['2022-01-01T01-01-00.mp4', '2022-02-02T02-02-00.mp4', '2022-03-03T03-03-00.mp4'];
vi.spyOn(axios, 'get').mockResolvedValue({ data: videoList });
vi.spyOn(vm.quasar, 'notify');
await vm.getVideoList(expeditionId, timed);
expect(vm.videoList.length).toEqual(videoList.length);
expect(vm.slide).toEqual(videoList.reverse()[0]);
});
it('should if not have video show notify', async () => {
const expeditionId = 1;
const timed = {
min: 1,
max: 2,
};
vi.spyOn(axios, 'get').mockResolvedValue({ data: [] });
vi.spyOn(vm.quasar, 'notify');
await vm.getVideoList(expeditionId, timed);
expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining({ type: 'negative' }));
});
});
});

52
test/vitest/helper.js Normal file
View File

@ -0,0 +1,52 @@
import { installQuasar } from '@quasar/quasar-app-extension-testing-unit-vitest';
import { mount, flushPromises } from '@vue/test-utils';
import { createTestingPinia } from '@pinia/testing';
import { vi } from 'vitest';
import { i18n } from 'src/boot/i18n';
import { Notify, Dialog } from 'quasar';
import axios from 'axios';
installQuasar({
plugins: {
Notify,
Dialog,
},
});
const mockPush = vi.fn();
vi.mock('vue-router', () => ({
useRouter: () => ({
push: mockPush,
currentRoute: { value: 'myCurrentRoute' },
}),
useRoute: () => ({
matched: [],
}),
}));
export function createWrapper(component, options) {
const pinia = createTestingPinia({ createSpy: vi.fn, stubActions: false });
const defaultOptions = {
global: {
plugins: [i18n, pinia],
},
};
const mountOptions = Object.assign({}, defaultOptions);
if (options instanceof Object) {
Object.assign(mountOptions, options);
if (options.global) {
mountOptions.global.plugins = defaultOptions.global.plugins;
}
}
const wrapper = mount(component, mountOptions);
const vm = wrapper.vm;
return { vm, wrapper };
}
export { axios, flushPromises };

View File

@ -0,0 +1 @@
// This file will be run before each test file

View File

@ -1,31 +0,0 @@
/// <reference types="cypress" />
/* eslint-env node */
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
// cypress/plugins/index.js
// const {injectDevServer} = require('@quasar/quasar-app-extension-testing-e2e-cypress/cct-dev-server');
// /**
// * @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);
// }
return config;
};

View File

@ -1,10 +0,0 @@
module.exports = {
extends: [
// Removes 'no-undef' lint errors for Jest global functions (`describe`, `it`, etc),
// add Jest-specific lint rules and Jest plugin
// See https://github.com/jest-community/eslint-plugin-jest#recommended
'plugin:jest/recommended',
// Uncomment following line to apply style rules
// 'plugin:jest/style',
],
};

View File

@ -1 +0,0 @@
coverage/*

View File

@ -1,32 +0,0 @@
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, Dialog } from 'quasar';
import axios from 'axios';
installQuasarPlugin({
plugins: {
Notify,
Dialog,
},
});
export function createWrapper(component, options) {
const mountOptions = {};
if (options instanceof Object) Object.assign(mountOptions, options);
if (mountOptions.global && mountOptions.global.plugins) {
mountOptions.global.plugins.push(i18n);
} else {
if (!mountOptions.global) mountOptions.global = {};
mountOptions.global.plugins = [i18n];
}
const wrapper = mount(component, mountOptions);
const vm = wrapper.vm;
return { vm, wrapper };
}
export { axios, flushPromises };

31
vitest.config.js Normal file
View File

@ -0,0 +1,31 @@
import { defineConfig } from 'vitest/config';
import vue from '@vitejs/plugin-vue';
import { quasar, transformAssetUrls } from '@quasar/vite-plugin';
import jsconfigPaths from 'vite-jsconfig-paths';
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
import path from 'path';
// https://vitejs.dev/config/
export default defineConfig({
test: {
environment: 'happy-dom',
setupFiles: 'test/vitest/setup-file.js',
include: [
// Matches vitest tests in any subfolder of 'src' or into 'test/vitest/__tests__'
// Matches all files with extension 'js', 'jsx', 'ts' and 'tsx'
'test/vitest/__tests__/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}',
],
},
plugins: [
vue({
template: { transformAssetUrls },
}),
quasar({
sassVariables: 'src/quasar-variables.scss',
}),
VueI18nPlugin({
include: path.resolve(__dirname, 'src/i18n/**'),
}),
jsconfigPaths(),
],
});