Merge pull request 'quasar-cli' (#4) from quasar-cli into dev

Reviewed-on: #4
This commit is contained in:
Joan Sanchez 2022-03-14 09:39:34 +00:00
commit f09925493b
104 changed files with 19417 additions and 21816 deletions

23
.babelrc Normal file
View File

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

View File

@ -1,3 +0,0 @@
> 1%
last 2 versions
not dead

9
.editorconfig Normal file
View File

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

2
.env
View File

@ -1,2 +0,0 @@
VUE_APP_I18N_LOCALE=es
VUE_APP_I18N_FALLBACK_LOCALE=en

9
.eslintignore Normal file
View File

@ -0,0 +1,9 @@
/dist
/src-bex/www
/src-capacitor
/src-cordova
/.quasar
/node_modules
.eslintrc.js
babel.config.js
/src-ssr

View File

@ -1,28 +1,93 @@
const { resolve } = require('path');
module.exports = {
// https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy
// This option interrupts the configuration hierarchy at this file
// Remove this if you have an higher level ESLint config file (it usually happens into a monorepos)
root: true,
env: {
node: true,
},
extends: [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/typescript/recommended',
'@vue/prettier',
'@vue/prettier/@typescript-eslint',
],
// https://eslint.vuejs.org/user-guide/#how-to-use-a-custom-parser
// Must use parserOptions instead of "parser" to allow vue-eslint-parser to keep working
// `parser: 'vue-eslint-parser'` is already included with any 'plugin:vue/**' config and should be omitted
parserOptions: {
ecmaVersion: 2020,
// https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/parser#configuration
// https://github.com/TypeStrong/fork-ts-checker-webpack-plugin#eslint
// Needed to make the parser take into account 'vue' files
extraFileExtensions: ['.vue'],
parser: '@typescript-eslint/parser',
project: resolve(__dirname, './tsconfig.json'),
tsconfigRootDir: __dirname,
ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
sourceType: 'module', // Allows for the use of imports
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
env: {
browser: true,
},
overrides: [
{
files: ['**/__tests__/*.{j,t}s?(x)', '**/tests/unit/**/*.spec.{j,t}s?(x)'],
env: {
jest: true,
},
},
// Rules order is important, please avoid shuffling them
extends: [
// Base ESLint recommended rules
'eslint:recommended',
// https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#usage
// ESLint typescript rules
'plugin:@typescript-eslint/recommended',
// consider disabling this class of rules if linting takes too long
'plugin:@typescript-eslint/recommended-requiring-type-checking',
// 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-strongly-recommended', // Priority B: Strongly Recommended (Improving Readability)
// 'plugin:vue/vue3-recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead)
// https://github.com/prettier/eslint-config-prettier#installation
// usage with Prettier, provided by 'eslint-config-prettier'.
'prettier',
],
plugins: [
// required to apply rules which need type information
'@typescript-eslint',
// https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-files
// required to lint *.vue files
'vue',
// https://github.com/typescript-eslint/typescript-eslint/issues/389#issuecomment-509292674
// Prettier has not been included as plugin to avoid performance impact
// add it as an extension for your IDE
],
globals: {
ga: 'readonly', // Google Analytics
cordova: 'readonly',
__statics: 'readonly',
__QUASAR_SSR__: 'readonly',
__QUASAR_SSR_SERVER__: 'readonly',
__QUASAR_SSR_CLIENT__: 'readonly',
__QUASAR_SSR_PWA__: 'readonly',
process: 'readonly',
Capacitor: 'readonly',
chrome: 'readonly',
},
// add your custom rules here
rules: {
'prefer-promise-reject-errors': 'off',
// TypeScript
quotes: ['warn', 'single', { avoidEscape: true }],
'@typescript-eslint/explicit-function-return-type': 'off',
"@typescript-eslint/unbound-method": "off",
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-floating-promises': 'off',
// allow debugger during development only
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
},
};

22
.gitignore vendored
View File

@ -1,20 +1,29 @@
.DS_Store
.thumbs.db
node_modules
# Quasar core related directories
.quasar
/dist
/tests/e2e/videos/
/tests/e2e/screenshots/
# Cordova related directories and files
/src-cordova/node_modules
/src-cordova/platforms
/src-cordova/plugins
/src-cordova/www
# Capacitor related directories and files
/src-capacitor/www
/src-capacitor/node_modules
# local env files
.env.local
.env.*.local
# BEX related directories and files
/src-bex/www
/src-bex/js/core
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
@ -22,4 +31,3 @@ pnpm-debug.log*
*.ntvs*
*.njsproj
*.sln
*.sw?

8
.postcssrc.js Normal file
View File

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

7
.prettierrc Normal file
View File

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

View File

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

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

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

47
.vscode/settings.json vendored
View File

@ -1,9 +1,42 @@
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
},
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": true,
"files.eol": "\n",
"editor.tabSize": 4,
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": true,
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": [
"source.fixAll.eslint"
],
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"vue"
],
"typescript.tsdk": "node_modules/typescript/lib",
"[typescript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
},
"[jsonc]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"[json]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"[javascript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
},
"[vue]": {
"editor.defaultFormatter": "johnsoncodehk.volar"
},
"[html]": {
"editor.defaultFormatter": "vscode.html-language-features"
},
"json.schemas": [
{
"fileMatch": [
"cypress.json"
],
"url": "https://on.cypress.io/cypress.schema.json"
}
]
}

View File

