Merge branch 'dev' into 4834-create-worker-module
gitea/salix-front/pipeline/head This commit looks good Details

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
/src-bex/www
/src-capacitor
/src-cordova
/.quasar
/node_modules
.eslintrc.js
babel.config.js

View File

@ -5,13 +5,13 @@ module.exports = {
root: true,
parserOptions: {
parser: '@babel/eslint-parser',
ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
sourceType: 'module', // Allows for the use of imports
ecmaVersion: '2021', // Allows for the parsing of modern ECMAScript features
},
env: {
node: true,
browser: true,
'vue/setup-compiler-macros': true,
},
// Rules order is important, please avoid shuffling them
@ -22,7 +22,7 @@ module.exports = {
// 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)
@ -52,9 +52,6 @@ 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
@ -64,10 +61,9 @@ module.exports = {
// allow debugger during development only
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
},
overrides: [
{
files: ['**/*.spec.{js,ts}'],
files: ['test/cypress/**/*.spec.{js,ts}'],
extends: [
// Add Cypress-specific lint rules, globals and Cypress plugin
// See https://github.com/cypress-io/eslint-plugin-cypress#rules

9
.gitignore vendored
View File

@ -1,7 +1,6 @@
.DS_Store
.thumbs.db
node_modules
junit.xml
# Quasar core related directories
.quasar
@ -17,10 +16,6 @@ junit.xml
/src-capacitor/www
/src-capacitor/node_modules
# BEX related directories and files
/src-bex/www
/src-bex/js/core
# Log files
npm-debug.log*
yarn-debug.log*
@ -32,3 +27,7 @@ yarn-error.log*
*.ntvs*
*.njsproj
*.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",
"esbenp.prettier-vscode",
"editorconfig.editorconfig",
"Vue.volar",
"vue.volar",
"wayou.vscode-todo-highlight"
],
"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
```bash
yarn
# or
npm install
```
@ -22,20 +20,16 @@ sudo npm install -g @quasar/cli
quasar dev
```
### Lint the files
### Run unit tests
```bash
yarn lint
# or
npm run lint
npm run test:unit
```
### Format the files
### Run e2e tests
```bash
yarn format
# or
npm run format
npm run test:e2e
```
### Build the app for production
@ -43,7 +37,3 @@ npm run format
```bash
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" />
</head>
<body>
<!-- DO NOT touch the following DIV -->
<div id="q-app"></div>
<!-- quasar:entry-point -->
</body>
</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"]
}
},
"exclude": ["dist", ".quasar", "node_modules"],
"vueCompilerOptions": {
"experimentalDisableTemplateSupport": true
}
"exclude": ["dist", ".quasar", "node_modules"]
}

29747
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,63 +1,54 @@
{
"name": "salix-front",
"version": "0.0.1",
"description": "Salix front-end",
"description": "Salix frontend",
"productName": "Salix",
"author": "Verdnatura",
"private": true,
"scripts": {
"lint": "eslint --ext .js,.vue ./",
"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:unit": "jest --reporters=default --watchAll",
"test:unit:ci": "jest --ci --reporters=default --reporters=jest-junit --maxWorkers=2",
"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\""
"test:unit": "vitest",
"test:unit:ci": "vitest run"
},
"dependencies": {
"@quasar/extras": "^1.15.8",
"axios": "^1.2.1",
"core-js": "^3.6.5",
"pinia": "^2.0.28",
"quasar": "^2.11.1",
"validator": "^13.7.0",
"vue": "^3.2.45",
"vue-i18n": "^9.2.2",
"vue-router": "^4.1.6"
"vue-router": "^4.1.6",
"vue-router-mock": "^0.1.9"
},
"devDependencies": {
"@babel/eslint-parser": "^7.13.14",
"@intlify/vue-i18n-loader": "^4.1.0",
"@intlify/unplugin-vue-i18n": "^0.8.1",
"@pinia/testing": "^0.0.14",
"@quasar/app-webpack": "^3.6.2",
"@quasar/quasar-app-extension-testing-e2e-cypress": "^4.2.2",
"@quasar/quasar-app-extension-testing-unit-jest": "^3.0.0-beta.5",
"@quasar/app-vite": "^1.1.3",
"@quasar/quasar-app-extension-testing-unit-vitest": "^0.1.2",
"@vue/test-utils": "^2.0.0",
"autoprefixer": "^10.4.13",
"cypress": "^12.2.0",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-jest": "^27.1.7",
"eslint-plugin-vue": "^8.7.1",
"eslint-webpack-plugin": "^3.2.0",
"jest-junit": "^13.0.0",
"prettier": "^2.5.1"
"eslint-plugin-vue": "^9.8.0",
"postcss": "^8.4.20",
"prettier": "^2.8.1",
"vitest": "^0.26.3"
},
"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": {
"node": ">= 12.22.1",
"node": "^18 || ^16 || ^14.19",
"npm": ">= 6.13.4",
"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
// 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 VueI18nPlugin = require('@intlify/unplugin-vue-i18n/vite')
const path = require('path');
module.exports = configure(function (ctx) {
module.exports = configure(function (/* ctx */) {
return {
// https://v2.quasar.dev/quasar-cli-webpack/supporting-ts
supportTS: false,
eslint: {
// 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,
// app boot file (/src/boot)
// --> boot files are part of "main.js"
// https://v2.quasar.dev/quasar-cli-webpack/boot-files
boot: ['i18n', 'axios', 'pinia'],
// https://v2.quasar.dev/quasar-cli/boot-files
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'],
// https://github.com/quasarframework/quasar/tree/dev/extras
@ -43,58 +49,63 @@ module.exports = configure(function (ctx) {
'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: {
vueRouterMode: 'hash', // available values: 'hash', 'history'
// transpile: false,
// 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'] }]);
target: {
browser: ['es2022', 'edge88', 'firefox78', 'chrome87', 'safari13.1'],
node: 'node18',
},
extendWebpack(cfg) {
cfg.resolve.alias = {
...cfg.resolve.alias, // This adds the existing alias
// Add your own alias like this
composables: path.resolve(__dirname, './src/composables'),
filters: path.resolve(__dirname, './src/filters'),
vueRouterMode: 'hash', // available values: 'hash', 'history'
// vueRouterBase,
// 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: {
server: {
type: 'http',
},
port: 8080,
proxy: {
'/api': {
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: {
config: {
brand: {
primary: 'orange',
config: {
brand: {
primary: 'orange',
},
dark: 'auto',
},
dark: 'auto',
},
lang: 'es',
lang: 'en-GB',
// iconSet: 'material-icons', // Quasar icon set
// lang: 'en-US', // Quasar language pack
@ -130,11 +143,29 @@ module.exports = configure(function (ctx) {
},
// animations: 'all', // --- includes all animations
// https://quasar.dev/options/animations
// https://v2.quasar.dev/options/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: {
// ssrPwaHtmlFilename: 'offline.html', // do NOT use index.html as name!
// will mess up SSR
// extendSSRWebserverConf (esbuildConf) {},
// extendPackageJson (json) {},
pwa: false,
// manualStoreHydration: true,
@ -143,81 +174,42 @@ module.exports = configure(function (ctx) {
prodPort: 3000, // The default port that the production server should use
// (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: [
ctx.prod ? 'compression' : '',
'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: {
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) {
chain.plugin('eslint-webpack-plugin').use(ESLintPlugin, [{ extensions: ['js'] }]);
},
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',
},
],
},
workboxMode: 'generateSW', // or 'injectManifest'
injectPwaMetaTags: true,
swFilename: 'sw.js',
manifestFilename: 'manifest.json',
useCredentialsForManifestTag: false,
// useFilenameHashes: true,
// extendGenerateSWOptions (cfg) {}
// extendInjectManifestOptions (cfg) {},
// extendManifestJson (json) {}
// extendPWACustomSWConf (esbuildConf) {}
},
// 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: {
// 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: {
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: {
// extendElectronMainConf (esbuildConf)
// extendElectronPreloadConf (esbuildConf)
inspectPort: 5858,
bundler: 'packager', // 'packager' or 'builder'
packager: {
@ -234,18 +226,16 @@ module.exports = configure(function (ctx) {
builder: {
// 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) {
chain.plugin('eslint-webpack-plugin').use(ESLintPlugin, [{ extensions: ['js'] }]);
},
chainWebpackPreload(chain) {
chain.plugin('eslint-webpack-plugin').use(ESLintPlugin, [{ extensions: ['js'] }]);
},
// extendBexScriptsConf (esbuildConf) {}
// extendBexManifestJson (json) {}
},
};
});

View File

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

View File

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

View File

@ -1,21 +1,24 @@
import { boot } from 'quasar/wrappers';
import axios from 'axios';
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(
function (context) {
const token = getToken();
axios.interceptors.request.use(
function (context) {
const token = getToken();
if (token.length && context.headers) {
context.headers.Authorization = token;
if (token.length && context.headers) {
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({
locale: 'en',
fallbackLocale: 'en',
globalInjection: true,
messages,
missingWarn: false,
legacy: false,
missingWarn: false
});
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>
<template>
<q-menu
anchor="bottom left"
class="row q-pa-md q-col-gutter-lg"
max-width="350px"
max-height="400px"
v-if="pinnedModules.length"
>
<div v-for="item of pinnedModules" :key="item.title" class="row no-wrap q-pa-xs flex-item">
<q-btn
align="evenly"
padding="16px"
flat
stack
size="lg"
:icon="item.icon"
color="primary"
class="col-4 button"
:to="{ name: item.name }"
>
<div class="text-center text-primary button-text">
{{ t(item.title) }}
</div>
</q-btn>
</div>
<q-menu anchor="bottom left" class="row q-pa-md q-col-gutter-lg" max-width="350px" max-height="400px">
<template v-if="pinnedModules.length">
<div v-for="item of pinnedModules" :key="item.title" class="row no-wrap q-pa-xs flex-item">
<q-btn
align="evenly"
padding="16px"
flat
stack
size="lg"
:icon="item.icon"
color="primary"
class="col-4 button"
:to="{ name: item.name }"
>
<div class="text-center text-primary button-text">
{{ t(item.title) }}
</div>
</q-btn>
</div>
</template>
<template v-else>
<div class="row no-wrap q-pa-xs flex-item text-center text-grey-5" style="min-width: 200px">
{{ t('globals.noPinnedModules') }}
</div>
</template>
</q-menu>
</template>

View File

@ -22,9 +22,16 @@ const userLocale = computed({
if (value === 'en') value = 'en-GB';
import(`quasar/lang/${value}`).then((language) => {
Quasar.lang.set(language.default);
});
// FIXME: Dynamic imports from absolute paths are not compatible with vite:
// 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
@import './icons.scss';
.body--dark {
.q-card--dark {
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;
}
a {
text-decoration: none;
}
.link {

View File

@ -23,8 +23,11 @@ $negative: #c10015;
$info: #31ccec;
$warning: #f2c037;
$color-spacer-light: rgba(255, 255, 255, .12);
$color-spacer:rgba(255, 255, 255, .3);
$color-spacer-light: rgba(255, 255, 255, 0.12);
$color-spacer: rgba(255, 255, 255, 0.3);
$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;

View File

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

View File

@ -30,6 +30,7 @@ export default {
rowAdded: 'Fila añadida',
rowRemoved: 'Fila eliminada',
pleaseWait: 'Por favor, espera...',
noPinnedModules: 'No has fijado ningún módulo',
},
moduleIndex: {
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';
import(`quasar/lang/${value}`).then((language) => {
Quasar.lang.set(language.default);
});
// FIXME: Dynamic imports from absolute paths are not compatible with vite:
// 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);
async function onSubmit() {
const { data } = await axios.post('Accounts/login', {
user: username.value,
password: password.value,
});
try {
const { data } = await axios.post('Accounts/login', {
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({
message: t('login.loginSuccess'),
type: 'positive',
});
quasar.notify({
message: t('login.loginSuccess'),
type: 'positive',
});
const currentRoute = router.currentRoute.value;
if (currentRoute.query && currentRoute.query.redirect) {
router.push(currentRoute.query.redirect);
} else {
router.push({ name: 'Dashboard' });
const currentRoute = router.currentRoute.value;
if (currentRoute.query && currentRoute.query.redirect) {
router.push(currentRoute.query.redirect);
} else {
router.push({ name: 'Dashboard' });
}
} catch (error) {
//
}
}
</script>
@ -70,7 +81,15 @@ async function onSubmit() {
<q-page id="login">
<q-page-sticky position="top-right">
<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-list dense>
<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!
// quasar.conf.js -> build -> vueRouterMode
// 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) => {

View File

@ -30,7 +30,7 @@ const routes = [
worker,
invoiceOut,
{
path: '/:pathMatch(.*)*',
path: '/:catchAll(.*)*',
name: 'NotFound',
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', () => {
beforeEach(() => {
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', () => {
@ -36,7 +38,7 @@ describe('Login', () => {
cy.get('#logout').click();
cy.window().its('localStorage').invoke('getItem', 'token').should('not.exist');
cy.url().should('contain', '/login');
})
});
it(`should get redirected to dashboard since employee can't create tickets`, () => {
cy.visit('/#/ticket/create', { failOnStatusCode: false });
@ -45,7 +47,7 @@ describe('Login', () => {
cy.get('input[aria-label="Password"]').type('nightmare');
cy.get('button[type="submit"]').click();
cy.url().should('contain', '/dashboard');
})
});
// 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`, () => {
@ -56,4 +58,4 @@ describe('Login', () => {
// cy.get('button[type="submit"]').click();
// cy.url().should('contain', '/#/ticket/create');
// })
});
});

View File

@ -1,8 +1,9 @@
describe('TicketBoxing', () => {
/// <reference types="cypress" />
xdescribe('TicketBoxing', () => {
beforeEach(() => {
const ticketId = 1;
cy.viewport(1280, 720)
cy.login('developer')
cy.viewport(1280, 720);
cy.login('developer');
cy.visit(`/#/ticket/${ticketId}/boxing`);
});
@ -23,16 +24,11 @@ describe('TicketBoxing', () => {
method: 'GET',
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');
cy.get('.q-list > :nth-child(3)').click();
cy.get('.q-list > :nth-child(1)').should('be.visible');
cy.get('.q-list > :nth-child(2)').should('be.visible');
});
});

View File

@ -26,7 +26,7 @@
// DO NOT REMOVE
// 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) => {
cy.visit('/#/login');
cy.request({
@ -34,10 +34,10 @@ Cypress.Commands.add('login', (user) => {
url: '/api/accounts/login',
body: {
user: user,
password: 'nightmare'
}
}).then(response => {
password: 'nightmare',
},
}).then((response) => {
window.localStorage.setItem('token', response.body.token);
})
})
registerCommands();
});
});
// registerCommands();

View File

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

View File

@ -1,21 +1,13 @@
import { jest, describe, expect, it, beforeAll } from '@jest/globals';
import { createWrapper } from 'app/tests/jest/jestHelpers';
import App from '../App.vue';
import { vi, describe, expect, it, beforeAll } from 'vitest';
import { createWrapper } from 'app/test/vitest/helper';
import App from 'src/App.vue';
import { useSession } from 'src/composables/useSession';
const mockPush = jest.fn();
const mockLoggedIn = jest.fn();
const mockDestroy = jest.fn();
const mockLoggedIn = vi.fn();
const mockDestroy = vi.fn();
const session = useSession();
jest.mock('vue-router', () => ({
useRouter: () => ({
push: mockPush,
currentRoute: { value: 'myCurrentRoute' },
}),
}));
jest.mock('src/composables/useSession', () => ({
vi.mock('src/composables/useSession', () => ({
useSession: () => ({
isLoggedIn: mockLoggedIn,
destroy: mockDestroy,
@ -24,6 +16,7 @@ jest.mock('src/composables/useSession', () => ({
describe('App', () => {
let vm;
beforeAll(() => {
const options = {
global: {
@ -34,7 +27,7 @@ describe('App', () => {
});
it('should return a login error message', async () => {
jest.spyOn(vm.quasar, 'notify');
vi.spyOn(vm.quasar, 'notify');
session.isLoggedIn.mockReturnValue(false);
@ -54,7 +47,7 @@ describe('App', () => {
});
it('should return an unauthorized error message', async () => {
jest.spyOn(vm.quasar, 'notify');
vi.spyOn(vm.quasar, 'notify');
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 { createWrapper, axios } from 'app/tests/jest/jestHelpers';
import Paginate from '../PaginateData.vue';
const mockPush = jest.fn();
jest.mock('vue-router', () => ({
useRouter: () => ({
push: mockPush,
currentRoute: { value: 'myCurrentRoute' }
}),
}));
import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest';
import { createWrapper, axios } from 'app/test/vitest/helper';
import Paginate from 'components/PaginateData.vue';
describe('Paginate', () => {
const expectedUrl = '/api/customers';
@ -19,17 +10,17 @@ describe('Paginate', () => {
attrs: {
url: expectedUrl,
sortBy: 'id DESC',
rowsPerPage: 3
}
rowsPerPage: 3,
},
};
vm = createWrapper(Paginate, options).vm;
jest.spyOn(axios, 'get').mockResolvedValue({
vi.spyOn(axios, 'get').mockResolvedValue({
data: [
{ id: 1, name: 'Tony Stark' },
{ id: 2, name: 'Jessica Jones' },
{ id: 3, name: 'Bruce Wayne' },
]
],
});
});
@ -37,7 +28,7 @@ describe('Paginate', () => {
vm.rows = [];
vm.pagination.page = 1;
vm.hasMoreData = true;
})
});
describe('paginate()', () => {
it('should call to the paginate() method and set the data on the rows property', async () => {
@ -46,9 +37,9 @@ describe('Paginate', () => {
filter: {
order: 'id DESC',
limit: 3,
skip: 0
}
}
skip: 0,
},
},
};
await vm.paginate();
@ -63,9 +54,9 @@ describe('Paginate', () => {
filter: {
order: 'id DESC',
limit: 3,
skip: 0
}
}
skip: 0,
},
},
};
await vm.paginate();
@ -78,9 +69,9 @@ describe('Paginate', () => {
filter: {
order: 'id DESC',
limit: 3,
skip: 3
}
}
skip: 3,
},
},
};
vm.pagination.page = 2;
@ -95,7 +86,7 @@ describe('Paginate', () => {
describe('onLoad()', () => {
it('should call to the done() callback and not increment the pagination', async () => {
const index = 1;
const done = jest.fn();
const done = vi.fn();
await vm.onLoad(index, done);
@ -113,7 +104,7 @@ describe('Paginate', () => {
expect(vm.pagination.page).toEqual(1);
const index = 1;
const done = jest.fn();
const done = vi.fn();
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 () => {
jest.spyOn(axios, 'get').mockResolvedValue({
vi.spyOn(axios, 'get').mockResolvedValue({
data: [
{ id: 1, name: 'Tony Stark' },
{ id: 2, name: 'Jessica Jones' }
]
{ id: 2, name: 'Jessica Jones' },
],
});
vm.rows = [
@ -138,7 +129,7 @@ describe('Paginate', () => {
expect(vm.pagination.page).toEqual(1);
const index = 1;
const done = jest.fn();
const done = vi.fn();
vm.hasMoreData = false;

View File

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

View File

@ -1,7 +1,7 @@
import { describe, expect, it, jest } from '@jest/globals';
import { useSession } from '../useSession';
import { useState } from '../useState';
import { axios } from 'app/tests/jest/jestHelpers';
import { vi, describe, expect, it } from 'vitest';
import { axios } from 'app/test/vitest/helper';
import { useSession } from 'composables/useSession';
import { useState } from 'composables/useState';
const session = useSession();
const state = useState();
@ -9,7 +9,7 @@ const state = useState();
describe('session', () => {
describe('getToken / setToken', () => {
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();
@ -17,11 +17,11 @@ describe('session', () => {
});
it('should return the token stored in local or session storage', async () => {
const expectedToken = 'myToken'
const expectedToken = 'myToken';
const data = {
token: expectedToken,
keepLogin: false
}
keepLogin: false,
};
session.setToken(data);
const token = session.getToken();
@ -38,23 +38,22 @@ describe('session', () => {
nickname: 'Black Panther',
lang: 'en',
darkMode: false,
}
};
const expectedUser = {
id: 0,
name: '',
nickname: '',
lang: '',
darkMode: null,
}
};
let user = state.getUser();
localStorage.setItem('token', 'tokenToBeGone');
state.setUser(previousUser)
state.setUser(previousUser);
expect(localStorage.getItem('token')).toEqual('tokenToBeGone');
expect(user.value).toEqual(previousUser);
session.destroy();
user = state.getUser();
@ -71,29 +70,29 @@ describe('session', () => {
lang: 'en',
userConfig: {
darkMode: false,
}
}
},
};
const rolesData = [
{
role: {
name: 'salesPerson'
}
name: 'salesPerson',
},
},
{
role: {
name: 'admin'
}
}
name: 'admin',
},
},
];
it('should fetch the user roles and then set token in the sessionStorage', async () => {
const expectedRoles = ['salesPerson', 'admin']
jest.spyOn(axios, 'get').mockResolvedValue({
data: { roles: rolesData, user: expectedUser }
const expectedRoles = ['salesPerson', 'admin'];
vi.spyOn(axios, 'get').mockResolvedValue({
data: { roles: rolesData, user: expectedUser },
});
const expectedToken = 'mySessionToken'
const keepLogin = false
const expectedToken = 'mySessionToken';
const keepLogin = false;
await session.login(expectedToken, keepLogin);
@ -105,17 +104,17 @@ describe('session', () => {
expect(localToken).toBeNull();
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 () => {
const expectedRoles = ['salesPerson', 'admin']
jest.spyOn(axios, 'get').mockResolvedValue({
data: { roles: rolesData, user: expectedUser }
const expectedRoles = ['salesPerson', 'admin'];
vi.spyOn(axios, 'get').mockResolvedValue({
data: { roles: rolesData, user: expectedUser },
});
const expectedToken = 'myLocalToken'
const keepLogin = true
const expectedToken = 'myLocalToken';
const keepLogin = true;
await session.login(expectedToken, keepLogin);
@ -127,7 +126,7 @@ describe('session', () => {
expect(localToken).toEqual(expectedToken);
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 { createWrapper, axios } from 'app/tests/jest/jestHelpers';
import Login from '../LoginMain.vue';
const mockPush = jest.fn();
jest.mock('vue-router', () => ({
useRouter: () => ({
push: mockPush,
currentRoute: { value: 'myCurrentRoute' }
}),
}));
import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest';
import { createWrapper, axios } from 'app/test/vitest/helper';
import Login from 'pages/Login/LoginMain.vue';
describe('Login', () => {
let vm;
@ -18,7 +9,7 @@ describe('Login', () => {
});
afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
it('should successfully set the token into session', async () => {
@ -29,26 +20,24 @@ describe('Login', () => {
lang: 'en',
userConfig: {
darkMode: false,
}
}
jest.spyOn(axios, 'post').mockResolvedValue({ data: { token: 'token' } });
jest.spyOn(axios, 'get').mockResolvedValue({ data: { roles: [], user: expectedUser } });
jest.spyOn(vm.quasar, 'notify');
},
};
vi.spyOn(axios, 'post').mockResolvedValue({ data: { token: 'token' } });
vi.spyOn(axios, 'get').mockResolvedValue({ data: { roles: [], user: expectedUser } });
vi.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' }
));
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').mockReturnValue({ data: null });
jest.spyOn(vm.quasar, 'notify');
vi.spyOn(axios, 'post').mockReturnValue({ data: null });
vi.spyOn(vm.quasar, 'notify');
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(),
],
});