@ -1,34 +1,37 @@
# salix-front
# Salix (salix-front)
## Project setup
```
Salix front-end
## Install the dependencies
```bash
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
### Start the app in development mode (hot-code reloading, error reporting, etc.)
```bash
quasar dev
```
### Compiles and minifies for production
```
npm run build
```
### Lint the files
### Run your unit tests
```
npm run test:unit
```
### Run your end-to-end tests
```
npm run test:e2e
```
### Lints and fixes files
```
```bash
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
### Format the files
```bash
npm run format
```
### Build the app for production
```bash
quasar build
```
### Customize the configuration
See [Configuring quasar.conf.js](https://quasar.dev/quasar-cli/quasar-conf-js).

View File

@ -1,3 +1,18 @@
/* 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: ['@vue/cli-plugin-babel/preset'],
presets: ['@quasar/babel-preset-app'],
extends: extend,
};

View File

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

82
jest.config.js Normal file → Executable file
View File

@ -1,6 +1,82 @@
const esModules = ['quasar', 'quasar/lang', 'lodash-es'].join('|');
/* eslint-env node */
module.exports = {
preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel',
transform: {
'^.+\\.vue$': 'vue-jest',
globals: {
__DEV__: true,
// TODO: Remove if resolved natively
// See https://github.com/vuejs/vue-jest/issues/175
'vue-jest': {
pug: { doctype: 'html' },
},
// Remove if using `const enums`
// See https://huafu.github.io/ts-jest/user/config/isolatedModules#example
'ts-jest': {
isolatedModules: true,
},
},
// noStackTrace: true,
// bail: true,
// cache: false,
// verbose: true,
// watch: true,
collectCoverage: false,
coverageDirectory: '<rootDir>/test/jest/coverage',
collectCoverageFrom: [
'<rootDir>/src/**/*.vue',
'<rootDir>/src/**/*.js',
'<rootDir>/src/**/*.ts',
'<rootDir>/src/**/*.jsx',
'<rootDir>/src/**/*.tsx',
],
coveragePathIgnorePatterns: ['/node_modules/', '.d.ts$'],
coverageThreshold: {
global: {
// branches: 50,
// functions: 50,
// lines: 50,
// statements: 50
},
},
testMatch: [
// Matches tests in any subfolder of 'src' or into 'test/jest/__tests__'
// Matches all files with extension 'js', 'jsx', 'ts' and 'tsx'
'<rootDir>/test/jest/__tests__/**/*.(spec|test).+(ts|js)?(x)',
//'<rootDir>/src/**/*.jest.(spec|test).+(ts|js)?(x)',
],
// Extension-less imports of components are resolved to .ts files by TS,
// grating correct type-checking in test files.
// Being 'vue' the first moduleFileExtension option, the very same imports
// will be resolved to .vue files by Jest, if both .vue and .ts files are
// in the same folder.
// This guarantee a great dev experience both for testing and type-checking.
// See https://github.com/vuejs/vue-jest/issues/188#issuecomment-620750728
moduleFileExtensions: ['vue', 'js', 'jsx', 'json', 'ts', 'tsx'],
moduleNameMapper: {
'^quasar$': 'quasar/dist/quasar.esm.prod.js',
'^~/(.*)$': '<rootDir>/$1',
'^src/(.*)$': '<rootDir>/src/$1',
'^app/(.*)$': '<rootDir>/$1',
'^components/(.*)$': '<rootDir>/src/components/$1',
'^composables/(.*)$': '<rootDir>/src/composables/$1',
'^layouts/(.*)$': '<rootDir>/src/layouts/$1',
'^pages/(.*)$': '<rootDir>/src/pages/$1',
'^assets/(.*)$': '<rootDir>/src/assets/$1',
'^boot/(.*)$': '<rootDir>/src/boot/$1',
'.*css$': '@quasar/quasar-app-extension-testing-unit-jest/stub.css',
},
transform: {
// See https://jestjs.io/docs/en/configuration.html#transformignorepatterns-array-string
[`^(${esModules}).+\\.js$`]: 'babel-jest',
'^.+\\.(ts|js|html)$': 'ts-jest',
// vue-jest uses find-babel-file, which searches by this order:
// (async) .babelrc, .babelrc.js, package.json, babel.config.js
// (sync) .babelrc, .babelrc.js, babel.config.js, package.json
// https://github.com/tleunen/find-babel-config/issues/33
'.*\\.vue$': 'vue-jest',
'.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$':
'jest-transform-stub',
},
transformIgnorePatterns: [`node_modules/(?!(${esModules}))`],
snapshotSerializers: ['<rootDir>/node_modules/jest-serializer-vue'],
};

38210
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,49 +1,60 @@
{
"name": "salix-front",
"version": "0.1.0",
"version": "0.0.1",
"description": "Salix front-end",
"productName": "Salix",
"author": "Salix",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test:unit": "vue-cli-service test:unit",
"test:e2e": "vue-cli-service test:e2e",
"lint": "vue-cli-service lint",
"i18n:report": "vue-cli-service i18n:report --src \"./src/**/*.?(js|vue)\" --locales \"./src/locales/**/*.json\""
"lint": "eslint --ext .js,.ts,.vue ./",
"format": "prettier --write \"**/*.{js,ts,vue,scss,html,md,json}\" --ignore-path .gitignore",
"test": "echo \"See package.json => scripts for available tests.\" && exit 0",
"test:unit:coverage": "jest --coverage",
"test:unit": "jest --watchAll",
"test:e2e": "cross-env E2E_TEST=true start-test \"quasar dev\" http-get://localhost:8080 \"cypress open --config-file cypress.json\"",
"test:e2e:ci": "cross-env E2E_TEST=true start-test \"quasar dev\" http-get://localhost:8080 \"cypress run\"",
"serve:test:coverage": "quasar serve test/jest/coverage/lcov-report/ --port 8788",
"concurrently:dev:jest": "concurrently \"quasar dev\" \"jest --watch\""
},
"dependencies": {
"@quasar/extras": "^1.0.0",
"axios": "^0.26.0",
"axios": "^0.21.1",
"core-js": "^3.6.5",
"quasar": "^2.0.0",
"vue": "^3.0.0",
"vue-i18n": "^9.1.0",
"vue-router": "^4.0.0-0"
"vue-i18n": "^9.0.0",
"vue-router": "^4.0.0"
},
"devDependencies": {
"@intlify/vue-i18n-loader": "^3.0.0",
"@types/jest": "^24.0.19",
"@typescript-eslint/eslint-plugin": "^4.18.0",
"@typescript-eslint/parser": "^4.18.0",
"@vue/cli-plugin-babel": "~4.5.15",
"@vue/cli-plugin-e2e-cypress": "~4.5.15",
"@vue/cli-plugin-eslint": "~4.5.15",
"@vue/cli-plugin-router": "~4.5.15",
"@vue/cli-plugin-typescript": "~4.5.15",
"@vue/cli-plugin-unit-jest": "~4.5.15",
"@vue/cli-service": "~4.5.15",
"@vue/compiler-sfc": "^3.0.0",
"@vue/eslint-config-prettier": "^6.0.0",
"@vue/eslint-config-typescript": "^7.0.0",
"@vue/test-utils": "^2.0.0-0",
"eslint": "^6.7.2",
"eslint-plugin-prettier": "^3.3.1",
"@babel/eslint-parser": "^7.13.14",
"@intlify/vue-i18n-loader": "^4.1.0",
"@quasar/app": "^3.0.0",
"@quasar/quasar-app-extension-testing-e2e-cypress": "^4.0.1",
"@quasar/quasar-app-extension-testing-unit-jest": "^3.0.0-alpha.8",
"@types/node": "^12.20.21",
"@typescript-eslint/eslint-plugin": "^5.10.0",
"@typescript-eslint/parser": "^5.10.0",
"eslint": "^7.14.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-jest": "^25.2.2",
"eslint-plugin-vue": "^7.0.0",
"prettier": "^2.2.1",
"sass": "1.32.12",
"sass-loader": "^10.1.0",
"typescript": "~4.1.5",
"vue-cli-plugin-i18n": "~2.3.1",
"vue-cli-plugin-quasar": "~4.0.4",
"vue-jest": "^5.0.0-0"
"prettier": "^2.5.1",
"eslint-plugin-cypress": "^2.11.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",
"npm": ">= 6.13.4",
"yarn": ">= 1.21.1"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 859 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -1,23 +0,0 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

233
quasar.conf.js Normal file
View File

@ -0,0 +1,233 @@
/*
* This file runs in a Node context (it's NOT transpiled by Babel), so use only
* the ES6 features that are supported by your Node version. https://node.green/
*/
// Configuration for your app
// https://quasar.dev/quasar-cli/quasar-conf-js
/* eslint-env node */
/* eslint-disable @typescript-eslint/no-var-requires */
const { configure } = require('quasar/wrappers');
module.exports = configure(function (ctx) {
return {
// https://quasar.dev/quasar-cli/supporting-ts
supportTS: {
tsCheckerConfig: {
eslint: {
enabled: true,
files: './src/**/*.{ts,tsx,js,jsx,vue}',
},
},
},
// https://quasar.dev/quasar-cli/prefetch-feature
// preFetch: true,
// app boot file (/src/boot)
// --> boot files are part of "main.js"
// https://quasar.dev/quasar-cli/boot-files
boot: ['i18n', 'axios'],
// https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-css
css: ['app.scss'],
// https://github.com/quasarframework/quasar/tree/dev/extras
extras: [
// 'ionicons-v4',
// 'mdi-v5',
// 'fontawesome-v5',
// 'eva-icons',
// 'themify',
// 'line-awesome',
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
'roboto-font', // optional, you are not bound to it
'material-icons', // optional, you are not bound to it
],
// Full list of options: https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-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://quasar.dev/quasar-cli/handling-webpack
// "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain
chainWebpack(/* chain */) {
//
},
},
// Full list of options: https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-devServer
devServer: {
server: {
type: 'http',
},
port: 8080,
open: true, // opens browser window automatically
proxy: {
'/api': {
target: 'http://localhost:3000',
logLevel: 'debug',
changeOrigin: true,
secure: false,
},
},
},
// https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-framework
framework: {
config: {},
// iconSet: 'material-icons', // Quasar icon set
// lang: 'en-US', // Quasar language pack
// For special cases outside of where the auto-import strategy can have an impact
// (like functional components as one of the examples),
// you can manually specify Quasar components/directives to be available everywhere:
//
// components: [],
// directives: [],
// Quasar plugins
plugins: ['Notify'],
},
// animations: 'all', // --- includes all animations
// https://quasar.dev/options/animations
animations: [],
// https://quasar.dev/quasar-cli/developing-ssr/configuring-ssr
ssr: {
pwa: false,
// manualStoreHydration: true,
// manualPostHydrationTrigger: true,
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 */) {
//
},
middlewares: [
ctx.prod ? 'compression' : '',
'render', // keep this as last one
],
},
// https://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 */) {
//
},
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://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://quasar.dev/quasar-cli/developing-capacitor-apps/configuring-capacitor
capacitor: {
hideSplashscreen: true,
},
// Full list of options: https://quasar.dev/quasar-cli/developing-electron-apps/configuring-electron
electron: {
bundler: 'packager', // 'packager' or 'builder'
packager: {
// https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options
// OS X / Mac App Store
// appBundleId: '',
// appCategoryType: '',
// osxSign: '',
// protocol: 'myapp://path',
// Windows only
// win32metadata: { ... }
},
builder: {
// https://www.electron.build/configuration/configuration
appId: 'salix-front',
},
// "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain
chainWebpack(/* chain */) {
// do something with the Electron main process Webpack cfg
// extendWebpackMain also available besides this chainWebpackMain
},
// "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain
chainWebpackPreload(/* chain */) {
// do something with the Electron main process Webpack cfg
// extendWebpackPreload also available besides this chainWebpackPreload
},
},
};
});

14
quasar.extensions.json Normal file
View File

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

11
quasar.testing.json Normal file
View File

@ -0,0 +1,11 @@
{
"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"
}
}

View File

@ -1,7 +1,3 @@
<template>
<router-view></router-view>
</template>
<script lang="ts" setup>
import { useQuasar } from 'quasar';
const quasar = useQuasar();
@ -17,6 +13,10 @@ quasar.iconMapFn = (iconName) => {
};
</script>
<template>
<router-view />
</template>
<style lang="scss">
.body--light {
background-color: #eee;

50
src/boot/axios.ts Normal file
View File

@ -0,0 +1,50 @@
import { boot } from 'quasar/wrappers';
import axios, { AxiosInstance } from 'axios';
import { useSession } from 'src/composables/useSession';
const { getToken } = useSession();
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$axios: AxiosInstance;
}
}
// Be careful when using SSR for cross-request state pollution
// due to creating a Singleton instance here;
// If any client changes this (global) instance, it might be a
// good idea to move this instance creation inside of the
// "export default () => {}" function below (which runs individually
// for each client)
const api = axios.create({ baseURL: 'https://api.example.com' });
axios.interceptors.request.use(
function (context) {
const token = getToken();
if (token.length && context.headers) {
context.headers.Authorization = token;
}
return context;
},
function (error) {
return Promise.reject(error);
}
);
export default boot(({ app }) => {
// for use inside Vue files (Options API) through this.$axios and this.$api
app.config.globalProperties.$axios = axios;
// ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form)
// so you won't necessarily have to import axios in each vue file
app.config.globalProperties.$api = api;
// ^ ^ ^ this will allow you to use this.$api (for Vue Options API form)
// so you can easily perform requests against your app's API
});
export { api };

16
src/boot/i18n.ts Normal file
View File

@ -0,0 +1,16 @@
import { boot } from 'quasar/wrappers';
import { createI18n } from 'vue-i18n';
import messages from 'src/i18n';
const i18n = createI18n({
locale: 'en',
messages,
});
export default boot(({ app }) => {
// Set i18n instance on app
app.use(i18n);
});
export { i18n };

View File

@ -4,10 +4,12 @@
<q-btn flat @click="$emit('on-toggle-drawer')" round dense icon="menu" />
<router-link to="/">
<q-btn flat round class="q-ml-xs" v-if="$q.screen.gt.xs">
<q-avatar square size="md"><img src="@/assets/logo_icon.svg" alt="Logo" /></q-avatar>
<q-avatar square size="md">
<q-img src="~/assets/logo_icon.svg" alt="Logo" />
</q-avatar>
</q-btn>
</router-link>
<q-toolbar-title shrink class="text-weight-bold"> Salix </q-toolbar-title>
<q-toolbar-title shrink class="text-weight-bold">Salix</q-toolbar-title>
<q-space></q-space>
<div class="q-pl-sm q-gutter-sm row items-center no-wrap">
<q-btn v-if="$q.screen.gt.xs" dense flat size="md" icon="add">
@ -41,9 +43,9 @@
</template>
<script lang="ts" setup>
import { useState } from '@/core/composables/useState';
import { useSession } from '@/core/composables/useSession';
import UserPanel from '@/components/UserPanel.vue';
import { useState } from 'src/composables/useState';
import { useSession } from 'src/composables/useSession';
import UserPanel from 'src/components/UserPanel.vue';
const session = useSession();
const state = useState();

View File

@ -0,0 +1,19 @@
<template>
<q-btn
data-cy="button"
label="test emit"
color="positive"
rounded
icon="edit"
@click="$emit('test')"
/>
</template>
<script>
import { defineComponent } from 'vue';
export default defineComponent({
name: 'QuasarButton',
emits: ['test'],
});
</script>

View File

@ -0,0 +1,71 @@
<template>
<!-- notice dialogRef here -->
<q-dialog ref="dialogRef" @hide="onDialogHide" data-cy="dialog">
<q-card class="q-dialog-plugin">
<q-card-section>{{ message }}</q-card-section>
<!-- buttons example -->
<q-card-actions align="right">
<q-btn
data-cy="ok-button"
color="primary"
label="OK"
@click="onOKClick"
/>
<q-btn color="primary" label="Cancel" @click="onCancelClick" />
</q-card-actions>
</q-card>
</q-dialog>
</template>
<script>
import { useDialogPluginComponent } from 'quasar';
import { defineComponent } from 'vue';
export default defineComponent({
name: 'QuasarDialog',
props: {
message: {
type: String,
required: true,
},
},
// REQUIRED; need to specify some events that your
// component will emit through useDialogPluginComponent()
emits: useDialogPluginComponent.emits,
setup() {
// REQUIRED; must be called inside of setup()
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
useDialogPluginComponent();
// dialogRef - Vue ref to be applied to QDialog
// onDialogHide - Function to be used as handler for @hide on QDialog
// onDialogOK - Function to call to settle dialog with "ok" outcome
// example: onDialogOK() - no payload
// example: onDialogOK({ /*.../* }) - with payload
// onDialogCancel - Function to call to settle dialog with "cancel" outcome
return {
// This is REQUIRED;
// Need to inject these (from useDialogPluginComponent() call)
// into the vue scope for the vue html template
dialogRef,
onDialogHide,
// other methods that we used in our vue html template;
// these are part of our example (so not required)
onOKClick() {
// on OK, it is REQUIRED to
// call onDialogOK (with optional payload)
onDialogOK();
// or with payload: onDialogOK({ ... })
// ...and it will also hide the dialog automatically
},
// we can passthrough onDialogCancel directly
onCancelClick: onDialogCancel,
};
},
});
</script>

View File

@ -0,0 +1,33 @@
<template>
<q-drawer
v-model="showDrawer"
show-if-above
:width="200"
:breakpoint="700"
elevated
data-cy="drawer"
class="bg-primary text-white"
>
<q-scroll-area class="fit">
<div class="q-pa-sm">
<div v-for="n in 50" :key="n">Drawer {{ n }} / 50</div>
</div>
<q-btn data-cy="button">Am I on screen?</q-btn>
</q-scroll-area>
</q-drawer>
</template>
<script>
import { ref, defineComponent } from 'vue';
export default defineComponent({
name: 'QuasarDrawer',
setup() {
const showDrawer = ref(true);
return {
showDrawer,
};
},
});
</script>

View File

@ -0,0 +1,21 @@
<template>
<q-page-sticky position="bottom-right" :offset="[18, 18]">
<q-btn data-cy="button" rounded color="accent" icon="arrow_forward">
{{ title }}
</q-btn>
</q-page-sticky>
</template>
<script>
import { defineComponent } from 'vue';
export default defineComponent({
name: 'QuasarPageSticky',
props: {
title: {
type: String,
required: true,
},
},
});
</script>

View File

@ -0,0 +1,28 @@
<template>
<q-btn color="primary" data-cy="button">
Button
<q-tooltip
v-model="showTooltip"
data-cy="tooltip"
class="bg-red"
:offset="[10, 10]"
>
Here I am!
</q-tooltip>
</q-btn>
</template>
<script>
import { ref, defineComponent } from 'vue';
export default defineComponent({
name: 'QuasarTooltip',
setup() {
const showTooltip = ref(true);
return {
showTooltip,
};
},
});
</script>

View File

@ -11,7 +11,12 @@
true-value="en"
v-model="locale"
/>
<q-toggle v-model="darkMode" checked-icon="dark_mode" color="orange" unchecked-icon="light_mode" />
<q-toggle
v-model="darkMode"
checked-icon="dark_mode"
color="orange"
unchecked-icon="light_mode"
/>
<q-btn color="orange" outline size="sm" label="Settings" icon="settings" />
</div>
@ -31,7 +36,15 @@
</div>
<div class="text-subtitle3 text-grey-7 q-mb-xs">@{{ user.username }}</div>
<q-btn color="orange" flat label="Log Out" size="sm" icon="logout" @click="logout()" v-close-popup />
<q-btn
color="orange"
flat
label="Log Out"
size="sm"
icon="logout"
@click="logout()"
v-close-popup
/>
</div>
</div>
</q-menu>
@ -44,8 +57,8 @@ import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import axios from 'axios';
import { useState } from '@/core/composables/useState';
import { useSession } from '@/core/composables/useSession';
import { useState } from 'src/composables/useState';
import { useSession } from 'src/composables/useSession';
const quasar = useQuasar();
const state = useState();

View File

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

View File

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

View File

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

View File

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

View File

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

8
src/components/models.ts Normal file
View File

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

View File

@ -1,4 +1,4 @@
import store from '@/store';
/* import store from '@/store';
export function useRole() {
function hasAny(roles: string[]): boolean {
@ -15,3 +15,4 @@ export function useRole() {
hasAny,
};
}
*/

2
src/css/app.scss Normal file
View File

@ -0,0 +1,2 @@
// app global css in SCSS form
@import './icons.scss';

View File

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 158 KiB

393
src/css/icons.scss Normal file
View File

@ -0,0 +1,393 @@
@font-face {
font-family: 'icomoon';
src: url('fonts/icomoon.eot?cjtmya');
src: url('fonts/icomoon.eot?cjtmya#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?cjtmya') format('truetype'), url('fonts/icomoon.woff?cjtmya') format('woff'),
url('fonts/icomoon.svg?cjtmya#icomoon') format('svg');
font-weight: normal;
font-style: normal;
font-display: block;
}
[class^='icon-'],
[class*=' icon-'] {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'icomoon' !important;
speak: never;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-frozen:before {
content: '\e900';
}
.icon-Person:before {
content: '\e901';
}
.icon-handmadeArtificial:before {
content: '\e902';
}
.icon-fruit:before {
content: '\e903';
}
.icon-funeral:before {
content: '\e904';
}
.icon-noPayMethod:before {
content: '\e905';
}
.icon-preserved:before {
content: '\e906';
}
.icon-greenery:before {
content: '\e907';
}
.icon-planta:before {
content: '\e908';
}
.icon-handmade:before {
content: '\e909';
}
.icon-accessory:before {
content: '\e90a';
}
.icon-artificial:before {
content: '\e90b';
}
.icon-flower:before {
content: '\e90c';
}
.icon-fixedPrice:before {
content: '\e90d';
}
.icon-addperson:before {
content: '\e90e';
}
.icon-supplierfalse:before {
content: '\e90f';
}
.icon-invoice-out:before {
content: '\e910';
}
.icon-invoice-in:before {
content: '\e911';
}
.icon-invoice-in-create:before {
content: '\e912';
}
.icon-basketadd:before {
content: '\e913';
}
.icon-basket:before {
content: '\e914';
}
.icon-uniE915:before {
content: '\e915';
}
.icon-uniE916:before {
content: '\e916';
}
.icon-uniE917:before {
content: '\e917';
}
.icon-uniE918:before {
content: '\e918';
}
.icon-uniE919:before {
content: '\e919';
}
.icon-uniE91A:before {
content: '\e91a';
}
.icon-isTooLittle:before {
content: '\e91b';
}
.icon-deliveryprices:before {
content: '\e91c';
}
.icon-onlinepayment:before {
content: '\e91d';
}
.icon-risk:before {
content: '\e91e';
}
.icon-noweb:before {
content: '\e91f';
}
.icon-no036:before {
content: '\e920';
}
.icon-disabled:before {
content: '\e921';
}
.icon-treatments:before {
content: '\e922';
}
.icon-invoice:before {
content: '\e923';
}
.icon-photo:before {
content: '\e924';
}
.icon-supplier:before {
content: '\e925';
}
.icon-languaje:before {
content: '\e926';
}
.icon-credit:before {
content: '\e927';
}
.icon-client:before {
content: '\e928';
}
.icon-shipment-01:before {
content: '\e929';
}
.icon-account:before {
content: '\e92a';
}
.icon-inventory:before {
content: '\e92b';
}
.icon-unavailable:before {
content: '\e92c';
}
.icon-wiki:before {
content: '\e92d';
}
.icon-attach:before {
content: '\e92e';
}
.icon-exit:before {
content: '\e92f';
}
.icon-anonymous:before {
content: '\e930';
}
.icon-net:before {
content: '\e931';
}
.icon-buyrequest:before {
content: '\e932';
}
.icon-thermometer:before {
content: '\e933';
}
.icon-entry:before {
content: '\e934';
}
.icon-deletedTicket:before {
content: '\e935';
}
.icon-logout:before {
content: '\e936';
}
.icon-catalog:before {
content: '\e937';
}
.icon-agency:before {
content: '\e938';
}
.icon-delivery:before {
content: '\e939';
}
.icon-wand:before {
content: '\e93a';
}
.icon-buscaman:before {
content: '\e93b';
}
.icon-pbx:before {
content: '\e93c';
}
.icon-calendar:before {
content: '\e93d';
}
.icon-splitline:before {
content: '\e93e';
}
.icon-consignatarios:before {
content: '\e93f';
}
.icon-tax:before {
content: '\e940';
}
.icon-notes:before {
content: '\e941';
}
.icon-lines:before {
content: '\e942';
}
.icon-zone:before {
content: '\e943';
}
.icon-greuge:before {
content: '\e944';
}
.icon-ticketAdd:before {
content: '\e945';
}
.icon-components:before {
content: '\e946';
}
.icon-pets:before {
content: '\e947';
}
.icon-linesprepaired:before {
content: '\e948';
}
.icon-control:before {
content: '\e949';
}
.icon-revision:before {
content: '\e94a';
}
.icon-deaulter:before {
content: '\e94b';
}
.icon-services:before {
content: '\e94c';
}
.icon-albaran:before {
content: '\e94d';
}
.icon-solunion:before {
content: '\e94e';
}
.icon-stowaway:before {
content: '\e94f';
}
.icon-apps:before {
content: '\e951';
}
.icon-info:before {
content: '\e952';
}
.icon-columndelete:before {
content: '\e953';
}
.icon-columnadd:before {
content: '\e954';
}
.icon-deleteline:before {
content: '\e955';
}
.icon-item:before {
content: '\e956';
}
.icon-worker:before {
content: '\e957';
}
.icon-headercol:before {
content: '\e958';
}
.icon-reserva:before {
content: '\e959';
}
.icon-100:before {
content: '\e95a';
}
.icon-sign:before {
content: '\e95d';
}
.icon-polizon:before {
content: '\e95e';
}
.icon-solclaim:before {
content: '\e95f';
}
.icon-actions:before {
content: '\e960';
}
.icon-details:before {
content: '\e961';
}
.icon-traceability:before {
content: '\e962';
}
.icon-claims:before {
content: '\e963';
}
.icon-regentry:before {
content: '\e964';
}
.icon-transaction:before {
content: '\e966';
}
.icon-History:before {
content: '\e968';
}
.icon-mana:before {
content: '\e96a';
}
.icon-ticket:before {
content: '\e96b';
}
.icon-niche:before {
content: '\e96c';
}
.icon-tags:before {
content: '\e96d';
}
.icon-volume:before {
content: '\e96e';
}
.icon-bin:before {
content: '\e96f';
}
.icon-splur:before {
content: '\e970';
}
.icon-barcode:before {
content: '\e971';
}
.icon-botanical:before {
content: '\e972';
}
.icon-clone:before {
content: '\e973';
}
.icon-sms:before {
content: '\e975';
}
.icon-eye:before {
content: '\e976';
}
.icon-doc:before {
content: '\e977';
}
.icon-package:before {
content: '\e978';
}
.icon-settings:before {
content: '\e979';
}
.icon-bucket:before {
content: '\e97a';
}
.icon-mandatory:before {
content: '\e97b';
}
.icon-recovery:before {
content: '\e97c';
}
.icon-payment:before {
content: '\e97e';
}
.icon-grid:before {
content: '\e980';
}
.icon-web:before {
content: '\e982';
}
.icon-dfiscales:before {
content: '\e984';
}

View File

@ -0,0 +1,24 @@
// Quasar SCSS (& Sass) Variables
// --------------------------------------------------
// To customize the look and feel of this app, you can override
// the Sass/SCSS variables found in Quasar's source Sass/SCSS files.
// Check documentation for full list of Quasar variables
// Your own variables (that are declared here) and Quasar's own
// ones will be available out of the box in your .vue/.scss/.sass files
// It's highly recommended to change the default colors
// to match your app's branding.
// Tip: Use the "Theme Builder" on Quasar's documentation website.
$primary: #1976d2;
$secondary: #26a69a;
$accent: #9c27b0;
$dark: #1d1d1d;
$positive: #21ba45;
$negative: #c10015;
$info: #31ccec;
$warning: #f2c037;

7
src/env.d.ts vendored Normal file
View File

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

View File

@ -1,27 +0,0 @@
import { createI18n, LocaleMessages, VueMessageType } from 'vue-i18n';
/**
* Load locale messages
*
* The loaded `JSON` locale messages is pre-compiled by `@intlify/vue-i18n-loader`, which is integrated into `vue-cli-plugin-i18n`.
* See: https://github.com/intlify/vue-i18n-loader#rocket-i18n-resource-pre-compilation
*/
function loadLocaleMessages(): LocaleMessages<VueMessageType> {
const locales = require.context('./locales', true, /[A-Za-z0-9-_,\s]+\.json$/i);
const messages: LocaleMessages<VueMessageType> = {};
locales.keys().forEach((key) => {
const matched = key.match(/([A-Za-z0-9-_]+)\./i);
if (matched && matched.length > 1) {
const locale = matched[1];
messages[locale] = locales(key).default;
}
});
return messages;
}
export default createI18n({
legacy: false, // Disabled for composition API usage
locale: process.env.VUE_APP_I18N_LOCALE || 'en',
fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en',
messages: loadLocaleMessages(),
});

35
src/i18n/en/index.ts Normal file
View File

@ -0,0 +1,35 @@
export default {
globals: {
lang: {
'es': 'Spanish',
'en': 'English'
}
},
errors: {
'statusUnauthorized': 'Access denied',
'statusInternalServerError': 'An internal server error has ocurred'
},
login: {
'title': 'Login',
'username': 'Username',
'password': 'Password',
'submit': 'Log in',
'keepLogin': 'Keep me logged in',
'loginSuccess': 'You have successfully logged in',
'loginError': 'Invalid username or password'
},
customer: {},
components: {
'topbar': {},
'userPanel': {
'settings': 'Settings',
'logOut': 'Log Out'
}
},
pages: {
'logIn': 'Log In',
'dashboard': 'Dashboard',
'customers': 'Customers',
'list': 'List',
}
};

29
src/i18n/es/index.ts Normal file
View File

@ -0,0 +1,29 @@
export default {
'globals': {
'lang': {
'es': 'Español',
'en': 'Inglés'
}
},
'errors': {
'statusUnauthorized': 'Acceso denegado',
'statusInternalServerError': 'Ha ocurrido un error interno del servidor'
},
'login': {
'title': 'Iniciar sesión',
'username': 'Nombre de usuario',
'password': 'Contraseña',
'submit': 'Iniciar sesión',
'keepLogin': 'Mantener sesión iniciada',
'loginSuccess': 'Inicio de sesión correcto',
'loginError': 'Nombre de usuario o contraseña incorrectos'
},
'customer': {},
'components': {
'topbar': {},
'userPanel': {
'settings': 'Configuración',
'logOut': 'Cerrar sesión'
}
}
}

7
src/i18n/index.ts Normal file
View File

@ -0,0 +1,7 @@
import en from './en';
import es from './es';
export default {
'en': en,
'es': es,
};

28
src/index.template.html Normal file
View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<title>
<%= productName %>
</title>
<meta charset="utf-8" />
<meta name="description" content="<%= productDescription %>" />
<meta name="format-detection" content="telephone=no" />
<meta name="msapplication-tap-highlight" content="no" />
<meta name="viewport"
content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width<% if (ctx.mode.cordova || ctx.mode.capacitor) { %>, viewport-fit=cover<% } %>" />
<link rel="icon" type="image/png" sizes="128x128" href="icons/favicon-128x128.png" />
<link rel="icon" type="image/png" sizes="96x96" href="icons/favicon-96x96.png" />
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png" />
<link rel="icon" type="image/ico" href="favicon.ico" />
</head>
<body>
<!-- DO NOT touch the following DIV -->
<div id="q-app"></div>
</body>
</html>

View File

@ -1,6 +1,6 @@
<template>
<q-layout view="hHh lpR fFf">
<Topbar @on-toggle-drawer="onToggleDrawer()" />
<Navbar @on-toggle-drawer="onToggleDrawer()" />
<q-drawer
v-model="drawer"
show-if-above
@ -13,30 +13,40 @@
>
<q-scroll-area class="fit text-grey-8">
<q-list padding>
<q-item clickable v-ripple :to="{ path: '/dashboard' }" active-class="text-orange">
<q-item
clickable
v-ripple
:to="{ path: '/dashboard' }"
active-class="text-orange"
>
<q-item-section avatar>
<q-icon name="dashboard" />
</q-item-section>
<q-item-section> Dashboard</q-item-section>
<q-item-section>Dashboard</q-item-section>
</q-item>
<q-item clickable v-ripple :to="{ path: '/customer' }" active-class="text-orange">
<q-item
clickable
v-ripple
:to="{ path: '/customer' }"
active-class="text-orange"
>
<q-item-section avatar>
<q-icon name="people" />
</q-item-section>
<q-item-section> Customers </q-item-section>
<q-item-section>Customers</q-item-section>
</q-item>
<q-item clickable v-ripple :to="{ path: '/ticket' }" active-class="text-orange">
<q-item-section avatar>
<q-icon name="vn:ticket" />
</q-item-section>
<q-item-section> Tickets </q-item-section>
<q-item-section>Tickets</q-item-section>
</q-item>
<q-item clickable v-ripple>
<q-item-section avatar>
<q-icon name="receipt" />
</q-item-section>
<q-item-section> Invoice Out </q-item-section>
<q-item-section>Invoice Out</q-item-section>
</q-item>
<q-item clickable v-ripple>
@ -44,7 +54,7 @@
<q-icon name="shopping_cart" />
</q-item-section>
<q-item-section> Catalog </q-item-section>
<q-item-section>Catalog</q-item-section>
</q-item>
<q-separator />
@ -54,7 +64,7 @@
<q-icon name="drafts" />
</q-item-section>
<q-item-section> Drafts </q-item-section>
<q-item-section>Drafts</q-item-section>
</q-item>
</q-list>
</q-scroll-area>
@ -67,7 +77,7 @@
<script lang="ts" setup>
import { ref } from 'vue';
import Topbar from '@/components/Topbar.vue';
import Navbar from 'src/components/Navbar.vue';
const drawer = ref(false);
const miniState = ref(true);
@ -77,7 +87,4 @@ function onToggleDrawer(): void {
</script>
<style lang="scss" scoped>
.bg-darker {
background-color: $darker;
}
</style>

View File

@ -1,29 +0,0 @@
{
"globals": {
"lang": {
"es": "Spanish",
"en": "English"
}
},
"errors": {
"statusUnauthorized": "Access denied",
"statusInternalServerError": "An internal server error has ocurred"
},
"login": {
"title": "Login",
"username": "Username",
"password": "Password",
"submit": "Log in",
"keepLogin": "Keep me logged in",
"loginSuccess": "You have successfully logged in",
"loginError": "Invalid username or password"
},
"customer": {},
"components": {
"topbar": {},
"userPanel": {
"settings": "Settings",
"logOut": "Log Out"
}
}
}

View File

@ -1,29 +0,0 @@
{
"globals": {
"lang": {
"es": "Español",
"en": "Inglés"
}
},
"errors": {
"statusUnauthorized": "Acceso denegado",
"statusInternalServerError": "Ha ocurrido un error interno del servidor"
},
"login": {
"title": "Iniciar sesión",
"username": "Nombre de usuario",
"password": "Contraseña",
"submit": "Iniciar sesión",
"keepLogin": "Mantener sesión iniciada",
"loginSuccess": "Inicio de sesión correcto",
"loginError": "Nombre de usuario o contraseña incorrectos"
},
"customer": {},
"components": {
"topbar": {},
"userPanel": {
"settings": "Configuración",
"logOut": "Cerrar sesión"
}
}
}

View File

@ -1,30 +0,0 @@
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import { Quasar } from 'quasar';
import quasarUserOptions from './quasar-user-options';
import i18n from './i18n';
const app = createApp(App).use(i18n).use(Quasar, quasarUserOptions).use(router);
app.mount('#app');
import axios from 'axios';
import { useSession } from '@/core/composables/useSession';
const { getToken } = useSession();
axios.interceptors.request.use(
function (context) {
const token = getToken();
if (token.length && context.headers) {
context.headers.Authorization = token;
}
return context;
},
function (error) {
return Promise.reject(error);
}
);

View File

@ -11,14 +11,25 @@
true-value="en"
v-model="locale"
/>
<q-toggle v-model="darkMode" checked-icon="dark_mode" color="orange" unchecked-icon="light_mode" />
<q-toggle
v-model="darkMode"
checked-icon="dark_mode"
color="orange"
unchecked-icon="light_mode"
/>
</q-toolbar>
</q-header>
<q-page-container>
<q-page>
<div id="login">
<q-card class="login q-pa-xl">
<img src="@/assets/logo.svg" alt="Logo" class="logo q-mb-xl" />
<q-img
src="~/assets/logo.svg"
alt="Logo"
fit="contain"
:ratio="16 / 9"
class="q-mb-md"
/>
<q-form @submit="onSubmit" class="q-gutter-md">
<q-input
filled
@ -41,7 +52,11 @@
(val: string) => (val && val.length > 0) || 'Please type something',
]"
/>
<q-toggle v-model="keepLogin" :label="t('login.keepLogin')" color="orange" />
<q-toggle
v-model="keepLogin"
:label="t('login.keepLogin')"
color="orange"
/>
<div>
<q-btn :label="t('login.submit')" type="submit" color="orange" />
@ -61,7 +76,7 @@ import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import axios from 'axios';
import { useSession } from '@/core/composables/useSession';
import { useSession } from 'src/composables/useSession';
const quasar = useQuasar();
const session = useSession();
@ -81,38 +96,38 @@ const darkMode = computed({
},
});
function onSubmit(): void {
axios
.post('/api/accounts/login', {
async function onSubmit(): Promise<void> {
try {
const { data } = await axios.post('/api/accounts/login', {
user: username.value,
password: password.value,
})
.then((response) => {
session.setToken({
token: response.data.token,
keepLogin: keepLogin.value,
});
});
session.setToken({
token: data.token,
keepLogin: keepLogin.value,
});
quasar.notify({
message: t('login.loginSuccess'),
type: 'positive',
});
router.push('/dashboard');
})
.catch((error) => {
const errorCode = error.response.status;
quasar.notify({
message: t('login.loginSuccess'),
type: 'positive',
});
await router.push({ path: '/dashboard' });
} catch (error) {
if (axios.isAxiosError(error)) {
const errorCode = error.response && error.response.status;
if (errorCode === 401) {
quasar.notify({
message: t('login.loginFailed'),
type: 'negative',
});
} else {
quasar.notify({
message: t('errors.statusInternalServerError'),
message: t('login.loginError'),
type: 'negative',
});
}
});
} else {
quasar.notify({
message: t('errors.statusInternalServerError'),
type: 'negative',
});
}
}
}
</script>

27
src/pages/NotFound.vue Normal file
View File

@ -0,0 +1,27 @@
<template>
<div class="fullscreen bg-blue text-white text-center q-pa-md flex flex-center">
<div>
<div style="font-size: 30vh">404</div>
<div class="text-h2" style="opacity: 0.4">Oops. Nothing here...</div>
<q-btn
class="q-mt-xl"
color="white"
text-color="blue"
unelevated
to="/"
label="Go Home"
no-caps
/>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'Error404',
});
</script>

View File

@ -1,12 +0,0 @@
import './styles/quasar.scss';
import lang from 'quasar/lang/es.js';
import '@quasar/extras/roboto-font/roboto-font.css';
import '@quasar/extras/material-icons/material-icons.css';
import { Notify } from 'quasar';
// To be used on app.use(Quasar, { ... })
export default {
config: {},
plugins: [Notify],
lang: lang,
};

7
src/quasar.d.ts vendored Normal file
View File

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

View File

View File

@ -1,64 +1,81 @@
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
import { useSession } from '@/core/composables/useSession';
import { route } from 'quasar/wrappers';
import {
createMemoryHistory,
createRouter,
createWebHashHistory,
createWebHistory,
} from 'vue-router';
import routes from './routes';
import { i18n } from 'src/boot/i18n';
import { useSession } from 'src/composables/useSession';
const session = useSession();
/*
* If not building with SSR mode, you can
* directly export the Router instantiation;
*
* The function below can be async too; either use
* async/await or return a Promise which resolves
* with the Router instance.
*/
const routes: Array<RouteRecordRaw> = [
{
path: '/login',
name: 'Login',
component: () => import('../views/Login/Login.vue'),
},
{
path: '/',
name: 'Main',
component: () => import('../views/Layout/Main.vue'),
redirect: { name: 'Dashboard' },
children: [
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('../views/Dashboard/Dashboard.vue'),
},
{
path: '/customer',
name: 'Customer',
component: () => import('../views/Customer/Customer.vue'),
redirect: { name: 'List' },
children: [
{
path: 'list',
name: 'List',
component: () => import('../views/Customer/List.vue'),
},
{
path: ':id',
name: 'Card',
component: () => import('../views/Customer/Card/Card.vue'),
},
],
},
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import('../views/Layout/NotFound.vue'),
},
],
},
];
export default route(function (/* { store, ssrContext } */) {
const createHistory = process.env.SERVER
? createMemoryHistory
: process.env.VUE_ROUTER_MODE === 'history'
? createWebHistory
: createWebHashHistory;
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
const Router = createRouter({
scrollBehavior: () => ({ left: 0, top: 0 }),
routes,
// Leave this as is and make changes in quasar.conf.js instead!
// quasar.conf.js -> build -> vueRouterMode
// quasar.conf.js -> build -> publicPath
history: createHistory(
process.env.MODE === 'ssr' ? void 0 : process.env.VUE_ROUTER_BASE
),
});
Router.beforeEach((to, from, next) => {
const { isLoggedIn } = session;
if (!isLoggedIn && to.name !== 'Login') {
next({ path: '/login', query: { redirect: to.fullPath } });
} else {
next();
}
});
Router.afterEach((to) => {
interface Meta {
title?: string;
}
const { t } = i18n.global;
let title = '';
const parent = to.matched[1];
if (parent) {
const parentMeta: Meta = parent.meta;
if (parentMeta && parentMeta.title) {
title += t(`pages.${parentMeta.title}`);
}
}
//const childTitle: string = childMeta.title;
const childMeta: Meta = to.meta;
if (childMeta && childMeta.title) {
if (title != '') title += ' - ';
title += t(`pages.${childMeta.title}`);
}
document.title = title;
});
return Router;
});
router.beforeEach((to, from, next) => {
const session = useSession();
const { isLoggedIn } = session;
if (!isLoggedIn && to.name !== 'Login') {
next({ path: '/login', query: { redirect: to.fullPath } });
} else {
next();
}
});
export default router;

51
src/router/routes.ts Normal file
View File

@ -0,0 +1,51 @@
import { RouteRecordRaw } from 'vue-router';
const routes: RouteRecordRaw[] = [
{
path: '/login',
name: 'Login',
meta: { title: 'logIn' },
component: () => import('../pages/Login/Login.vue'),
},
{
path: '/',
name: 'Main',
component: () => import('../layouts/Main.vue'),
redirect: { name: 'Dashboard' },
children: [
{
path: '/dashboard',
name: 'Dashboard',
meta: { title: 'dashboard' },
component: () => import('../pages/Dashboard/Dashboard.vue'),
},
{
path: '/customer',
name: 'Customer',
meta: { title: 'customers' },
component: () => import('../pages/Customer/Customer.vue'),
redirect: { name: 'List' },
children: [
{
path: 'list',
name: 'List',
meta: { title: 'list' },
component: () => import('../pages/Customer/List.vue'),
},
{
path: ':id',
name: 'Card',
component: () => import('../pages/Customer/Card/Card.vue'),
},
],
},
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import('../pages/NotFound.vue'),
},
],
},
];
export default routes;

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

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

View File

@ -1,394 +0,0 @@
@font-face {
font-family: 'icomoon';
src: url('fonts/icomoon.eot?cjtmya');
src: url('fonts/icomoon.eot?cjtmya#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?cjtmya') format('truetype'),
url('fonts/icomoon.woff?cjtmya') format('woff'),
url('fonts/icomoon.svg?cjtmya#icomoon') format('svg');
font-weight: normal;
font-style: normal;
font-display: block;
}
[class^="icon-"], [class*=" icon-"] {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'icomoon' !important;
speak: never;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-frozen:before {
content: "\e900";
}
.icon-Person:before {
content: "\e901";
}
.icon-handmadeArtificial:before {
content: "\e902";
}
.icon-fruit:before {
content: "\e903";
}
.icon-funeral:before {
content: "\e904";
}
.icon-noPayMethod:before {
content: "\e905";
}
.icon-preserved:before {
content: "\e906";
}
.icon-greenery:before {
content: "\e907";
}
.icon-planta:before {
content: "\e908";
}
.icon-handmade:before {
content: "\e909";
}
.icon-accessory:before {
content: "\e90a";
}
.icon-artificial:before {
content: "\e90b";
}
.icon-flower:before {
content: "\e90c";
}
.icon-fixedPrice:before {
content: "\e90d";
}
.icon-addperson:before {
content: "\e90e";
}
.icon-supplierfalse:before {
content: "\e90f";
}
.icon-invoice-out:before {
content: "\e910";
}
.icon-invoice-in:before {
content: "\e911";
}
.icon-invoice-in-create:before {
content: "\e912";
}
.icon-basketadd:before {
content: "\e913";
}
.icon-basket:before {
content: "\e914";
}
.icon-uniE915:before {
content: "\e915";
}
.icon-uniE916:before {
content: "\e916";
}
.icon-uniE917:before {
content: "\e917";
}
.icon-uniE918:before {
content: "\e918";
}
.icon-uniE919:before {
content: "\e919";
}
.icon-uniE91A:before {
content: "\e91a";
}
.icon-isTooLittle:before {
content: "\e91b";
}
.icon-deliveryprices:before {
content: "\e91c";
}
.icon-onlinepayment:before {
content: "\e91d";
}
.icon-risk:before {
content: "\e91e";
}
.icon-noweb:before {
content: "\e91f";
}
.icon-no036:before {
content: "\e920";
}
.icon-disabled:before {
content: "\e921";
}
.icon-treatments:before {
content: "\e922";
}
.icon-invoice:before {
content: "\e923";
}
.icon-photo:before {
content: "\e924";
}
.icon-supplier:before {
content: "\e925";
}
.icon-languaje:before {
content: "\e926";
}
.icon-credit:before {
content: "\e927";
}
.icon-client:before {
content: "\e928";
}
.icon-shipment-01:before {
content: "\e929";
}
.icon-account:before {
content: "\e92a";
}
.icon-inventory:before {
content: "\e92b";
}
.icon-unavailable:before {
content: "\e92c";
}
.icon-wiki:before {
content: "\e92d";
}
.icon-attach:before {
content: "\e92e";
}
.icon-exit:before {
content: "\e92f";
}
.icon-anonymous:before {
content: "\e930";
}
.icon-net:before {
content: "\e931";
}
.icon-buyrequest:before {
content: "\e932";
}
.icon-thermometer:before {
content: "\e933";
}
.icon-entry:before {
content: "\e934";
}
.icon-deletedTicket:before {
content: "\e935";
}
.icon-logout:before {
content: "\e936";
}
.icon-catalog:before {
content: "\e937";
}
.icon-agency:before {
content: "\e938";
}
.icon-delivery:before {
content: "\e939";
}
.icon-wand:before {
content: "\e93a";
}
.icon-buscaman:before {
content: "\e93b";
}
.icon-pbx:before {
content: "\e93c";
}
.icon-calendar:before {
content: "\e93d";
}
.icon-splitline:before {
content: "\e93e";
}
.icon-consignatarios:before {
content: "\e93f";
}
.icon-tax:before {
content: "\e940";
}
.icon-notes:before {
content: "\e941";
}
.icon-lines:before {
content: "\e942";
}
.icon-zone:before {
content: "\e943";
}
.icon-greuge:before {
content: "\e944";
}
.icon-ticketAdd:before {
content: "\e945";
}
.icon-components:before {
content: "\e946";
}
.icon-pets:before {
content: "\e947";
}
.icon-linesprepaired:before {
content: "\e948";
}
.icon-control:before {
content: "\e949";
}
.icon-revision:before {
content: "\e94a";
}
.icon-deaulter:before {
content: "\e94b";
}
.icon-services:before {
content: "\e94c";
}
.icon-albaran:before {
content: "\e94d";
}
.icon-solunion:before {
content: "\e94e";
}
.icon-stowaway:before {
content: "\e94f";
}
.icon-apps:before {
content: "\e951";
}
.icon-info:before {
content: "\e952";
}
.icon-columndelete:before {
content: "\e953";
}
.icon-columnadd:before {
content: "\e954";
}
.icon-deleteline:before {
content: "\e955";
}
.icon-item:before {
content: "\e956";
}
.icon-worker:before {
content: "\e957";
}
.icon-headercol:before {
content: "\e958";
}
.icon-reserva:before {
content: "\e959";
}
.icon-100:before {
content: "\e95a";
}
.icon-sign:before {
content: "\e95d";
}
.icon-polizon:before {
content: "\e95e";
}
.icon-solclaim:before {
content: "\e95f";
}
.icon-actions:before {
content: "\e960";
}
.icon-details:before {
content: "\e961";
}
.icon-traceability:before {
content: "\e962";
}
.icon-claims:before {
content: "\e963";
}
.icon-regentry:before {
content: "\e964";
}
.icon-transaction:before {
content: "\e966";
}
.icon-History:before {
content: "\e968";
}
.icon-mana:before {
content: "\e96a";
}
.icon-ticket:before {
content: "\e96b";
}
.icon-niche:before {
content: "\e96c";
}
.icon-tags:before {
content: "\e96d";
}
.icon-volume:before {
content: "\e96e";
}
.icon-bin:before {
content: "\e96f";
}
.icon-splur:before {
content: "\e970";
}
.icon-barcode:before {
content: "\e971";
}
.icon-botanical:before {
content: "\e972";
}
.icon-clone:before {
content: "\e973";
}
.icon-sms:before {
content: "\e975";
}
.icon-eye:before {
content: "\e976";
}
.icon-doc:before {
content: "\e977";
}
.icon-package:before {
content: "\e978";
}
.icon-settings:before {
content: "\e979";
}
.icon-bucket:before {
content: "\e97a";
}
.icon-mandatory:before {
content: "\e97b";
}
.icon-recovery:before {
content: "\e97c";
}
.icon-payment:before {
content: "\e97e";
}
.icon-grid:before {
content: "\e980";
}
.icon-web:before {
content: "\e982";
}
.icon-dfiscales:before {
content: "\e984";
}

View File

@ -1,4 +0,0 @@
@import './quasar.variables.scss';
@import '~quasar-styl';
@import './icons.scss';
// @import '~quasar-addon-styl';

View File

@ -1,16 +0,0 @@
// It's highly recommended to change the default colors
// to match your app's branding.
$primary : #027BE3;
$secondary : #26A69A;
$accent : #9C27B0;
$dark : #1D1D1D;
$darker : #111111;
$positive : #21BA45;
$negative : #C10015;
$info : #31CCEC;
$warning : #F2C037;
@import '~quasar-variables-styl'

View File

@ -1,5 +0,0 @@
<template>
<q-page> Page not found </q-page>
</template>
<style lang="scss" scoped></style>

2
test/cypress/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
videos/*
screenshots/*

View File

@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

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

View File

@ -0,0 +1,33 @@
/// <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

@ -23,3 +23,8 @@
//
// -- This is will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
// DO NOT REMOVE
// Imports Quasar Cypress AE predefined commands
import { registerCommands } from '@quasar/quasar-app-extension-testing-e2e-cypress';
registerCommands();

View File

@ -1,6 +1,6 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
// loaded automatically before your e2e test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
@ -13,8 +13,4 @@
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands';
// Alternatively you can use CommonJS syntax:
// require('./commands')

View File

@ -0,0 +1,46 @@
// ***********************************************************
// This example support/unit.js is processed and
// loaded automatically before your unit test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
import './commands';
// Change this if you have a different entrypoint for the main scss.
import 'src/css/app.scss';
// Quasar styles
import 'quasar/src/css/index.sass';
// ICON SETS
// If you use multiple or different icon-sets then the default, be sure to import them here.
import 'quasar/dist/icon-set/material-icons.umd.prod';
import '@quasar/extras/material-icons/material-icons.css';
import { installQuasarPlugin } from '@quasar/quasar-app-extension-testing-e2e-cypress';
import { config } from '@vue/test-utils';
import { Dialog } from 'quasar';
// Example to import i18n from boot and use as plugin
// import { i18n } from 'src/boot/i18n';
// You can modify the global config here for all tests or pass in the configuration per test
// For example use the actual i18n instance or mock it
// config.global.plugins.push(i18n);
config.global.mocks = {
$t: () => '',
};
// Overwrite the transition and transition-group stubs which are stubbed by test-utils by default.
// We do want transitions to show when doing visual testing :)
config.global.stubs = {};
installQuasarPlugin({ plugins: { Dialog } });

View File

@ -0,0 +1,26 @@
<script>
import { defineComponent } from 'vue';
import { Dialog } from 'quasar';
export default defineComponent({
name: 'DialogWrapper',
props: {
component: {
type: Object,
required: true,
},
componentProps: {
type: Object,
default: () => ({}),
},
},
setup(props) {
Dialog.create({
component: props.component,
// props forwarded to your custom component
componentProps: props.componentProps,
});
},
});
</script>

View File

@ -0,0 +1,20 @@
<template>
<q-layout>
<component :is="component" v-bind="$attrs" />
</q-layout>
</template>
<script>
import { defineComponent } from 'vue';
export default defineComponent({
name: 'LayoutContainer',
inheritAttrs: false,
props: {
component: {
type: Object,
required: true,
},
},
});
</script>

10
test/jest/.eslintrc.js Normal file
View File

@ -0,0 +1,10 @@
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',
],
};

1
test/jest/.gitignore vendored Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +0,0 @@
module.exports = {
plugins: ['cypress'],
env: {
mocha: true,
'cypress/globals': true,
},
rules: {
strict: 'off',
},
};

View File

@ -1,25 +0,0 @@
/* eslint-disable arrow-body-style */
// https://docs.cypress.io/guides/guides/plugins-guide.html
// if you need a custom webpack configuration you can uncomment the following import
// and then use the `file:preprocessor` event
// as explained in the cypress docs
// https://docs.cypress.io/api/plugins/preprocessors-api.html#Examples
// /* eslint-disable import/no-extraneous-dependencies, global-require */
// const webpack = require('@cypress/webpack-preprocessor')
module.exports = (on, config) => {
// on('file:preprocessor', webpack({
// webpackOptions: require('@vue/cli-service/webpack.config'),
// watchOptions: {}
// }))
return Object.assign({}, config, {
fixturesFolder: 'tests/e2e/fixtures',
integrationFolder: 'tests/e2e/specs',
screenshotsFolder: 'tests/e2e/screenshots',
videosFolder: 'tests/e2e/videos',
supportFile: 'tests/e2e/support/index.js',
});
};

Some files were not shown because too many files have changed in this diff Show More