├── .eslintrc.json
├── .github
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .npmignore
├── .prettierignore
├── .prettierrc.json
├── .travis.yml
├── CHANGELOG.md
├── Docs
├── Admin
│ ├── Admin.md
│ ├── Authenticated.md
│ ├── Composer.md
│ └── Unauthenticated.md
├── Layouts
│ └── AppLayout.md
├── Resource
│ └── Resource.md
└── Ui-Components
│ ├── DateField.md
│ └── Sidebar
│ ├── Sidebar.md
│ ├── SidebarAction.md
│ ├── SidebarHeading.md
│ ├── SidebarLink.md
│ ├── SidebarNode.md
│ ├── SimpleSidebar.md
│ └── sidebar.png
├── LICENSE
├── README.md
├── _config.yml
├── babel.config.js
├── cypress.json
├── demo
├── App.vue
├── assets
│ ├── camba-icon.png
│ └── logo.png
├── components
│ ├── AuthCustomView.vue
│ ├── CustomSidebar.vue
│ ├── UnauthorizedCustomView.vue
│ ├── UnauthorizedView.vue
│ ├── articles
│ │ ├── CreateArticles.vue
│ │ ├── EditArticles.vue
│ │ ├── ListArticles.vue
│ │ └── ShowArticles.vue
│ ├── authors
│ │ ├── CreateAuthors.vue
│ │ ├── EditAuthors.vue
│ │ ├── ListAuthors.vue
│ │ └── ShowAuthors.vue
│ └── magazines
│ │ ├── CreateMagazines.vue
│ │ ├── EditMagazines.vue
│ │ ├── ListMagazines.vue
│ │ └── ShowMagazines.vue
├── constants
│ └── index.js
├── utils
│ └── dates.js
└── va-auth-adapter
│ └── axios.adapter.js
├── jest.config.js
├── package-lock.json
├── package.json
├── public
├── background.png
├── banner.png
├── camba_icon.png
├── demo.gif
├── favicon.ico
├── index.html
└── logo.png
├── src
├── assets
│ ├── fonts
│ │ └── Montserrat
│ │ │ ├── Montserrat-Black.ttf
│ │ │ ├── Montserrat-BlackItalic.ttf
│ │ │ ├── Montserrat-Bold.ttf
│ │ │ ├── Montserrat-BoldItalic.ttf
│ │ │ ├── Montserrat-ExtraBold.ttf
│ │ │ ├── Montserrat-ExtraBoldItalic.ttf
│ │ │ ├── Montserrat-ExtraLight.ttf
│ │ │ ├── Montserrat-ExtraLightItalic.ttf
│ │ │ ├── Montserrat-Italic.ttf
│ │ │ ├── Montserrat-Light.ttf
│ │ │ ├── Montserrat-LightItalic.ttf
│ │ │ ├── Montserrat-Medium.ttf
│ │ │ ├── Montserrat-MediumItalic.ttf
│ │ │ ├── Montserrat-Regular.ttf
│ │ │ ├── Montserrat-SemiBold.ttf
│ │ │ ├── Montserrat-SemiBoldItalic.ttf
│ │ │ ├── Montserrat-Thin.ttf
│ │ │ ├── Montserrat-ThinItalic.ttf
│ │ │ ├── Montserrat.css
│ │ │ └── OFL.txt
│ └── logo.png
├── components
│ ├── Actions
│ │ ├── Create
│ │ │ ├── Composer.vue
│ │ │ ├── Create.vue
│ │ │ ├── defaults.js
│ │ │ └── index.js
│ │ ├── Edit
│ │ │ ├── Composer.vue
│ │ │ ├── Edit.vue
│ │ │ ├── defaults.js
│ │ │ └── index.js
│ │ ├── List
│ │ │ ├── Composer.vue
│ │ │ ├── List.vue
│ │ │ ├── defaults.js
│ │ │ └── index.js
│ │ ├── Show
│ │ │ ├── Composer.vue
│ │ │ ├── Show.vue
│ │ │ ├── defaults.js
│ │ │ └── index.js
│ │ ├── compose.js
│ │ └── index.js
│ ├── Admin
│ │ ├── index.js
│ │ └── src
│ │ │ ├── Admin.vue
│ │ │ ├── Alerts.vue
│ │ │ ├── Authenticated.vue
│ │ │ ├── Composer.vue
│ │ │ ├── Unauthenticated.vue
│ │ │ └── defaults.js
│ ├── Core
│ │ ├── index.js
│ │ └── src
│ │ │ └── Core.vue
│ ├── Layouts
│ │ ├── index.js
│ │ └── src
│ │ │ ├── AppLayout
│ │ │ ├── AppLayout.vue
│ │ │ └── index.js
│ │ │ ├── AuthLayout
│ │ │ ├── AuthLayout.vue
│ │ │ ├── defaults.js
│ │ │ └── index.js
│ │ │ ├── HomeLayout
│ │ │ ├── HomeLayout.vue
│ │ │ └── index.js
│ │ │ └── UnauthorizedLayout
│ │ │ ├── UnauthorizedLayout.vue
│ │ │ └── index.js
│ ├── Resource
│ │ ├── index.js
│ │ └── src
│ │ │ ├── Composer.vue
│ │ │ ├── Resource.vue
│ │ │ └── defaults.js
│ └── UiComponents
│ │ ├── DateField
│ │ ├── index.js
│ │ └── src
│ │ │ ├── DateField.vue
│ │ │ └── defaults.js
│ │ ├── DeleteButton
│ │ ├── DeleteButton.vue
│ │ └── index.js
│ │ ├── EditButton
│ │ ├── EditButton.vue
│ │ └── index.js
│ │ ├── Sidebar
│ │ ├── Sidebar.vue
│ │ ├── SidebarAction.vue
│ │ ├── SidebarHeading.vue
│ │ ├── SidebarLink.vue
│ │ ├── SidebarNode.vue
│ │ ├── SimpleSidebar.vue
│ │ ├── defaults.js
│ │ └── index.js
│ │ ├── SimpleText
│ │ ├── index.js
│ │ └── src
│ │ │ └── SimpleText.vue
│ │ ├── Spinner
│ │ ├── Spinner.vue
│ │ └── index.js
│ │ ├── TextField
│ │ ├── index.js
│ │ └── src
│ │ │ └── TextField.vue
│ │ └── index.js
├── constants
│ ├── error.messages.js
│ ├── ui.content.default.js
│ ├── ui.element.names.js
│ └── ui.elements.props.js
├── handlers
│ └── error
│ │ └── src
│ │ └── index.js
├── index.js
├── main.js
├── plugins
│ ├── vuetify.js
│ └── vuex
│ │ ├── index.js
│ │ └── subscriptions.js
├── router
│ ├── auth.utils.js
│ ├── index.js
│ ├── route.bindings.js
│ └── route.hooks.js
├── store
│ ├── modules
│ │ ├── alerts.js
│ │ ├── crud.js
│ │ ├── entities.js
│ │ ├── index.js
│ │ ├── requests.js
│ │ └── resources.js
│ └── utils
│ │ ├── common.utils.js
│ │ ├── create.utils.js
│ │ ├── edit.utils.js
│ │ ├── list.utils.js
│ │ └── show.utils.js
├── styles
│ └── main.sass
├── templates
│ └── src
│ │ ├── en
│ │ └── error.json
│ │ └── index.js
├── va-auth
│ └── src
│ │ ├── store
│ │ ├── index.js
│ │ └── modules
│ │ │ ├── actions.js
│ │ │ ├── getters.js
│ │ │ ├── mutations.js
│ │ │ └── state.js
│ │ └── types
│ │ └── index.js
└── validators
│ └── src
│ ├── components
│ └── resource.js
│ └── index.js
├── tests
├── e2e
│ ├── .eslintrc.js
│ ├── factory
│ │ ├── auth
│ │ │ └── index.js
│ │ ├── env
│ │ │ └── index.js
│ │ ├── index.js
│ │ ├── resources
│ │ │ ├── articles.js
│ │ │ ├── authors.js
│ │ │ ├── index.js
│ │ │ └── magazines.js
│ │ ├── store
│ │ │ ├── common.utils.js
│ │ │ ├── index.js
│ │ │ ├── initial.getters.js
│ │ │ └── initial.state.js
│ │ ├── users
│ │ │ └── index.js
│ │ └── utils.js
│ ├── fixtures
│ │ ├── articles.json
│ │ ├── authors.json
│ │ └── magazines.json
│ ├── helpers
│ │ └── index.js
│ ├── lib
│ │ ├── commands.js
│ │ ├── helpers.js
│ │ └── server.js
│ ├── plugins
│ │ └── index.js
│ ├── specs
│ │ ├── articles
│ │ │ ├── create.spec.js
│ │ │ ├── delete.spec.js
│ │ │ ├── edit.spec.js
│ │ │ ├── list.spec.js
│ │ │ └── show.spec.js
│ │ ├── auth-custom
│ │ │ └── auth-custom.spec.js
│ │ ├── auth
│ │ │ └── auth.spec.js
│ │ ├── authors
│ │ │ ├── create.spec.js
│ │ │ ├── edit.spec.js
│ │ │ ├── list.spec.js
│ │ │ └── show.spec.js
│ │ ├── magazines
│ │ │ ├── create.spec.js
│ │ │ ├── edit.spec.js
│ │ │ ├── list.spec.js
│ │ │ └── show.spec.js
│ │ ├── spinner
│ │ │ ├── spinner-create.spec.js
│ │ │ ├── spinner-edit.spec.js
│ │ │ ├── spinner-list.spec.js
│ │ │ └── spinner-show.spec.js
│ │ ├── store
│ │ │ ├── getters.spec.js
│ │ │ └── state.spec.js
│ │ ├── ui
│ │ │ ├── sidebar.spec.js
│ │ │ └── ui.spec.js
│ │ └── unauthorized
│ │ │ └── unauthorized.spec.js
│ └── support
│ │ ├── commands.js
│ │ └── index.js
└── unit
│ ├── .eslintrc.js
│ ├── factory
│ ├── admin
│ │ └── index.js
│ ├── auth
│ │ └── index.js
│ ├── index.js
│ ├── resource
│ │ ├── index.js
│ │ └── responses.js
│ └── store
│ │ ├── common.utils.js
│ │ ├── index.js
│ │ ├── initial.getters.js
│ │ ├── initial.mutations.js
│ │ ├── initial.state.js
│ │ └── modules
│ │ └── index.js
│ ├── fixtures
│ ├── actions
│ │ └── index.js
│ ├── admin
│ │ └── index.js
│ ├── auth
│ │ └── index.js
│ ├── resource
│ │ └── magazines.js
│ └── ui-components
│ │ └── date.input.js
│ ├── lib
│ ├── constants.js
│ └── utils
│ │ └── wrapper.js
│ └── specs
│ ├── components
│ ├── actions
│ │ ├── create.spec.js
│ │ ├── edit.spec.js
│ │ ├── list.spec.js
│ │ └── show.spec.js
│ ├── admin
│ │ ├── admin.spec.js
│ │ ├── alerts.spec.js
│ │ ├── authenticated.spec.js
│ │ └── unauthenticated.spec.js
│ ├── core.spec.js
│ └── resource.spec.js
│ ├── layouts
│ ├── applayout.spec.js
│ ├── auth.spec.js
│ ├── homelayout.spec.js
│ └── unauthorized.spec.js
│ └── ui-components
│ ├── date.input.spec.js
│ ├── delete.button.spec.js
│ ├── edit.button.spec.js
│ ├── sidebar.spec.js
│ ├── simple.text.spec.js
│ ├── spinner.spec.js
│ └── text.field.spec.js
├── utils
└── server-test
│ ├── package-lock.json
│ ├── package.json
│ ├── server.js
│ └── services
│ ├── articles.js
│ ├── auth.js
│ ├── authors.js
│ └── magazines.js
└── vue.config.js
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "env": {
4 | "node": true,
5 | "es6": true
6 | },
7 | "parser": "vue-eslint-parser",
8 | "parserOptions": {
9 | "ecmaVersion": 6,
10 | "sourceType": "module"
11 | },
12 | "env": {
13 | "node": true
14 | },
15 | "extends": [
16 | "plugin:vue/essential",
17 | "eslint:recommended",
18 | "plugin:prettier/recommended"
19 | ],
20 | "parserOptions": {
21 | "parser": "babel-eslint"
22 | },
23 | "overrides": [
24 | {
25 | "files": ["src/**/*.{js,json,vue}"]
26 | }
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Description
2 |
3 | **Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.**
4 |
5 |
13 |
14 |
15 | Fixes #(issue)
16 |
17 |
18 | Closes #(issue)
19 |
20 | ## Type of change
21 |
22 | **Please delete options that are not relevant.**
23 |
24 |
25 |
26 | - [ ] Bug fix (non-breaking change which fixes an issue)
27 | - [ ] New feature (non-breaking change which adds functionality)
28 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
29 | - [ ] This change requires a documentation update
30 |
31 | ## How Has This Been Tested?
32 |
33 | **Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce the cases. Please also list any relevant details for your test configuration**
34 |
35 |
36 |
37 | - [ ] Test A
38 | - [ ] Test B
39 |
40 |
41 |
42 | **Instructions:**
43 |
44 | **1.** First instruction
45 | **2.** Second instruction
46 | **3.** Third instruction
47 |
48 | **Expected result:** a result description
49 |
50 | ## Checklist:
51 |
52 | > The following options in **bold** are required for a PR approval. Please check the boxes only if necessary, it help us minimizing the reviewing process.
53 |
54 |
55 |
56 | - [ ] **I have performed a self-review of my own code**
57 | - [ ] I have commented my code, particularly in hard-to-understand areas
58 | - [ ] I have made corresponding changes to the documentation
59 | - [ ] **My changes generate no new warnings**
60 | - [ ] I have added tests that prove my fix is effective or that my feature works
61 | - [ ] **New and existing unit tests pass locally with my changes**
62 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # tests
6 | tests/**/coverage
7 |
8 | # e2e tests
9 | tests/e2e/videos
10 |
11 | # local env files
12 | .env.local
13 | .env.*.local
14 |
15 | # Log files
16 | npm-debug.log*
17 | yarn-debug.log*
18 | yarn-error.log*
19 |
20 | # Editor directories and files
21 | .idea
22 | .vscode
23 | *.suo
24 | *.ntvs*
25 | *.njsproj
26 | *.sln
27 | *.sw*
28 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Ignore everything by default
2 | *
3 |
4 | # Keep source
5 | !/src/**/*
6 | /src/main.js
7 | /src/App.vue
8 |
9 | # Keep built files
10 | !/dist/**/*
11 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cambalab/vue-admin/dbc50d1bac6af27338a107f6111a40c557716a38/.prettierignore
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "tabWidth": 2,
4 | "semi": false,
5 | "singleQuote": true
6 | }
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "10.15.3"
4 | addons:
5 | apt:
6 | packages:
7 | # Ubuntu 16+ does not install this dependency by default, so we need to install it ourselves
8 | - libgconf-2-4
9 | cache:
10 | # Caches $HOME/.npm when npm ci is default script command
11 | # Caches node_modules in all other cases
12 | npm: true
13 | directories:
14 | # we also need to cache folder with Cypress binary
15 | - ~/.cache
16 | install:
17 | - npm ci
18 | before_script:
19 | - npm i -g vue-cli
20 | script:
21 | - npm run lint
22 | - npm run build
23 | - npm run test:coverage && ./node_modules/.bin/codecov
24 | - npm run test:e2e -- --headless
25 |
--------------------------------------------------------------------------------
/Docs/Admin/Authenticated.md:
--------------------------------------------------------------------------------
1 | # `Authenticated`
2 |
3 | The `Authenticated` component is used by the `Admin` component. It can be understood as a wrapper of the main application layout whenever a truthy authentication state is present in the `Admin` component.
4 |
5 | It's responsible of creating the unauthorized routes and rendering a Core component along with other application layouts.
6 |
7 | ## internal props
8 |
9 | ### appLayout
10 |
11 | + **Type:** `Object`
12 |
13 | + **Details:** The `AppLayout` component is assigned by default in the `Admin` component's defaults file.
14 |
15 | > Future feature: Admin should expose appLayout
16 |
17 | ## methods
18 |
19 | ### logout
20 |
21 | + **Details:** implements the `auth/AUTH_LOGOUT_REQUEST` interface from `@va-auth/types`
22 |
23 | ### getUser
24 |
25 | + **Details:** implements the `auth/AUTH_GET_USER` interface from `@va-auth/types`
26 |
--------------------------------------------------------------------------------
/Docs/Admin/Composer.md:
--------------------------------------------------------------------------------
1 | # `Admin/Composer`
2 |
3 | The `Composer` wrapper for the `Admin` is nothing more than a functional component that receives an `authProvider` and `options` to render an `Admin` component. By default, the `authProvider` prop will be fed to `@va-auth/store` to obtain a store module that implements `@va-auth/types`.
4 |
5 | ## props
6 |
7 | ### authLayout
8 |
9 | + **Type:** `Object (optional)`
10 |
11 | + **Details:** A component to log in that is passed as prop to `Admin`
12 |
13 | ### authProvider
14 |
15 | + **Type:** `Function`
16 |
17 | + **Details:** a function that implements the `@va-auth/types`.
18 |
19 | + **Example:** [**va-auth-axios-adapter**](https://github.com/Cambalab/va-auth-axios-adapter)
20 |
21 | ### options
22 |
23 | + **Type:** `Object`
24 |
25 | + **Details:** An object with options that are passed as props to `Admin`:
26 | + `authModule`: a vuex store `Object` with properties: `namespaced: true`, state, actions, mutations and getters.
27 | > Corresponds to the `@va-auth` module.
28 |
29 | ### sidebar
30 |
31 | + **Type:** `Object (optional)`
32 |
33 | + **Details:** A sidebar component that is passed as prop to `Admin`
34 |
--------------------------------------------------------------------------------
/Docs/Admin/Unauthenticated.md:
--------------------------------------------------------------------------------
1 | # `Unauthenticated`
2 |
3 | The `Unauthenticated` component is used by the `Admin` component. It's just a wrapper for the provided `authLayout` whenever a falsy authentication state is present in the `Admin` component.
4 |
5 | It's responsible of rendering a component that represents the authentication view.
6 |
7 | > The authentication route `/login` is currently defined in the `Admin/defaults`.
8 |
9 | ## props
10 |
11 | ### layout
12 |
13 | + **Type:** `Object`
14 |
15 | + **Details:** A view that is used for user authentication, corresponding to the `/login` route.
16 |
17 | + **Default:** The `Auth` layout.
18 |
19 | ## methods
20 |
21 | ### login
22 |
23 | + **Details:** implements the `auth/AUTH_LOGIN_REQUEST` interface from `@va-auth/types`
24 |
--------------------------------------------------------------------------------
/Docs/Layouts/AppLayout.md:
--------------------------------------------------------------------------------
1 | # `AppLayout`
2 |
3 | `AppLayout` is used as default to `appLayout` prop in the `Admin` component. It defines how most of the vue admin visual components are rendered.
4 |
5 | ## props
6 |
7 | ### sidebar
8 |
9 | + **Type:** `Sidebar`
10 |
11 | + **Details:** a `Sidebar` component from the `@va-ui` module.
12 |
13 | + **Default:** [`SimpleSidebar`](/Docs/Ui-Components/Sidebar/SimpleSidebar)
14 | > Note that it's currently assigned by `Admin`. Soon to be moved to `AppLayout` defaults.
15 |
16 | ### title
17 |
18 | + **Type:** `String`
19 |
20 | + **Details:** a text used by the `v-app-bar` as a `v-toolbar-title`.
21 |
22 | + **Default:** `VAppBar`
23 |
24 | ## internal props
25 |
26 | ### va
27 |
28 | + **Type:** `Object`
29 |
30 | + **Details:** An object that contains user related functions that are exposed to layouts:
31 | + `logout`: a function that implements the `auth/AUTH_LOGOUT_REQUEST` interface to call the `authModule`.
32 | + `getUser`: a function that implements the `auth/AUTH_GET_USER` interface to call the `authModule`
33 |
34 | > Interfaces implementation can be found in `@va-auth/types`, while the `authModule` is a `@va-auth/store` module, and it's assigned by the `Admin` component.
35 |
36 | + **Default:** An object with the logout and getUser function that, in turn, use the `@va-auth/types`: `AUTH_LOGOUT_REQUEST`, `AUTH_GET_USER`
37 |
38 | + **Provided by:** [`Authenticated`](/Docs/Admin/Authenticated)
39 |
--------------------------------------------------------------------------------
/Docs/Ui-Components/DateField.md:
--------------------------------------------------------------------------------
1 | # DateField
2 |
3 | ### Usage
4 |
5 | In order to Vue Admin understand what kind of input the View should render, we must provide a value to a **type** property to the `input` in the template.
6 |
7 | ```vue
8 |
9 |
10 |
18 |
19 |
20 |
53 | ```
54 |
55 | *It is recommended to implement the parse and/or format functions in a separate module for re-usability*
56 |
57 | ### Supported properties
58 | The DateField component supports the following properties:
59 | * **placeHolder**: The placeholder for the input field (as previously);
60 | * **value**: The value to init the component (as previously);
61 | * **name**: The name of the component (as previously);
62 | * **readonly**: A Boolean indicating if the date in the input field can be modified manually (readonly = false) or only with the datepicker (readonly = true);
63 | * **disabled**: A Boolean indicating if the field is disabled;
64 | * **vDatePickerProps**: An Object with the fields matching the [DatePicker api](https://vuetifyjs.com/en/components/date-pickers#api) and the possible values for them;
65 | * **vMenuProps**: An Object with the fields matching the [Vuetify menu component api](https://vuetifyjs.com/en/components/menus#api) and the possible values for them;
66 | * **format**: A Function that receives a Date formatted string and returns returns a string value to show in the text-field of the DateField component.
67 | * **parse**: A Function that receives a date value and must return a valid string that represents a date for the v-date-picker component
68 |
--------------------------------------------------------------------------------
/Docs/Ui-Components/Sidebar/Sidebar.md:
--------------------------------------------------------------------------------
1 | # `Sidebar`
2 |
3 | A `Sidebar` component that wraps the Sidebar related custom components.
4 |
5 |
6 |
7 |
8 |
9 |
10 | Here you can find an example on how to use this component and the available components that you can provide to it as children:
11 |
12 | ```vue
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | ...
25 | ```
26 |
27 | As you've guessed a function `logout` must be defined in order to invoke it in the `SidebarAction` component.
28 |
29 | A more detailed description of each related component can be found in their respective file:
30 |
31 | ## Related components
32 |
33 | - [SidebarHeading](Sidebar/SidebarHeading.md)
34 | - [SidebarAction](Sidebar/SidebarAction.md)
35 | - [SidebarLink](Sidebar/SidebarLink.md)
36 | - [SidebarNode](Sidebar/SidebarNode.md)
37 |
--------------------------------------------------------------------------------
/Docs/Ui-Components/Sidebar/SidebarAction.md:
--------------------------------------------------------------------------------
1 | # `SidebarAction`
2 |
3 | A `SidebarAction` component renders an item in the sidebar that can execute a certain action.
4 |
5 | *It's important to notice that using this component outside the `Sidebar` component is senseless.*
6 |
7 | Here you can find an example on how to use this component:
8 |
9 | ```vue
10 |
11 | ...
12 |
13 | ...
14 |
15 | ...
16 |
17 | ...
18 |
19 |
28 | ```
29 |
30 | ## props
31 |
32 | ### title
33 |
34 | + **Type:** `String` *(optional)*
35 |
36 | + **Description:** A String that will be displayed as a title inside the sidebar item for this action.
37 |
38 | ### action
39 |
40 | + **Type:** `Function` *(optional)*
41 |
42 | + **Description:** A Function that will be executed when the `SidebarAction` item is clicked.
43 |
44 | ### icon
45 |
46 | + **Type:** `String` *(optional*)
47 |
48 | + **Description:** A String representing the name of a [Material Icon](https://cdn.materialdesignicons.com/3.8.95/) to prepend in the `SidebarAction` item.
49 |
--------------------------------------------------------------------------------
/Docs/Ui-Components/Sidebar/SidebarHeading.md:
--------------------------------------------------------------------------------
1 | # `SidebarHeading`
2 |
3 | A `SidebarHeading` component renders a heading item in the sidebar.
4 |
5 | *It's important to notice that not using this component as a `Sidebar` component child is senseless.*
6 |
7 | Here's a usage example:
8 |
9 | ```vue
10 |
11 | ...
12 |
13 | ...
14 |
20 | ...
21 |
22 | ...
23 |
24 | ```
25 |
26 | ## props
27 |
28 | ### avatar
29 |
30 | + **Type:** `Object`
31 |
32 | + **Description:** represents a Vue component that is be rendered at the left side of the sidebar heading.
33 |
34 | + **Default:** A functional component that given `color` and `content` as `avatarProps`, renders a `VListItemAvatar` component. More about [Vuetify avatar components](https://vuetifyjs.com/en/components/avatars). Example:
35 |
36 | > ```javascript
37 | > ...
38 | > avatar: {
39 | > type: Object,
40 | > default: () => ({
41 | > name: 'SidebarHeadingAvatar',
42 | > functional: true,
43 | > render: function(h, context) {
44 | > const { props } = context
45 | > return (
46 | >
47 | > {h(props.content)}
48 | >
49 | > )
50 | > },
51 | > }),
52 | > },
53 | > ...
54 | > ```
55 |
56 | ### avatarProps
57 |
58 | + **Type:** `Object`
59 |
60 | + **Description:** An Object containing props to customize the `avatar` component.
61 |
62 | + **Default:** an object with props to be passed to the `avatar` prop component: `color: String` and `content: Object | VNode`. Example:
63 |
64 | > ```javascript
65 | > ...
66 | > avatarProps: {
67 | > type: Object,
68 | > default: () => ({
69 | > color: 'teal',
70 | > content: AccountIcon,
71 | > }),
72 | > },
73 | > ...
74 | > const AccountIcon = {
75 | > name: 'AccountIcon',
76 | > render: function(h) {
77 | > return account_circle
78 | > },
79 | > }
80 | > ```
81 |
82 | ### title
83 |
84 | + **Type:** `String`
85 |
86 | + **Description:** A String that will be displayed as title inside the sidebar item.
87 |
88 | + **Default:** `'Menu'`
89 |
90 | ### subTitle
91 |
92 | + **Type:** `String`
93 |
94 | + **Description:** A String that will be displayed as title inside the sidebar item.
95 |
96 | + **Default:** `''`
97 |
--------------------------------------------------------------------------------
/Docs/Ui-Components/Sidebar/SidebarLink.md:
--------------------------------------------------------------------------------
1 | # `SidebarLink`
2 |
3 | A `SidebarLink` component renders an item in the sidebar that can be linked to an specific path in the application. Unlike the `SidebarHeading` component, this component should provide a path relative to this application for one of the declared `Resources`.
4 |
5 | *It's important to notice that using this component outside the `` component is senseless.*
6 |
7 | Here you can find an example on how to use this component:
8 |
9 | ```vue
10 |
11 | ...
12 |
13 | ...
14 |
15 | ...
16 |
17 | ...
18 |
19 | ```
20 |
21 | ## props
22 |
23 | ### title
24 |
25 | + **Type:** `String` *(optional)*
26 |
27 | + **Description:** A String that will be displayed as a title inside the sidebar item.
28 |
29 | ### path
30 |
31 | + **Type:** `String` *(optional)*
32 |
33 | + **Description:** A String representing a relative path.
34 |
35 | ### icon
36 |
37 | + **Type:** `String` *(optional)*
38 |
39 | + **Description:** A String representing the name of the [Material Icon](https://cdn.materialdesignicons.com/3.8.95/) to prepend in the Action item when the group is being displayed.
40 |
--------------------------------------------------------------------------------
/Docs/Ui-Components/Sidebar/SidebarNode.md:
--------------------------------------------------------------------------------
1 | # `SidebarNode`
2 |
3 | A `SidebarNode` component renders an item in the sidebar representing a group parent that on clicked event will toggle the visibility of its children items.
4 |
5 | *It's important to notice that using this component outside the `` component is senseless.*
6 |
7 | Here you can find an example on how to use this component:
8 |
9 | ```vue
10 |
11 | ...
12 |
13 | ...
14 |
15 | ...
16 |
17 | ...
18 |
19 | ...
20 |
21 | ```
22 |
23 | ## props
24 |
25 | ### title
26 |
27 | + **Type:** `String` *(optional)*
28 |
29 | + **Description:** A `String` that will be displayed as title inside the sidebar item.
30 |
31 | ### icon
32 |
33 | + **Type:** `String` *(optional)*
34 |
35 | + **Description:** A `String` representing the name of the [Material Icon](https://cdn.materialdesignicons.com/3.8.95/) to prepend in the Action item when the group is being displayed.
36 |
37 | ### iconAlt
38 |
39 | + **Type:** `String` *(optional)*
40 |
41 | + **Description:** A `String` representing the name of the [Material Icon](https://cdn.materialdesignicons.com/3.8.95/) to prepend in the Action item when the group is being displayed.
42 |
--------------------------------------------------------------------------------
/Docs/Ui-Components/Sidebar/SimpleSidebar.md:
--------------------------------------------------------------------------------
1 | # `SimpleSidebar`
2 |
3 | This is a custom sidebar created using `Sidebar`, `SidebarHeading`,`SidebarAction`,`SidebarLink`,`SidebarNode` that can be exported from `vue-admin-js`. It's a little less customizable than building your own sidebar but it accepts a few useful props.
4 |
5 | ## internal props
6 |
7 | ### subscriptions
8 |
9 | + **Type:** `Array: Function`
10 |
11 | + **Details:** A list of functions. These are currently used to subscribe events to the store and eventually add items to the menu sidebar. These functions must take an `action: Function` that, in turn, take a `mutation: Object` and a `state: Object` (similar to vuex subsciptions). For example:
12 | > ```javascript
13 | > [
14 | > action => (mutation, state) => {
15 | > if (mutation.type === 'something') {
16 | > state.someProp = action()
17 | > }
18 | > },
19 | > ]
20 | > ```
21 |
22 | + **Default:** A list of one function that adds a route to the store state every time a mutation is triggered:
23 |
24 | > ```javascript
25 | > [
26 | > action => (mutation, state) => {
27 | > const { namespace, RESOURCES_ADD_ROUTE } = ResourcesTypes
28 | > if (mutation.type === `${namespace}/${RESOURCES_ADD_ROUTE}`) {
29 | > const currentRoutes = state.resources.routes.map(route => {
30 | > return { icon: 'list', title: route.name, link: route.path }
31 | > })
32 | > action(currentRoutes)
33 | > }
34 | > }
35 | > ]
36 | > ```
37 |
38 | ### va
39 |
40 | + **Type:** `Object`
41 |
42 | + **Details:** An object that contains user related functions that are exposed to layouts:
43 | + `logout`: a function that implements the `auth/AUTH_LOGOUT_REQUEST` interface to call the `authModule`.
44 | + `getUser`: a function that implements the `auth/AUTH_GET_USER` interface to call the `authModule`
45 |
46 | > Interfaces implementation can be found in `@va-auth/types`, while the `authModule` is a `@va-auth/store` module, and it's assigned by the `Admin` component.
47 |
48 | + **Default:** An object with the logout and getUser function that, in turn, use the `@va-auth/types`: `AUTH_LOGOUT_REQUEST`, `AUTH_GET_USER`
49 | > The va object is designed in the `Authenticated` component.
50 | > The `Authenticated` component defines the va object.
51 |
52 | + **Provided by:** [`AppLayout`](/Docs/Layouts/AppLayout)
--------------------------------------------------------------------------------
/Docs/Ui-Components/Sidebar/sidebar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cambalab/vue-admin/dbc50d1bac6af27338a107f6111a40c557716a38/Docs/Ui-Components/Sidebar/sidebar.png
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@babel/preset-env',
4 | [
5 | '@vue/app',
6 | {
7 | useBuiltIns: 'entry',
8 | },
9 | ],
10 | ],
11 | }
12 |
--------------------------------------------------------------------------------
/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "baseUrl": "http://localhost:8081",
3 | "pluginsFile": "tests/e2e/plugins/index.js",
4 | "projectId": "qawufu"
5 | }
6 |
--------------------------------------------------------------------------------
/demo/assets/camba-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cambalab/vue-admin/dbc50d1bac6af27338a107f6111a40c557716a38/demo/assets/camba-icon.png
--------------------------------------------------------------------------------
/demo/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cambalab/vue-admin/dbc50d1bac6af27338a107f6111a40c557716a38/demo/assets/logo.png
--------------------------------------------------------------------------------
/demo/components/CustomSidebar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
18 |
19 |
20 |
67 |
--------------------------------------------------------------------------------
/demo/components/UnauthorizedCustomView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | You do not have permission to access this page.
7 |
8 |
9 |
10 |
11 |
12 |
13 | If you think you should be allowed to see this page, please contact
14 | the administrator.
15 |
16 |
17 |
23 | back
24 |
25 |
26 |
27 |
28 |
49 |
54 |
--------------------------------------------------------------------------------
/demo/components/UnauthorizedView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
12 |
13 |
14 |
31 |
36 |
--------------------------------------------------------------------------------
/demo/components/articles/CreateArticles.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
18 |
--------------------------------------------------------------------------------
/demo/components/articles/EditArticles.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
19 |
--------------------------------------------------------------------------------
/demo/components/articles/ListArticles.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
18 |
25 |
26 |
27 |
28 |
38 |
--------------------------------------------------------------------------------
/demo/components/articles/ShowArticles.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
19 |
--------------------------------------------------------------------------------
/demo/components/authors/CreateAuthors.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
14 |
15 |
16 |
17 |
45 |
--------------------------------------------------------------------------------
/demo/components/authors/EditAuthors.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
14 |
15 |
16 |
17 |
44 |
--------------------------------------------------------------------------------
/demo/components/authors/ListAuthors.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
13 |
14 |
15 |
33 |
--------------------------------------------------------------------------------
/demo/components/authors/ShowAuthors.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
28 |
--------------------------------------------------------------------------------
/demo/components/magazines/ShowMagazines.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Magazine
8 |
9 |
10 |
11 |
12 |
13 |
14 |
20 |
26 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
90 |
--------------------------------------------------------------------------------
/demo/constants/index.js:
--------------------------------------------------------------------------------
1 | export const rowsPerPage = 10
2 |
--------------------------------------------------------------------------------
/demo/utils/dates.js:
--------------------------------------------------------------------------------
1 | import moment from 'moment'
2 |
3 | function parseDate(date) {
4 | return new Date(date).toISOString(true)
5 | }
6 |
7 | function formatDate(date) {
8 | const momentDate = moment(date)
9 | const day = momentDate.date()
10 | const month = momentDate.month() + 1
11 | const year = momentDate.year()
12 | return `${day}/${month}/${year}`
13 | }
14 |
15 | export default {
16 | parseDate,
17 | formatDate,
18 | }
19 |
--------------------------------------------------------------------------------
/demo/va-auth-adapter/axios.adapter.js:
--------------------------------------------------------------------------------
1 | import AuthTypes from '@va-auth/types'
2 |
3 | export default (client, options = {}) => {
4 | return (type, params) => {
5 | const {
6 | AUTH_LOGIN_REQUEST,
7 | AUTH_LOGOUT_REQUEST,
8 | AUTH_CHECK_REQUEST,
9 | } = AuthTypes
10 |
11 | const { authFields, authUrl, storageKey, userField } = Object.assign(
12 | {
13 | authFields: { username: 'username', password: 'password' },
14 | storageKey: 'token',
15 | userField: 'user',
16 | },
17 | options
18 | )
19 |
20 | switch (type) {
21 | case AUTH_LOGIN_REQUEST:
22 | return new Promise((resolve, reject) => {
23 | const headers = {
24 | 'Content-Type': 'application/x-www-form-urlencoded',
25 | [authFields.username]: params.username,
26 | [authFields.password]: params.password,
27 | }
28 | const method = 'post'
29 | const url = authUrl
30 |
31 | client({ url, headers, method })
32 | .then(response => {
33 | const { data } = response
34 | const { [storageKey]: token, [userField]: user } = data
35 | // something more secure maybe?
36 | localStorage.setItem(storageKey, token)
37 | client.defaults.headers.common['Authorization'] = token
38 | resolve(user)
39 | })
40 | .catch(error => {
41 | localStorage.removeItem(storageKey)
42 | reject(error)
43 | })
44 | })
45 |
46 | case AUTH_LOGOUT_REQUEST:
47 | return new Promise(resolve => {
48 | localStorage.removeItem(storageKey)
49 | delete client.defaults.headers.common['Authorization']
50 | resolve()
51 | })
52 |
53 | case AUTH_CHECK_REQUEST:
54 | return new Promise((resolve, reject) => {
55 | const token = localStorage.getItem(storageKey)
56 | if (token) {
57 | const url = authUrl
58 | const headers = {
59 | 'Content-Type': 'application/x-www-form-urlencoded',
60 | token,
61 | }
62 | const method = 'get'
63 | client({ url, headers, method })
64 | .then(response => {
65 | const { data } = response
66 | const { [userField]: user } = data
67 | resolve(user)
68 | })
69 | .catch(error => {
70 | reject(error)
71 | })
72 | } else {
73 | reject('Authentication failed.')
74 | }
75 | })
76 | default:
77 | return Promise.reject(`Unsupported @va-auth action type: ${type}`)
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
3 | transform: {
4 | '^.+\\.vue$': 'vue-jest',
5 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$':
6 | 'jest-transform-stub',
7 | '^.+\\.jsx?$': 'babel-jest',
8 | },
9 | moduleNameMapper: {
10 | '^@/(.*)$': '/src/$1',
11 | '@assets(.*)$': '/src/assets/$1',
12 | '@components(.*)$': '/src/components/$1',
13 | '@constants(.*)$': '/src/constants/$1',
14 | '@demo(.*)$': '/demo/$1',
15 | '@handlers(.*)$': '/src/handlers/$1',
16 | '@plugins(.*)$': '/src/plugins/$1',
17 | '@router(.*)$': '/src/router/$1',
18 | '@store(.*)$': '/src/store/$1',
19 | '@templates(.*)$': '/src/templates/src/$1',
20 | '@unit(.*)$': '/tests/unit/$1',
21 | '@va-auth(.*)$': '/src/va-auth/src/$1',
22 | '@validators(.*)$': '/src/validators/src/$1',
23 | },
24 | snapshotSerializers: ['jest-serializer-vue'],
25 | testMatch: [
26 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)',
27 | ],
28 | collectCoverageFrom: ['**/src/**/*.(js|jsx|ts|tsx|vue)'],
29 | coverageDirectory: 'tests/unit/coverage',
30 | coverageReporters: [
31 | 'text',
32 | 'text-summary',
33 | 'html',
34 | 'json',
35 | 'json-summary',
36 | 'lcov',
37 | ],
38 | transformIgnorePatterns: ['node_modules/(?!(vuetify)/)'],
39 | testURL: 'http://localhost/',
40 | }
41 |
--------------------------------------------------------------------------------
/public/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cambalab/vue-admin/dbc50d1bac6af27338a107f6111a40c557716a38/public/background.png
--------------------------------------------------------------------------------
/public/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cambalab/vue-admin/dbc50d1bac6af27338a107f6111a40c557716a38/public/banner.png
--------------------------------------------------------------------------------
/public/camba_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cambalab/vue-admin/dbc50d1bac6af27338a107f6111a40c557716a38/public/camba_icon.png
--------------------------------------------------------------------------------
/public/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cambalab/vue-admin/dbc50d1bac6af27338a107f6111a40c557716a38/public/demo.gif
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cambalab/vue-admin/dbc50d1bac6af27338a107f6111a40c557716a38/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | vue-admin
9 |
10 |
11 |
12 | We're sorry but vue-admin doesn't work properly without JavaScript enabled. Please enable it to continue.
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cambalab/vue-admin/dbc50d1bac6af27338a107f6111a40c557716a38/public/logo.png
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat/Montserrat-Black.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cambalab/vue-admin/dbc50d1bac6af27338a107f6111a40c557716a38/src/assets/fonts/Montserrat/Montserrat-Black.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat/Montserrat-BlackItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cambalab/vue-admin/dbc50d1bac6af27338a107f6111a40c557716a38/src/assets/fonts/Montserrat/Montserrat-BlackItalic.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat/Montserrat-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cambalab/vue-admin/dbc50d1bac6af27338a107f6111a40c557716a38/src/assets/fonts/Montserrat/Montserrat-Bold.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat/Montserrat-BoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cambalab/vue-admin/dbc50d1bac6af27338a107f6111a40c557716a38/src/assets/fonts/Montserrat/Montserrat-BoldItalic.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat/Montserrat-ExtraBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cambalab/vue-admin/dbc50d1bac6af27338a107f6111a40c557716a38/src/assets/fonts/Montserrat/Montserrat-ExtraBold.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat/Montserrat-ExtraBoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cambalab/vue-admin/dbc50d1bac6af27338a107f6111a40c557716a38/src/assets/fonts/Montserrat/Montserrat-ExtraBoldItalic.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat/Montserrat-ExtraLight.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cambalab/vue-admin/dbc50d1bac6af27338a107f6111a40c557716a38/src/assets/fonts/Montserrat/Montserrat-ExtraLight.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat/Montserrat-ExtraLightItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cambalab/vue-admin/dbc50d1bac6af27338a107f6111a40c557716a38/src/assets/fonts/Montserrat/Montserrat-ExtraLightItalic.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat/Montserrat-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cambalab/vue-admin/dbc50d1bac6af27338a107f6111a40c557716a38/src/assets/fonts/Montserrat/Montserrat-Italic.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat/Montserrat-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cambalab/vue-admin/dbc50d1bac6af27338a107f6111a40c557716a38/src/assets/fonts/Montserrat/Montserrat-Light.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat/Montserrat-LightItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cambalab/vue-admin/dbc50d1bac6af27338a107f6111a40c557716a38/src/assets/fonts/Montserrat/Montserrat-LightItalic.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat/Montserrat-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cambalab/vue-admin/dbc50d1bac6af27338a107f6111a40c557716a38/src/assets/fonts/Montserrat/Montserrat-Medium.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat/Montserrat-MediumItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cambalab/vue-admin/dbc50d1bac6af27338a107f6111a40c557716a38/src/assets/fonts/Montserrat/Montserrat-MediumItalic.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat/Montserrat-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cambalab/vue-admin/dbc50d1bac6af27338a107f6111a40c557716a38/src/assets/fonts/Montserrat/Montserrat-Regular.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat/Montserrat-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cambalab/vue-admin/dbc50d1bac6af27338a107f6111a40c557716a38/src/assets/fonts/Montserrat/Montserrat-SemiBold.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat/Montserrat-SemiBoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cambalab/vue-admin/dbc50d1bac6af27338a107f6111a40c557716a38/src/assets/fonts/Montserrat/Montserrat-SemiBoldItalic.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat/Montserrat-Thin.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cambalab/vue-admin/dbc50d1bac6af27338a107f6111a40c557716a38/src/assets/fonts/Montserrat/Montserrat-Thin.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat/Montserrat-ThinItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cambalab/vue-admin/dbc50d1bac6af27338a107f6111a40c557716a38/src/assets/fonts/Montserrat/Montserrat-ThinItalic.ttf
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cambalab/vue-admin/dbc50d1bac6af27338a107f6111a40c557716a38/src/assets/logo.png
--------------------------------------------------------------------------------
/src/components/Actions/Create/Composer.vue:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/src/components/Actions/Create/defaults.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Defaults - Default attributes for the Create view
3 | *
4 | * @return {Object} An object containing props and methods
5 | */
6 | export default () => {
7 | /**
8 | * Create View default validations
9 | */
10 | const composer = {
11 | parentPropKeys: ['resourceName', 'redirect', 'va'],
12 | componentPropKeys: ['title'],
13 | childrenAdapter: {
14 | placeHolder: 'placeHolder',
15 | source: 'label',
16 | type: 'type',
17 | },
18 | }
19 |
20 | return {
21 | composer,
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/components/Actions/Create/index.js:
--------------------------------------------------------------------------------
1 | import Create from './Composer'
2 |
3 | Create.install = function(Vue) {
4 | Vue.component(Create.name, Create)
5 | }
6 |
7 | export default Create
8 |
--------------------------------------------------------------------------------
/src/components/Actions/Edit/Composer.vue:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/src/components/Actions/Edit/defaults.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Defaults - Default attributes for the Edit view
3 | *
4 | * @return {Object} An object containing props and methods
5 | */
6 | export default () => {
7 | /**
8 | * Edit View default composer options
9 | */
10 | const composer = {
11 | parentPropKeys: ['resourceName', 'va'],
12 | componentPropKeys: ['title'],
13 | childrenAdapter: {
14 | placeHolder: 'placeHolder',
15 | source: 'label',
16 | type: 'type',
17 | },
18 | }
19 |
20 | return {
21 | composer,
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/components/Actions/Edit/index.js:
--------------------------------------------------------------------------------
1 | import Edit from './Composer'
2 |
3 | Edit.install = function(Vue) {
4 | Vue.component(Edit.name, Edit)
5 | }
6 |
7 | export default Edit
8 |
--------------------------------------------------------------------------------
/src/components/Actions/List/Composer.vue:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/src/components/Actions/List/defaults.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Defaults - Default attributes for the List view
3 | *
4 | * @return {Object} An object containing props and methods
5 | */
6 | export default () => {
7 | /**
8 | * List View default composer options
9 | */
10 | const composer = {
11 | parentPropKeys: [
12 | 'resourceName',
13 | 'resourceIdName',
14 | 'hasCreate',
15 | 'hasShow',
16 | 'hasEdit',
17 | 'title',
18 | 'va',
19 | ],
20 | componentPropKeys: ['title'],
21 | childrenAdapter: {
22 | alignContent: 'align',
23 | alignHeader: 'alignHeader',
24 | headerText: 'headerText',
25 | sortable: 'sortable',
26 | source: 'label',
27 | },
28 | }
29 |
30 | return {
31 | composer,
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/components/Actions/List/index.js:
--------------------------------------------------------------------------------
1 | import List from './Composer'
2 |
3 | List.install = function(Vue) {
4 | Vue.component(List.name, List)
5 | }
6 |
7 | export default List
8 |
--------------------------------------------------------------------------------
/src/components/Actions/Show/Composer.vue:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/src/components/Actions/Show/defaults.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Defaults - Default attributes for the Show view
3 | *
4 | * @return {Object} An object containing props and methods
5 | */
6 | export default () => {
7 | /**
8 | * Show View default composer options
9 | */
10 | const composer = {
11 | parentPropKeys: ['resourceIdName', 'resourceName', 'redirect', 'va'],
12 | componentPropKeys: ['title'],
13 | childrenAdapter: { placeHolder: 'placeHolder', source: 'label' },
14 | }
15 |
16 | return {
17 | composer,
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/Actions/Show/index.js:
--------------------------------------------------------------------------------
1 | import Show from './Composer'
2 |
3 | Show.install = function(Vue) {
4 | Vue.component(Show.name, Show)
5 | }
6 |
7 | export default Show
8 |
--------------------------------------------------------------------------------
/src/components/Actions/index.js:
--------------------------------------------------------------------------------
1 | import Create from './Create'
2 | import Edit from './Edit'
3 | import List from './List'
4 | import Show from './Show'
5 |
6 | export { Create, Edit, List, Show }
7 |
--------------------------------------------------------------------------------
/src/components/Admin/index.js:
--------------------------------------------------------------------------------
1 | import Admin from './src/Composer'
2 |
3 | Admin.install = function(Vue) {
4 | Vue.component(Admin.name, Admin)
5 | }
6 |
7 | export default Admin
8 |
--------------------------------------------------------------------------------
/src/components/Admin/src/Alerts.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 | {{ snackbar.text }}
9 |
15 | {{ UI_CONTENT.SNACKBAR_CLOSE_TEXT }}
16 |
17 |
18 |
19 |
20 |
47 |
--------------------------------------------------------------------------------
/src/components/Admin/src/Authenticated.vue:
--------------------------------------------------------------------------------
1 |
51 |
--------------------------------------------------------------------------------
/src/components/Admin/src/Composer.vue:
--------------------------------------------------------------------------------
1 |
50 |
--------------------------------------------------------------------------------
/src/components/Admin/src/Unauthenticated.vue:
--------------------------------------------------------------------------------
1 |
27 |
--------------------------------------------------------------------------------
/src/components/Admin/src/defaults.js:
--------------------------------------------------------------------------------
1 | import UI_CONTENT from '@constants/ui.content.default'
2 | import {
3 | AppLayout,
4 | AuthLayout,
5 | HomeLayout,
6 | UnauthorizedLayout,
7 | } from '@components/Layouts'
8 | import { SimpleSidebar } from '@components/UiComponents'
9 | import alertsModule from '@store/modules/alerts'
10 | import entitiesModule from '@store/modules/entities'
11 | import requestsModule from '@store/modules/requests'
12 | import resourceModule from '@store/modules/resources'
13 |
14 | /**
15 | * Defaults - Default attributes for the Admin component
16 | *
17 | * @return {Object} An object containing default attributes
18 | */
19 | export default () => {
20 | const appLayout = AppLayout
21 | const authLayout = AuthLayout
22 | const homeLayout = HomeLayout
23 | const sidebar = SimpleSidebar
24 | const title = UI_CONTENT.MAIN_TOOLBAR_TITLE
25 | const unauthorized = UnauthorizedLayout
26 |
27 | const createUnauthenticatedRoutes = anAuthLayout => [
28 | {
29 | path: '/login',
30 | name: 'login',
31 | component: anAuthLayout || authLayout,
32 | props: {},
33 | },
34 | ]
35 |
36 | const createUnauthorizedRoutes = anUnauthorizedLayout => {
37 | return [
38 | {
39 | path: '/unauthorized',
40 | name: 'unauthorized',
41 | component: anUnauthorizedLayout || unauthorized,
42 | },
43 | ]
44 | }
45 |
46 | const createSiteRoutes = ({ homeLayout: aHomeLayout }) => [
47 | {
48 | path: '/',
49 | name: 'home',
50 | component: aHomeLayout || homeLayout,
51 | props: {},
52 | },
53 | ]
54 |
55 | return {
56 | props: {
57 | appLayout,
58 | authLayout,
59 | homeLayout,
60 | sidebar,
61 | title,
62 | unauthorized,
63 | },
64 | args: {
65 | alertsModule,
66 | createSiteRoutes,
67 | createUnauthenticatedRoutes,
68 | entitiesModule,
69 | requestsModule,
70 | resourceModule,
71 | createUnauthorizedRoutes,
72 | },
73 | }
74 | }
75 |
76 | /**
77 | * Defaults - Default attributes for the Authenticated component
78 | *
79 | * @return {Object} An object containing default attributes
80 | */
81 |
82 | export const authenticatedDefaults = {
83 | args: {},
84 | }
85 |
86 | /**
87 | * Defaults - Default attributes for the Unauthenticated component
88 | *
89 | * @return {Object} An object containing default attributes
90 | */
91 |
92 | export const unauthenticatedDefaults = {
93 | args: {},
94 | }
95 |
--------------------------------------------------------------------------------
/src/components/Core/index.js:
--------------------------------------------------------------------------------
1 | import Core from './src/Core'
2 |
3 | Core.install = function(Vue) {
4 | Vue.component(Core.name, Core)
5 | }
6 |
7 | export default Core
8 |
--------------------------------------------------------------------------------
/src/components/Core/src/Core.vue:
--------------------------------------------------------------------------------
1 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/components/Layouts/index.js:
--------------------------------------------------------------------------------
1 | import AppLayout from './src/AppLayout'
2 | import AuthLayout from './src/AuthLayout'
3 | import HomeLayout from './src/HomeLayout'
4 | import UnauthorizedLayout from './src/UnauthorizedLayout'
5 |
6 | export { AppLayout, AuthLayout, HomeLayout, UnauthorizedLayout }
7 |
--------------------------------------------------------------------------------
/src/components/Layouts/src/AppLayout/AppLayout.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 | {{ title }}
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
54 |
55 |
56 |
75 |
--------------------------------------------------------------------------------
/src/components/Layouts/src/AppLayout/index.js:
--------------------------------------------------------------------------------
1 | import AppLayout from './AppLayout'
2 |
3 | AppLayout.install = function(Vue) {
4 | Vue.component(AppLayout.name, AppLayout)
5 | }
6 |
7 | export default AppLayout
8 |
--------------------------------------------------------------------------------
/src/components/Layouts/src/AuthLayout/defaults.js:
--------------------------------------------------------------------------------
1 | import logo from '@assets/logo.png'
2 | import UI_CONTENT from '@constants/ui.content.default'
3 |
4 | /**
5 | * Defaults - Default attributes for the Auth view
6 | *
7 | * @return {Object} An object containing props and methods
8 | */
9 | export default () => {
10 | const authFormTitle = AuthFormTitle
11 | const authFooter = AuthFooter
12 | const authMainContent = AuthContent
13 |
14 | const usernameRules = [
15 | v => !!v || UI_CONTENT.AUTH_ALERT_EMAIL_REQUIRED,
16 | v =>
17 | /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/.test(v) ||
18 | UI_CONTENT.AUTH_ALERT_INVALID_EMAIL,
19 | ]
20 | const passwordRules = [v => !!v || UI_CONTENT.AUTH_ALERT_PASSWORD_REQUIRED]
21 |
22 | return {
23 | props: {
24 | authFormTitle,
25 | authFooter,
26 | authMainContent,
27 | usernameRules,
28 | passwordRules,
29 | },
30 | }
31 | }
32 |
33 | const AuthFormTitle = {
34 | name: 'AuthFormTitle',
35 | // eslint-disable-next-line
36 | render: function(h) {
37 | return (
38 |
39 |
{UI_CONTENT.AUTH_CONTAINER_TITLE}
40 |
41 | )
42 | },
43 | }
44 |
45 | const AuthContent = {
46 | name: 'AuthContent',
47 | // eslint-disable-next-line
48 | render: function(h) {
49 | return (
50 |
51 |
52 |
Welcome to Vue-Admin
53 |
54 | )
55 | },
56 | }
57 |
58 | const AuthFooter = {
59 | name: 'AuthFooter',
60 | // eslint-disable-next-line
61 | render: function(h) {
62 | return null
63 | },
64 | }
65 |
66 | const styles = {
67 | authFormContainer: {
68 | margin: '10px',
69 | },
70 | authFormTitle: {
71 | fontWeight: '300',
72 | },
73 | vaImg: {
74 | margin: 'auto',
75 | width: '140px',
76 | },
77 | }
78 |
--------------------------------------------------------------------------------
/src/components/Layouts/src/AuthLayout/index.js:
--------------------------------------------------------------------------------
1 | import AuthLayout from './AuthLayout'
2 |
3 | AuthLayout.install = function(Vue) {
4 | Vue.component(AuthLayout.name, AuthLayout)
5 | }
6 |
7 | export default AuthLayout
8 |
--------------------------------------------------------------------------------
/src/components/Layouts/src/HomeLayout/HomeLayout.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Welcome to Vue-Admin
8 |
9 |
10 |
11 |
12 |
13 |
26 |
27 |
42 |
--------------------------------------------------------------------------------
/src/components/Layouts/src/HomeLayout/index.js:
--------------------------------------------------------------------------------
1 | import HomeLayout from './HomeLayout'
2 |
3 | HomeLayout.install = function(Vue) {
4 | Vue.component(HomeLayout.name, HomeLayout)
5 | }
6 |
7 | export default HomeLayout
8 |
--------------------------------------------------------------------------------
/src/components/Layouts/src/UnauthorizedLayout/UnauthorizedLayout.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
19 |
20 |
27 | {{ BUTTON_GO_BACK_CONTENT }}
28 |
29 |
30 |
31 |
32 |
33 |
63 |
--------------------------------------------------------------------------------
/src/components/Layouts/src/UnauthorizedLayout/index.js:
--------------------------------------------------------------------------------
1 | import UnauthorizedLayout from './UnauthorizedLayout'
2 |
3 | UnauthorizedLayout.install = function(Vue) {
4 | Vue.component(UnauthorizedLayout.name, UnauthorizedLayout)
5 | }
6 |
7 | export default UnauthorizedLayout
8 |
--------------------------------------------------------------------------------
/src/components/Resource/index.js:
--------------------------------------------------------------------------------
1 | import Resource from './src/Composer'
2 |
3 | Resource.install = function(Vue) {
4 | Vue.component(Resource.name, Resource)
5 | }
6 |
7 | export default Resource
8 |
--------------------------------------------------------------------------------
/src/components/Resource/src/Composer.vue:
--------------------------------------------------------------------------------
1 |
34 |
--------------------------------------------------------------------------------
/src/components/UiComponents/DateField/index.js:
--------------------------------------------------------------------------------
1 | import DateField from './src/DateField.vue'
2 |
3 | DateField.install = function(Vue) {
4 | Vue.component(DateField.name, DateField)
5 | }
6 |
7 | export default DateField
8 |
--------------------------------------------------------------------------------
/src/components/UiComponents/DateField/src/DateField.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
18 |
19 |
20 |
21 |
22 |
23 |
103 |
--------------------------------------------------------------------------------
/src/components/UiComponents/DateField/src/defaults.js:
--------------------------------------------------------------------------------
1 | import { handleEmptyProp } from '@handlers/error/src'
2 |
3 | /**
4 | * Defaults - Default attributes for the DateField component
5 | *
6 | * @return {Object} An object containing props and methods
7 | */
8 | export default () => {
9 | const component = 'DateField'
10 |
11 | function _vDatePickerProps() {
12 | return { noTitle: true }
13 | }
14 |
15 | function _vMenuProps() {
16 | return {}
17 | }
18 |
19 | /**
20 | * DateField default props
21 | */
22 | const disabled = false
23 | const format = handleEmptyProp({ prop: 'format', at: component })
24 | const name = 'va-date-input'
25 | const parse = handleEmptyProp({ prop: 'parse', at: component })
26 | const readonly = true
27 | const vDatePickerProps = _vDatePickerProps
28 | const vMenuProps = _vMenuProps
29 |
30 | return {
31 | props: {
32 | disabled,
33 | format,
34 | name,
35 | parse,
36 | readonly,
37 | vDatePickerProps,
38 | vMenuProps,
39 | },
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/UiComponents/DeleteButton/DeleteButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 | {{ content.deleteButton }}
13 |
14 |
15 |
16 | Delete
17 |
18 |
19 |
20 |
87 |
--------------------------------------------------------------------------------
/src/components/UiComponents/DeleteButton/index.js:
--------------------------------------------------------------------------------
1 | import DeleteButton from './DeleteButton'
2 |
3 | DeleteButton.install = function(Vue) {
4 | Vue.component(DeleteButton.name, DeleteButton)
5 | }
6 |
7 | export default DeleteButton
8 |
--------------------------------------------------------------------------------
/src/components/UiComponents/EditButton/EditButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 | {{
12 | UI_CONTENT.RESOURCE_EDIT_BUTTON
13 | }}
14 |
15 |
16 | Edit
17 |
18 |
19 |
20 |
79 |
--------------------------------------------------------------------------------
/src/components/UiComponents/EditButton/index.js:
--------------------------------------------------------------------------------
1 | import EditButton from './EditButton'
2 |
3 | EditButton.install = function(Vue) {
4 | Vue.component(EditButton.name, EditButton)
5 | }
6 |
7 | export default EditButton
8 |
--------------------------------------------------------------------------------
/src/components/UiComponents/Sidebar/Sidebar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
11 |
--------------------------------------------------------------------------------
/src/components/UiComponents/Sidebar/SidebarAction.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ icon }}
5 |
6 |
7 |
8 | {{ title }}
9 |
10 |
11 |
12 |
13 |
23 |
--------------------------------------------------------------------------------
/src/components/UiComponents/Sidebar/SidebarHeading.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ title }}
7 |
8 |
9 | {{ subTitle }}
10 |
11 |
12 |
13 |
14 |
15 |
40 |
41 |
50 |
--------------------------------------------------------------------------------
/src/components/UiComponents/Sidebar/SidebarLink.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ icon }}
5 |
6 |
7 |
8 | {{ title }}
9 |
10 |
11 |
12 |
13 |
34 |
--------------------------------------------------------------------------------
/src/components/UiComponents/Sidebar/SidebarNode.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 | {{ title }}
14 |
15 |
16 |
17 |
18 |
19 |
20 |
36 |
--------------------------------------------------------------------------------
/src/components/UiComponents/Sidebar/SimpleSidebar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
19 |
20 |
21 |
28 |
29 |
36 |
37 |
38 |
39 |
40 |
95 |
--------------------------------------------------------------------------------
/src/components/UiComponents/Sidebar/defaults.js:
--------------------------------------------------------------------------------
1 | import { VListItemAvatar, VAvatar, VIcon } from 'vuetify/lib'
2 | import { Types as ResourcesTypes } from '@store/modules/resources'
3 |
4 | /**
5 | * Defaults - Default attributes for the SimpleSidebar view
6 | *
7 | * @return {Object} An object containing props and methods
8 | */
9 | export default () => {
10 | const menuItems = [
11 | {
12 | icon: 'keyboard_arrow_up',
13 | 'icon-alt': 'keyboard_arrow_down',
14 | title: 'Resources',
15 | children: [],
16 | model: {},
17 | value: true,
18 | },
19 | ]
20 |
21 | const subscriptions = [
22 | action => (mutation, state) => {
23 | const { namespace, RESOURCES_ADD_ROUTE } = ResourcesTypes
24 | if (mutation.type === `${namespace}/${RESOURCES_ADD_ROUTE}`) {
25 | const currentRoutes = state.resources.routes.map(route => {
26 | return { icon: 'list', title: route.name, link: route.path }
27 | })
28 | action(currentRoutes)
29 | }
30 | },
31 | ]
32 |
33 | return {
34 | data: {
35 | menuItems,
36 | },
37 | props: {
38 | subscriptions,
39 | },
40 | }
41 | }
42 |
43 | /**
44 | * Defaults - Default attributes for the SidebarHeading view
45 | *
46 | * @return {Object} An object containing props and methods
47 | */
48 | export const sidebarHeadingDefaults = () => {
49 | const avatar = Avatar
50 | const avatarProps = {
51 | color: 'teal',
52 | content: AccountIcon,
53 | }
54 | const title = 'Menu'
55 | const subTitle = ''
56 |
57 | return {
58 | props: {
59 | avatar,
60 | avatarProps,
61 | title,
62 | subTitle,
63 | },
64 | }
65 | }
66 |
67 | const AccountIcon = {
68 | name: 'AccountIcon',
69 | // eslint-disable-next-line
70 | render: function(h) {
71 | return account_circle
72 | },
73 | }
74 |
75 | const Avatar = {
76 | name: 'SidebarHeadingAvatar',
77 | functional: true,
78 | // eslint-disable-next-line
79 | render: function(h, context) {
80 | const { props } = context
81 | return (
82 |
83 | {h(props.content)}
84 |
85 | )
86 | },
87 | }
88 |
--------------------------------------------------------------------------------
/src/components/UiComponents/Sidebar/index.js:
--------------------------------------------------------------------------------
1 | import Sidebar from './Sidebar'
2 | import SidebarAction from './SidebarAction'
3 | import SidebarHeading from './SidebarHeading'
4 | import SidebarLink from './SidebarLink'
5 | import SidebarNode from './SidebarNode'
6 |
7 | export { Sidebar, SidebarAction, SidebarHeading, SidebarLink, SidebarNode }
8 |
--------------------------------------------------------------------------------
/src/components/UiComponents/SimpleText/index.js:
--------------------------------------------------------------------------------
1 | import SimpleText from './src/SimpleText'
2 |
3 | SimpleText.install = function(Vue) {
4 | Vue.component(SimpleText.name, SimpleText)
5 | }
6 |
7 | export default SimpleText
8 |
--------------------------------------------------------------------------------
/src/components/UiComponents/SimpleText/src/SimpleText.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ parsedContent }}
5 |
6 |
7 |
8 |
9 |
43 |
--------------------------------------------------------------------------------
/src/components/UiComponents/Spinner/Spinner.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
53 |
75 |
--------------------------------------------------------------------------------
/src/components/UiComponents/Spinner/index.js:
--------------------------------------------------------------------------------
1 | import Spinner from './Spinner'
2 |
3 | Spinner.install = function(Vue) {
4 | Vue.component(Spinner.name, Spinner)
5 | }
6 |
7 | export default Spinner
8 |
--------------------------------------------------------------------------------
/src/components/UiComponents/TextField/index.js:
--------------------------------------------------------------------------------
1 | import TextField from './src/TextField'
2 |
3 | TextField.install = function(Vue) {
4 | Vue.component(TextField.name, TextField)
5 | }
6 |
7 | export default TextField
8 |
--------------------------------------------------------------------------------
/src/components/UiComponents/TextField/src/TextField.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
45 |
--------------------------------------------------------------------------------
/src/components/UiComponents/index.js:
--------------------------------------------------------------------------------
1 | import DateField from './DateField'
2 | import SimpleSidebar from './Sidebar/SimpleSidebar'
3 | import DeleteButton from './DeleteButton'
4 | import EditButton from './EditButton'
5 | import TextField from './TextField'
6 | import SimpleText from './SimpleText'
7 | import Spinner from './Spinner'
8 | import {
9 | Sidebar,
10 | SidebarLink,
11 | SidebarNode,
12 | SidebarAction,
13 | SidebarHeading,
14 | } from './Sidebar'
15 |
16 | export {
17 | DateField,
18 | SimpleSidebar,
19 | DeleteButton,
20 | EditButton,
21 | TextField,
22 | SimpleText,
23 | Spinner,
24 | Sidebar,
25 | SidebarLink,
26 | SidebarNode,
27 | SidebarAction,
28 | SidebarHeading,
29 | }
30 |
--------------------------------------------------------------------------------
/src/constants/error.messages.js:
--------------------------------------------------------------------------------
1 | import templates from '@templates'
2 |
3 | const buildMessage = templates('en.error')
4 |
5 | function parseErrorDetails(details) {
6 | return details.map(detail => `\t${detail.message}`).join('\n')
7 | }
8 |
9 | export default {
10 | UNDEFINED_PROPERTY: {
11 | with: ({ prop, at }) => {
12 | return buildMessage('UNDEFINED_PROPERTY', { prop, at })
13 | },
14 | },
15 | INVALID_SCHEMA: {
16 | with: ({ prop, at, details }) => {
17 | const _details = parseErrorDetails(details)
18 | return buildMessage('INVALID_SCHEMA', { prop, at, details: _details })
19 | },
20 | },
21 | }
22 |
--------------------------------------------------------------------------------
/src/constants/ui.elements.props.js:
--------------------------------------------------------------------------------
1 | export default {
2 | rowsPerPage: 10,
3 | dateInputMaxWidthNoLandscape: '290px',
4 | dateInputMaxWidthLandscape: '580px',
5 | }
6 |
--------------------------------------------------------------------------------
/src/handlers/error/src/index.js:
--------------------------------------------------------------------------------
1 | import ERROR_MESSAGES from '@constants/error.messages'
2 | import { validateSchema } from '@validators'
3 |
4 | /**
5 | * handleEmptyProp - Given a prop, throws an error with the proper user
6 | * feedback
7 | *
8 | * @param {String} prop The property of a component
9 | * @param {String} at The name of the the error is coming from. This param is
10 | * useful add references to specific documentation on error
11 | * messages.
12 | */
13 | export const handleEmptyProp = ({ prop, at }) => () => {
14 | const { UNDEFINED_PROPERTY } = ERROR_MESSAGES
15 | throw new Error(UNDEFINED_PROPERTY.with({ prop, at }))
16 | }
17 |
18 | /**
19 | * handleSchemaValidation - Given a schema, a prop and a component name, returns
20 | * a validation result or throws an error
21 | *
22 | * @param {Object} schema A property to validate
23 | * @param {String} prop The name of the property
24 | * @param {String} at The name of the component
25 | *
26 | * @return {Object} The given property
27 | */
28 | export const handleSchemaValidation = ({ schema, prop, at }) => {
29 | const validation = validateSchema(prop, schema)
30 | if (validation.error) {
31 | const { INVALID_SCHEMA } = ERROR_MESSAGES
32 | const { details } = validation.error
33 | throw new Error(INVALID_SCHEMA.with({ prop, at, details }))
34 | }
35 | return validation
36 | }
37 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import { Create, Edit, List, Show } from '@components/Actions'
2 | import {
3 | DeleteButton,
4 | EditButton,
5 | Sidebar,
6 | SidebarLink,
7 | SidebarNode,
8 | SidebarAction,
9 | SidebarHeading,
10 | } from '@components/UiComponents'
11 | import Admin from '@components/Admin'
12 | import AuthTypes from '@va-auth/types'
13 | import Resource from '@components/Resource'
14 | import { Types as AlertTypes } from '@store/modules/alerts'
15 | import { UnauthorizedLayout } from '@components/Layouts'
16 | import { name, description, version } from '../package.json'
17 |
18 | const components = [
19 | Admin,
20 | Resource,
21 | Create,
22 | DeleteButton,
23 | Edit,
24 | EditButton,
25 | List,
26 | Show,
27 | Sidebar,
28 | SidebarLink,
29 | SidebarNode,
30 | SidebarAction,
31 | SidebarHeading,
32 | ]
33 |
34 | const install = function(Vue) {
35 | components.forEach(component => {
36 | Vue.component(component.name, component)
37 | })
38 | }
39 |
40 | if (typeof window !== 'undefined' && window.Vue) {
41 | install(window.Vue)
42 | }
43 |
44 | export {
45 | // Package data
46 | name,
47 | description,
48 | version,
49 | // Exports Actions components
50 | Create,
51 | DeleteButton,
52 | Edit,
53 | EditButton,
54 | List,
55 | Show,
56 | // Exports Core components
57 | Admin,
58 | Resource,
59 | // Exports Layouts
60 | UnauthorizedLayout,
61 | // Exports Ui Components
62 | Sidebar,
63 | SidebarLink,
64 | SidebarNode,
65 | SidebarAction,
66 | SidebarHeading,
67 | // Exports Types
68 | AlertTypes,
69 | AuthTypes,
70 | }
71 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import '@babel/polyfill'
2 | import 'material-design-icons-iconfont/dist/material-design-icons.css'
3 | import './styles/main.sass'
4 | import './assets/fonts/Montserrat/Montserrat.css'
5 | import Vue from 'vue'
6 | import Vuex from 'vuex'
7 | import VueRouter from 'vue-router'
8 | import vuetify from '@plugins/vuetify'
9 | import { subscriptionsPlugin } from '@plugins/vuex'
10 | import createLogger from 'vuex/dist/logger'
11 |
12 | Vue.config.productionTip = false
13 |
14 | Vue.use(VueRouter)
15 | const routes = [{}]
16 | const router = new VueRouter(routes)
17 |
18 | import App from '@demo/App.vue'
19 |
20 | Vue.use(Vuex)
21 | const store = new Vuex.Store({
22 | plugins: [subscriptionsPlugin, createLogger()],
23 | })
24 |
25 | const app = new Vue({
26 | router,
27 | store,
28 | vuetify,
29 | render: h => h(App),
30 | }).$mount('#app')
31 |
32 | if (window.Cypress) {
33 | // exposes the app, only available during E2E tests
34 | window.app = app
35 | }
36 |
--------------------------------------------------------------------------------
/src/plugins/vuetify.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuetify from 'vuetify/lib'
3 | import '@mdi/font/css/materialdesignicons.css'
4 | import 'vuetify/dist/vuetify.min.css'
5 |
6 | Vue.use(Vuetify)
7 |
8 | export default new Vuetify({
9 | icons: {
10 | iconfont: 'mdi',
11 | },
12 | })
13 |
--------------------------------------------------------------------------------
/src/plugins/vuex/index.js:
--------------------------------------------------------------------------------
1 | import { subscriptions } from './subscriptions'
2 |
3 | export const subscriptionsPlugin = store => {
4 | subscriptions.forEach(subscription => store.subscribe(subscription(store)))
5 | }
6 |
--------------------------------------------------------------------------------
/src/plugins/vuex/subscriptions.js:
--------------------------------------------------------------------------------
1 | import AuthTypes from '@va-auth/types'
2 | import { Types as AlertTypes } from '@store/modules/alerts'
3 | import UI_CONTENT from '@constants/ui.content.default'
4 |
5 | const {
6 | namespace: authNamespace,
7 | AUTH_LOGIN_FAILURE,
8 | AUTH_LOGIN_SUCCESS,
9 | } = AuthTypes
10 | const { namespace: alertsNamespace, ALERTS_SHOW_SNACKBAR } = AlertTypes
11 |
12 | const loginAlert = store => mutation => {
13 | const mutationSubscription = `${authNamespace}/${AUTH_LOGIN_SUCCESS}`
14 | if (mutation.type === mutationSubscription) {
15 | const mutationCommit = `${alertsNamespace}/${ALERTS_SHOW_SNACKBAR}`
16 | const args = {
17 | color: UI_CONTENT.SNACKBAR_SUCCESS_COLOR,
18 | text: username =>
19 | UI_CONTENT.AUTH_SNACKBAR_LOGIN_SUCCESS.with({ username }),
20 | }
21 | const { email: username } = mutation.payload
22 | const text = args.text(username)
23 | store.commit(mutationCommit, { ...args, text })
24 | }
25 | }
26 |
27 | const failedAuthentication = store => mutation => {
28 | const mutationSubscription = `${authNamespace}/${AUTH_LOGIN_FAILURE}`
29 | if (mutation.type === mutationSubscription) {
30 | const mutationCommit = `${alertsNamespace}/${ALERTS_SHOW_SNACKBAR}`
31 | const args = {
32 | color: UI_CONTENT.SNACKBAR_ERROR_COLOR,
33 | text: UI_CONTENT.AUTH_SNACKBAR_INVALID_USER_PASSWORD,
34 | }
35 | store.commit(mutationCommit, args)
36 | }
37 | }
38 |
39 | export const subscriptions = [failedAuthentication, loginAlert]
40 |
--------------------------------------------------------------------------------
/src/router/auth.utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Auth Utils - A function used to create utilities
3 | *
4 | * @param {Object} store The global Vuex store variable
5 | *
6 | * @return {Object} A set of functions to be used for Authentication
7 | */
8 | export default ({ store, types }) => {
9 | const { namespace, AUTH_IS_AUTHENTICATED, AUTH_GET_USER } = types
10 | return {
11 | /**
12 | * checkAuthentication - Indicates whether the user is authenticated or not.
13 | *
14 | * @return {Boolean}
15 | */
16 | isAuthenticated() {
17 | return store.getters[`${namespace}/${AUTH_IS_AUTHENTICATED}`]
18 | },
19 |
20 | /**
21 | * getUser - Returns the user object from the store
22 | *
23 | * @return {Object} The current logged user object
24 | */
25 | getUser() {
26 | return store.getters[`${namespace}/${AUTH_GET_USER}`]
27 | },
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | const Router = {}
2 |
3 | Router.redirect = ({ router, resource, view, id }) => {
4 | ;({
5 | list: () => {
6 | router.push({ name: `${resource}/${view}` })
7 | },
8 | show: () => {
9 | router.push({ name: `${resource}/${view}`, params: { id } })
10 | },
11 | }[view]())
12 | }
13 |
14 | export default Router
15 |
--------------------------------------------------------------------------------
/src/router/route.hooks.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Create Route Hooks - A function used to create route hooks
3 | *
4 | * @param {Boolean} isPublic Indicates whether or not a route needs authentication to be visited
5 | * @param {Array} permissions An array of route permissions as Strings
6 | * @param {Object} store A group of store auth actions
7 | * @param {String} userPermissionsField The name of the permissions field in a user object
8 | * @return {type} An object with hook functions
9 | */
10 | export default ({ isPublic, permissions, store, userPermissionsField }) => {
11 | const requiresAuth = !isPublic
12 |
13 | const beforeEnter = (to, from, next) => {
14 | if (requiresAuth) {
15 | // It's a private route
16 |
17 | const isAuthenticated = store.isAuthenticated()
18 | if (!isAuthenticated) {
19 | // User is not authenticated
20 | next({
21 | path: '/login',
22 | params: { nextUrl: to.fullPath },
23 | })
24 | } else {
25 | // User is authenticated
26 | if (permissions.length > 0) {
27 | // Route has permissions restriction
28 |
29 | const user = store.getUser()
30 | const { [userPermissionsField]: userPermissions } = user
31 | const userHasPermissions = permissions.some(permission => {
32 | return userPermissions.indexOf(permission) > -1
33 | })
34 | if (userHasPermissions) {
35 | // User is authenticated and has route permissions
36 | next()
37 | } else {
38 | // User is authenticated but does not have route permissions
39 | next('/unauthorized')
40 | }
41 | } else {
42 | // Route has no permissions restriction
43 | next()
44 | }
45 | }
46 | } else {
47 | // It's a public route
48 | next()
49 | }
50 | }
51 |
52 | return {
53 | beforeEnter,
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/store/modules/alerts.js:
--------------------------------------------------------------------------------
1 | export const Types = {
2 | namespace: 'alerts',
3 |
4 | ALERTS_HIDE_SNACKBAR: 'ALERTS_HIDE_SNACKBAR',
5 | ALERTS_SHOW_SNACKBAR: 'ALERTS_SHOW_SNACKBAR',
6 |
7 | ALERTS_GET_SNACKBAR_STATUS: 'ALERTS_GET_SNACKBAR_STATUS',
8 | }
9 |
10 | export default {
11 | namespaced: true,
12 | state: {
13 | snackbar: {
14 | color: '',
15 | isVisible: false,
16 | text: '',
17 | },
18 | },
19 | mutations: {
20 | [Types.ALERTS_HIDE_SNACKBAR](state) {
21 | state.snackbar.color = ''
22 | state.snackbar.text = ''
23 | state.snackbar.isVisible = false
24 | },
25 | [Types.ALERTS_SHOW_SNACKBAR](state, { color, text }) {
26 | state.snackbar.color = color
27 | state.snackbar.text = text
28 | state.snackbar.isVisible = true
29 | },
30 | },
31 | getters: {
32 | [Types.ALERTS_GET_SNACKBAR_STATUS]: state => state.snackbar,
33 | },
34 | }
35 |
--------------------------------------------------------------------------------
/src/store/modules/entities.js:
--------------------------------------------------------------------------------
1 | export const Types = {
2 | namespace: 'entities',
3 |
4 | ENTITIES_CREATE_FORM: 'ENTITIES_CREATE_FORM',
5 | ENTITIES_UPDATE_FORM: 'ENTITIES_UPDATE_FORM',
6 |
7 | ENTITIES_GET_ENTITY: 'ENTITIES_GET_ENTITY',
8 | }
9 |
10 | const initForm = (state, { formType, entity }) => {
11 | state[formType] = state[formType] || {}
12 | state[formType][entity] = state[formType][entity] || {}
13 | }
14 |
15 | export default {
16 | namespaced: true,
17 | state: {},
18 | mutations: {
19 | [`${Types.ENTITIES_CREATE_FORM}`]: initForm,
20 | [`${Types.ENTITIES_UPDATE_FORM}`](
21 | state,
22 | { formType, entity, resourceKey, value }
23 | ) {
24 | initForm(state, { formType, entity })
25 | state[formType][entity][resourceKey] = value
26 | },
27 | },
28 | getters: {
29 | [`${Types.ENTITIES_GET_ENTITY}`]: state => state,
30 | },
31 | }
32 |
--------------------------------------------------------------------------------
/src/store/modules/index.js:
--------------------------------------------------------------------------------
1 | import createCrudModule from './crud'
2 |
3 | export { createCrudModule }
4 |
--------------------------------------------------------------------------------
/src/store/modules/requests.js:
--------------------------------------------------------------------------------
1 | export const Types = {
2 | namespace: 'requests',
3 |
4 | REQUESTS_SET_LOADING: 'REQUESTS_SET_LOADING',
5 |
6 | REQUESTS_IS_LOADING: 'REQUESTS_IS_LOADING',
7 | }
8 |
9 | export default {
10 | namespaced: true,
11 | state: {
12 | isLoading: false,
13 | },
14 | mutations: {
15 | [`${Types.REQUESTS_SET_LOADING}`](state, payload) {
16 | state.isLoading = payload.isLoading
17 | },
18 | },
19 | getters: {
20 | [`${Types.REQUESTS_IS_LOADING}`]: state => state.isLoading,
21 | },
22 | }
23 |
--------------------------------------------------------------------------------
/src/store/modules/resources.js:
--------------------------------------------------------------------------------
1 | export const Types = {
2 | namespace: 'resources',
3 |
4 | RESOURCES_ADD_ROUTE: 'RESOURCES_ADD_ROUTE',
5 |
6 | RESOURCES_GET_ALL_ROUTES: 'RESOURCES_GET_ALL_ROUTES',
7 | }
8 |
9 | export default {
10 | namespaced: true,
11 | state: {
12 | routes: [],
13 | },
14 | mutations: {
15 | [Types.RESOURCES_ADD_ROUTE](
16 | { routes },
17 | { path, name, addedRouteCallback }
18 | ) {
19 | let matchingPathRouteIndex
20 | let newRoute = { path, name }
21 | routes.forEach(
22 | (route, index) =>
23 | route.name === name && (matchingPathRouteIndex = index)
24 | )
25 | if (matchingPathRouteIndex !== undefined) {
26 | routes[matchingPathRouteIndex] = newRoute
27 | } else {
28 | routes.push(newRoute)
29 | addedRouteCallback()
30 | }
31 | },
32 | },
33 | getters: {
34 | [Types.RESOURCES_GET_ALL_ROUTES]: state => state.routes,
35 | },
36 | }
37 |
--------------------------------------------------------------------------------
/src/store/utils/create.utils.js:
--------------------------------------------------------------------------------
1 | import {
2 | submitEntity,
3 | initEntity,
4 | updateEntity,
5 | getEntityForm,
6 | } from './common.utils'
7 | import { Types as CrudTypes } from '@store/modules/crud'
8 |
9 | /**
10 | * Create View Utils - A function used to create utilities
11 | *
12 | * @param {String} redirectView A view the router will redirect to on submit
13 | * @param {String} resourceIdName The name of the id of a resource
14 | * @param {String} resourceName The name of the resource
15 | * @param {Object} store The global Vuex store variable
16 | * @param {Object} router The global Vue router variable
17 | * @param {Object} parseResponses An object containing a parseSingle function
18 | * and a parseList function to be used on submit actions.
19 | *
20 | * @return {Object} A set of functions to be used in a Create form.
21 | */
22 | export default ({
23 | redirectView,
24 | resourceIdName,
25 | resourceName,
26 | store,
27 | router,
28 | parseResponses,
29 | }) => {
30 | return {
31 | /**
32 | * getEntityForm - Gets the current 'resourceName' entity. The value does not
33 | * exist until a user inputs data using 'updateEntity'.
34 | *
35 | * @return {Object} a 'resourceName' object with updated data from the form.
36 | */
37 | getEntityForm() {
38 | const formType = 'createForm'
39 | return getEntityForm({ store, resourceName, formType })
40 | },
41 |
42 | /**
43 | * updateEntity - Given a key and a value, updates the 'resourceName' entity
44 | * in the store.
45 | *
46 | * @param {String} resourceKey A 'resourceName' attribute key
47 | * @param {String} value A given value to be stored
48 | */
49 | updateEntity({ resourceKey, value }) {
50 | const formType = 'createForm'
51 | updateEntity({
52 | resourceKey,
53 | value,
54 | store,
55 | resourceName,
56 | formType,
57 | })
58 | },
59 |
60 | /**
61 | * initEntity - Init the 'resourceName' entity in the store.
62 | */
63 | initEntity() {
64 | const formType = 'createForm'
65 | initEntity({
66 | store,
67 | resourceName,
68 | formType,
69 | })
70 | },
71 |
72 | /**
73 | * submitEntity - Dispatchs a create request
74 | *
75 | * @return {Promise} A pending promise.
76 | */
77 | submitEntity() {
78 | const { VUEX_CRUD_PUT } = CrudTypes
79 | const actionType = VUEX_CRUD_PUT
80 | const actionTypeParams = { data: this.getEntityForm() }
81 | submitEntity({
82 | resourceName,
83 | actionType,
84 | actionTypeParams,
85 | store,
86 | router,
87 | redirectView,
88 | resourceIdName,
89 | parseResponses,
90 | })
91 | },
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/store/utils/list.utils.js:
--------------------------------------------------------------------------------
1 | import { fetchList, getList } from './common.utils'
2 |
3 | /**
4 | * List View Utils - A function used to create utilities
5 | *
6 | * @param {String} resourceName The name of the resource
7 | * @param {Object} store The global Vuex store variable
8 | *
9 | * @return {Object} A set of functions to be used in an Edit form.
10 | */
11 | export default ({ resourceName, store }) => {
12 | return {
13 | /**
14 | * fetchList - Fetchs a set of 'resourceName' using the Vuex Crud getters
15 | *
16 | * @return {Array} An array of 'resourceName' elements
17 | */
18 | fetchList() {
19 | return fetchList({ resourceName, store })
20 | },
21 |
22 | /**
23 | * getList - Gets a set of 'resourceName' elements from the store
24 | *
25 | * @return {Array} An array of 'resourceName' elements
26 | */
27 | getList() {
28 | return getList({ resourceName, store })
29 | },
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/store/utils/show.utils.js:
--------------------------------------------------------------------------------
1 | import { fetchEntity, getEntity } from './common.utils'
2 |
3 | /**
4 | * Show View Utils - A function used to create utilities
5 | *
6 | * @param {String} resourceName The name of the resource
7 | * @param {Object} store The global Vuex store variable
8 | * @param {Object} router The global Vue router variable
9 | *
10 | * @return {Object} A set of functions to be used in a Show form.
11 | */
12 | export default ({ resourceName, store, router }) => {
13 | return {
14 | /**
15 | * getEntity - Gets a 'resourceName' entity from the store.
16 | *
17 | * @return {Object} A 'resourceName' entity.
18 | */
19 | getEntity() {
20 | return getEntity({ router, resourceName, store })
21 | },
22 |
23 | /**
24 | * fetchEntity - Fetchs a single 'resourceName' element from the store.
25 | *
26 | * @return {Object} A fetched 'resourceName' entity.
27 | */
28 | fetchEntity() {
29 | return fetchEntity({ resourceName, router, store })
30 | },
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/styles/main.sass:
--------------------------------------------------------------------------------
1 | $font-stack: 'Montserrat', Helvetica, Arial
2 |
3 | body
4 | font: 100% $font-stack
5 |
6 | @import '~vuetify/src/styles/main.sass'
7 |
--------------------------------------------------------------------------------
/src/templates/src/en/error.json:
--------------------------------------------------------------------------------
1 | {
2 | "UNDEFINED_PROPERTY": "{prefix} It seems that the {prop} property is undefined.\n\n",
3 | "INVALID_SCHEMA": "{prefix} Some of the {prop} properties are invalid:\n{details}\n\n"
4 | }
5 |
--------------------------------------------------------------------------------
/src/templates/src/index.js:
--------------------------------------------------------------------------------
1 | const docsUrl = require('@/../package.json').directories.doc
2 |
3 | const errorTitle = '\n\nVueAdmin/{at}:\n\n'
4 | const errorFooter =
5 | '{errorMessage}\tTake a look at our documentation at {url}\n'
6 |
7 | // Component doc paths should be added here
8 | const componentsDocs = {
9 | Admin: '{docsUrl}/Admin/Admin.md#props',
10 | Resource: '{docsUrl}/Resource/Resource.md#props',
11 | DateField: '{docsUrl}/Ui-Components/DateField.md',
12 | }
13 |
14 | /**
15 | * withParams - Defines the interpolation symbol of a template
16 | */
17 | function withParams(key) {
18 | return `{${key}}`
19 | }
20 |
21 | /**
22 | * buildMessage - Given a message and a set of parameters, interpolates the
23 | * string to return another message
24 | *
25 | * @param {String} message A string containing a template message
26 | * @param {Object} args A set of properties to complete a template message
27 | *
28 | * @return {String} A message built with args
29 | */
30 | function buildMessage(message, args) {
31 | const paramKeys = Object.keys(args)
32 | return paramKeys.reduce((parsedMessage, paramKey) => {
33 | return parsedMessage.replace(withParams(paramKey), args[paramKey])
34 | }, message)
35 | }
36 |
37 | /**
38 | * Templates - Given the name of a resource, returns a builder function to
39 | * create error messages.
40 | *
41 | * @param {String} template A string containing the language and type of error
42 | * of a template, e.g.: 'en.error'
43 | *
44 | * @return {Function} A builder function of messageType
45 | */
46 | export default template => {
47 | const params = template.split('.')
48 | const language = params[0]
49 | const messageType = params[1]
50 | const messages = require(`./${language}/${messageType}.json`)
51 |
52 | /**
53 | * buildErrorMessage - Given a template constant and a set of params, returns
54 | * an error message
55 | *
56 | * @param {String} constant The name of a template message
57 | * @param {Object} messageParams An object containing params to fill a constant
58 | *
59 | * @return {String} An error message built with messageParams
60 | */
61 | function buildErrorMessage(constant, messageParams) {
62 | const { at } = messageParams
63 | const componentDoc = componentsDocs[at]
64 | const prefix = buildMessage(errorTitle, { at })
65 | const url = buildMessage(componentDoc, { docsUrl })
66 | Object.assign(messageParams, { prefix })
67 | const errorMessage = buildMessage(messages[constant], messageParams)
68 |
69 | return buildMessage(errorFooter, { errorMessage, url })
70 | }
71 |
72 | const messageTypes = {
73 | error: buildErrorMessage,
74 | // newMessages: buildNewMessage
75 | }
76 |
77 | return messageTypes[messageType]
78 | }
79 |
--------------------------------------------------------------------------------
/src/va-auth/src/store/index.js:
--------------------------------------------------------------------------------
1 | import createActions from './modules/actions'
2 | import createGetters from './modules/getters'
3 | import createMutations from './modules/mutations'
4 | import createState from './modules/state'
5 | import Types from '../types'
6 |
7 | /**
8 | * Create Auth Module - Given a set of data, creates a vuex auth module and
9 | * calls the store to get it registered.
10 | *
11 | * @param {Function} client An http client
12 | * @param {String} moduleName The name of the auth module
13 | * @param {Object} store The global Vuex store variable
14 | */
15 | export default ({ client }) => {
16 | const types = Types
17 | return {
18 | namespaced: true,
19 | state: createState(),
20 | actions: createActions({ client, types }),
21 | mutations: createMutations({ types }),
22 | getters: createGetters({ types }),
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/va-auth/src/store/modules/actions.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Create Auth Module Actions - Given a set of data, creates the actions for an
3 | * auth store module
4 | *
5 | * @param {Object} client An http client
6 | * @param {Object} types An object containing all the auth Types
7 | *
8 | * @return {Object} The actions for the auth store
9 | */
10 | export default ({ client, types }) => {
11 | return {
12 | [types.AUTH_LOGIN_REQUEST]: ({ commit }, user) => {
13 | commit(types.AUTH_LOGIN_REQUEST)
14 | return client(types.AUTH_LOGIN_REQUEST, { ...user })
15 | .then(response => {
16 | commit(types.AUTH_LOGIN_SUCCESS, response)
17 | })
18 | .catch(error => {
19 | commit(types.AUTH_LOGIN_FAILURE, error)
20 | })
21 | },
22 |
23 | [types.AUTH_LOGOUT_REQUEST]: ({ commit }) => {
24 | commit(types.AUTH_LOGOUT_REQUEST)
25 | client(types.AUTH_LOGOUT_REQUEST)
26 | .then(() => {
27 | commit(types.AUTH_LOGOUT_SUCCESS)
28 | })
29 | .catch(error => {
30 | commit(types.AUTH_LOGIN_FAILURE, error)
31 | })
32 | },
33 |
34 | [types.AUTH_CHECK_REQUEST]: ({ commit }) => {
35 | commit(types.AUTH_CHECK_REQUEST)
36 | client(types.AUTH_CHECK_REQUEST)
37 | .then(response => {
38 | commit(types.AUTH_CHECK_SUCCESS, response)
39 | })
40 | .catch(error => {
41 | commit(types.AUTH_CHECK_FAILURE, error)
42 | })
43 | },
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/va-auth/src/store/modules/getters.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Create Auth Module Getters - Creates the getters for an auth store module
3 | *
4 | * @return {Object} The getters for the auth store
5 | */
6 | export default ({ types }) => {
7 | return {
8 | [types.AUTH_GET_STATUS]: state => state.status,
9 | [types.AUTH_IS_AUTHENTICATED]: state => state.isAuthenticated,
10 | [types.AUTH_GET_USER]: state => state.user,
11 | [types.AUTH_GET_ERROR]: state => state.error,
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/va-auth/src/store/modules/mutations.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Create Auth Module Mutations - Given a set of data, creates the mutations
3 | * for an auth store module
4 | *
5 | * @param {Object} types An object containing all the auth Types
6 | *
7 | * @return {Object} The mutations for the auth store
8 | */
9 | export default ({ types }) => {
10 | return {
11 | [types.AUTH_LOGIN_REQUEST]: state => {
12 | state.error = ''
13 | state.status = 'running'
14 | },
15 | [types.AUTH_LOGIN_SUCCESS]: (state, user) => {
16 | state.isAuthenticated = true
17 | state.status = 'idle'
18 | state.user = user
19 | },
20 | [types.AUTH_LOGIN_FAILURE]: (state, error) => {
21 | state.isAuthenticated = false
22 | state.error = error
23 | state.status = 'idle'
24 | },
25 |
26 | [types.AUTH_LOGOUT_REQUEST]: state => {
27 | state.error = ''
28 | state.status = 'running'
29 | },
30 | [types.AUTH_LOGOUT_SUCCESS]: state => {
31 | state.isAuthenticated = false
32 | state.status = 'idle'
33 | state.user = {}
34 | },
35 | [types.AUTH_LOGOUT_FAILURE]: (state, error) => {
36 | state.error = error
37 | state.status = 'idle'
38 | },
39 |
40 | [types.AUTH_CHECK_REQUEST]: state => {
41 | state.error = ''
42 | state.status = 'running'
43 | },
44 | [types.AUTH_CHECK_SUCCESS]: (state, user) => {
45 | state.isAuthenticated = true
46 | state.status = 'idle'
47 | state.user = user
48 | },
49 | [types.AUTH_CHECK_FAILURE]: (state, error) => {
50 | state.error = error
51 | state.isAuthenticated = false
52 | state.status = 'idle'
53 | },
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/va-auth/src/store/modules/state.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Create Auth Module State - Creates the state for an auth store module
3 | *
4 | * @return {Object} The state for the auth store
5 | */
6 | export default () => {
7 | return {
8 | error: '',
9 | isAuthenticated: false,
10 | status: 'idle',
11 | user: {},
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/va-auth/src/types/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | namespace: 'auth',
3 |
4 | /**
5 | * Actions and Mutations
6 | */
7 |
8 | AUTH_LOGIN_REQUEST: 'AUTH_LOGIN_REQUEST',
9 | AUTH_LOGIN_SUCCESS: 'AUTH_LOGIN_SUCCESS',
10 | AUTH_LOGIN_FAILURE: 'AUTH_LOGIN_FAILURE',
11 |
12 | AUTH_LOGOUT_REQUEST: 'AUTH_LOGOUT_REQUEST',
13 | AUTH_LOGOUT_SUCCESS: 'AUTH_LOGOUT_SUCCESS',
14 | AUTH_LOGOUT_FAILURE: 'AUTH_LOGOUT_FAILURE',
15 |
16 | AUTH_CHECK_REQUEST: 'AUTH_CHECK_REQUEST',
17 | AUTH_CHECK_SUCCESS: 'AUTH_CHECK_SUCCESS',
18 | AUTH_CHECK_FAILURE: 'AUTH_CHECK_FAILURE',
19 |
20 | /**
21 | * Getters
22 | */
23 |
24 | AUTH_GET_USER: 'AUTH_GET_USER',
25 | AUTH_GET_STATUS: 'AUTH_GET_STATUS',
26 | AUTH_GET_ERROR: 'AUTH_GET_ERROR',
27 | AUTH_IS_AUTHENTICATED: 'AUTH_IS_AUTHENTICATED',
28 | }
29 |
--------------------------------------------------------------------------------
/src/validators/src/components/resource.js:
--------------------------------------------------------------------------------
1 | import Joi from 'joi'
2 |
3 | function formatResult(result) {
4 | if (result.error) {
5 | const { name, details } = result.error
6 | const error = { name, details }
7 | return { error }
8 | }
9 | return result.value
10 | }
11 |
12 | /**
13 | * validateRedirect - Given an object checks it has a valid redirect schema
14 | *
15 | * @param {Object} redirect An object
16 | *
17 | * @return {Object} A Joi object with the validation review
18 | */
19 | export const validateRedirect = redirect => {
20 | const joiResult = Joi.object()
21 | .keys({
22 | views: Joi.object().keys({
23 | create: Joi.string().valid(['edit', 'list', 'show']),
24 | edit: Joi.string().valid(['create', 'list', 'show']),
25 | }),
26 | })
27 | .validate(redirect)
28 | return formatResult(joiResult)
29 | }
30 |
--------------------------------------------------------------------------------
/src/validators/src/index.js:
--------------------------------------------------------------------------------
1 | import { validateRedirect } from './components/resource'
2 |
3 | function validateSchema(prop, schema) {
4 | const validations = {
5 | redirect: validateRedirect,
6 | // add more properties here, binded with a validation function
7 | }
8 | return validations[prop](schema)
9 | }
10 |
11 | export { validateSchema }
12 |
--------------------------------------------------------------------------------
/tests/e2e/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: ['cypress'],
3 | env: {
4 | mocha: true,
5 | 'cypress/globals': true,
6 | },
7 | rules: {
8 | strict: 'off',
9 | },
10 | }
11 |
--------------------------------------------------------------------------------
/tests/e2e/factory/auth/index.js:
--------------------------------------------------------------------------------
1 | import { createUser } from '../users'
2 |
3 | export const createAuthResponse = (args = {}) => {
4 | const status = 200
5 | const user = createUser()
6 | const _args = {
7 | status,
8 | user,
9 | }
10 | return Object.assign({}, _args, args)
11 | }
12 |
13 | export const createCredentials = (args = {}) => {
14 | const username = 'dev@camba.coop'
15 | const password = '123456'
16 | const _args = {
17 | username,
18 | password,
19 | }
20 | return Object.assign({}, _args, args)
21 | }
22 |
--------------------------------------------------------------------------------
/tests/e2e/factory/env/index.js:
--------------------------------------------------------------------------------
1 | export const createApiUrl = ({ url, port, route }) => {
2 | const address = {}
3 | address.url = url || 'http://localhost'
4 | address.port = port || '8080'
5 | address.route = route || ''
6 | return `${address.url}:${address.port}/${address.route}`
7 | }
8 |
--------------------------------------------------------------------------------
/tests/e2e/factory/index.js:
--------------------------------------------------------------------------------
1 | import { createArticle, createMagazine, createAuthor } from './resources'
2 | import { createAuthResponse, createCredentials } from './auth'
3 | import { createUser } from './users'
4 | import {
5 | createInitialVuexStoreGetters,
6 | createInitialVuexStoreState,
7 | } from './store'
8 | import { createApiUrl } from './env'
9 |
10 | export default {
11 | // Auth builders
12 | createAuthResponse,
13 | createCredentials,
14 | // Entities builders
15 | createArticle,
16 | createMagazine,
17 | createAuthor,
18 | // Env builders
19 | createApiUrl,
20 | // Store builders
21 | createInitialVuexStoreState,
22 | createInitialVuexStoreGetters,
23 | // User builders
24 | createUser,
25 | }
26 |
--------------------------------------------------------------------------------
/tests/e2e/factory/resources/articles.js:
--------------------------------------------------------------------------------
1 | import { ipsum } from '../utils'
2 |
3 | export const createArticle = (args = {}) => {
4 | // Shortens the paragraph
5 | const title = ipsum.generateSentence()
6 | const content = ipsum.generateParagraph({ useStartingSentence: true })
7 | const _args = {
8 | title,
9 | content,
10 | }
11 | return Object.assign({}, _args, args)
12 | }
13 |
--------------------------------------------------------------------------------
/tests/e2e/factory/resources/authors.js:
--------------------------------------------------------------------------------
1 | import { ipsum, randomDate } from '../utils'
2 |
3 | export const createAuthor = (args = {}) => {
4 | // Shortens the paragraph
5 | const name = ipsum.generateWord()
6 | const lastname = ipsum.generateWord()
7 | const birthdate = randomDate(
8 | new Date(1970, 1, 1, 0, 0, 0, 0),
9 | new Date(1980, 1, 1, 0, 0, 0, 0)
10 | ).toISOString()
11 | const _args = {
12 | name,
13 | lastname,
14 | birthdate,
15 | }
16 | return Object.assign({}, _args, args)
17 | }
18 |
--------------------------------------------------------------------------------
/tests/e2e/factory/resources/index.js:
--------------------------------------------------------------------------------
1 | import { createArticle } from './articles'
2 | import { createMagazine } from './magazines'
3 | import { createAuthor } from './authors'
4 |
5 | export { createArticle, createMagazine, createAuthor }
6 |
--------------------------------------------------------------------------------
/tests/e2e/factory/resources/magazines.js:
--------------------------------------------------------------------------------
1 | import { ipsum, numbers } from '../utils'
2 |
3 | export const createMagazine = (args = {}) => {
4 | const name = ipsum.generateSentence()
5 | const issue = `#${numbers.randomBetween(1, 500)}`
6 | const publisher = ipsum.generateParagraph(1, { useStartingSentence: true })
7 | const _args = {
8 | name,
9 | issue,
10 | publisher,
11 | }
12 | return Object.assign({}, _args, args)
13 | }
14 |
--------------------------------------------------------------------------------
/tests/e2e/factory/store/common.utils.js:
--------------------------------------------------------------------------------
1 | export const initialResourcesRoutes = resources => {
2 | return resources.map(resource => {
3 | return {
4 | path: `/${resource}`,
5 | name: resource,
6 | }
7 | })
8 | }
9 |
--------------------------------------------------------------------------------
/tests/e2e/factory/store/index.js:
--------------------------------------------------------------------------------
1 | import createInitialVuexStoreState from './initial.state'
2 | import createInitialVuexStoreGetters from './initial.getters'
3 |
4 | export { createInitialVuexStoreState, createInitialVuexStoreGetters }
5 |
--------------------------------------------------------------------------------
/tests/e2e/factory/store/initial.state.js:
--------------------------------------------------------------------------------
1 | import { initialResourcesRoutes } from './common.utils'
2 |
3 | /**
4 | * Anonymous Function - Creates a simualtion of initial vuex crud state
5 | *
6 | * @return {Object} The expected Vuex Crud mocked state
7 | */
8 | export default () => {
9 | // Initial vuex crud resources should be added here
10 | const initialResources = ['articles', 'magazines', 'authors']
11 | // Vuex Crud Initial State for a resource
12 | const initialResourceState = {
13 | createError: null,
14 | destroyError: null,
15 | entities: {},
16 | fetchListError: null,
17 | fetchSingleError: null,
18 | isCreating: false,
19 | isDestroying: false,
20 | isFetchingList: false,
21 | isFetchingSingle: false,
22 | isReplacing: false,
23 | isUpdating: false,
24 | list: [],
25 | replaceError: null,
26 | updateError: null,
27 | }
28 | // Vuex Initial State for alerts
29 | const initialAlertsState = {
30 | color: '',
31 | isVisible: false,
32 | text: '',
33 | }
34 | // Vuex Initial State for auth
35 | const initialAuthState = {
36 | error: '',
37 | isAuthenticated: false,
38 | status: 'idle',
39 | user: {},
40 | }
41 |
42 | // Vuex Initial State for request
43 | const initialRequestState = {
44 | isLoading: false,
45 | }
46 | // Vuex Initial State for entities
47 | const initialEntitiesState = {}
48 | // Vuex Initial State for resource routes
49 | const initialResourcesState = {
50 | routes: initialResourcesRoutes(initialResources),
51 | }
52 |
53 | /**
54 | * initResourcesCrud - Given a list of resources, creates mocked vuex crud
55 | * state for each of them
56 | *
57 | * @param {Array} resources An array of strings
58 | *
59 | * @return {Object} An object with mocked vuex crud state
60 | */
61 | function initResourcesState(resources) {
62 | const _resources = {}
63 | resources.forEach(resource => {
64 | _resources[resource] = initialResourceState
65 | })
66 | return _resources
67 | }
68 |
69 | return {
70 | ...initResourcesState(initialResources),
71 | alerts: initialAlertsState,
72 | auth: initialAuthState,
73 | entities: initialEntitiesState,
74 | resources: initialResourcesState,
75 | requests: initialRequestState,
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/tests/e2e/factory/users/index.js:
--------------------------------------------------------------------------------
1 | export const createUser = (args = {}) => {
2 | const id = 234567
3 | const email = 'dev@camba.coop'
4 | const permissions = ['admin']
5 | const _args = {
6 | id,
7 | email,
8 | permissions,
9 | }
10 | return Object.assign({}, _args, args)
11 | }
12 |
--------------------------------------------------------------------------------
/tests/e2e/factory/utils.js:
--------------------------------------------------------------------------------
1 | const Ipsum = require('bavaria-ipsum')
2 |
3 | export const ipsum = new Ipsum({
4 | startSentence: 'Vue Admin aspera iaspis',
5 | minSentenceWords: 2,
6 | maxSentenceWords: 6,
7 | minParagraphSentences: 1,
8 | maxParagraphSentences: 3,
9 | })
10 |
11 | export const numbers = {
12 | randomBetween(min, max) {
13 | return Math.floor(Math.random() * (max - min + 1) + min)
14 | },
15 | }
16 |
17 | export const randomDate = (start, end) => {
18 | return new Date(
19 | start.getTime() + Math.random() * (end.getTime() - start.getTime())
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/tests/e2e/fixtures/authors.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": 1628157,
4 | "name": "Jerónimo",
5 | "lastname": "Zepeda",
6 | "birthdate": "2018-06-27T22:41:40.611Z"
7 | },
8 | {
9 | "id": 2474107,
10 | "name": "Graciela",
11 | "lastname": "Solorio",
12 | "birthdate": "2019-04-13T08:03:23.089Z"
13 | },
14 | {
15 | "id": 453660,
16 | "name": "Roberto",
17 | "lastname": "Saldivar",
18 | "birthdate": "2018-07-28T08:57:55.461Z"
19 | },
20 | {
21 | "id": 655968,
22 | "name": "María Luisa",
23 | "lastname": "Archuleta",
24 | "birthdate": "2018-07-30T13:50:05.975Z"
25 | },
26 | {
27 | "id": 2168222,
28 | "name": "Isabel",
29 | "lastname": "Acuña",
30 | "birthdate": "2018-12-24T05:26:21.284Z"
31 | },
32 | {
33 | "id": 110552,
34 | "name": "Yolanda",
35 | "lastname": "Bustos",
36 | "birthdate": "2018-10-16T13:14:41.552Z"
37 | },
38 | {
39 | "id": 508428,
40 | "name": "Julio César",
41 | "lastname": "Terrazas",
42 | "birthdate": "2019-02-22T11:38:51.171Z"
43 | },
44 | {
45 | "id": 2349762,
46 | "name": "Sara",
47 | "lastname": "Villa",
48 | "birthdate": "2018-07-10T16:38:49.125Z"
49 | },
50 | {
51 | "id": 726880,
52 | "name": "Emilia",
53 | "lastname": "Merino",
54 | "birthdate": "2018-11-21T22:10:32.339Z"
55 | },
56 | {
57 | "id": 2392010,
58 | "name": "Adela",
59 | "lastname": "Esquivel",
60 | "birthdate": "2019-03-06T03:05:58.655Z"
61 | },
62 | {
63 | "id": 590988,
64 | "name": "Gilberto",
65 | "lastname": "Delagarza",
66 | "birthdate": "2018-09-24T15:35:27.656Z"
67 | },
68 | {
69 | "id": 2003453,
70 | "name": "Luisa",
71 | "lastname": "Urrutia",
72 | "birthdate": "2018-06-17T11:14:44.059Z"
73 | },
74 | {
75 | "id": 2012058,
76 | "name": "Francisca",
77 | "lastname": "Abreu",
78 | "birthdate": "2018-10-29T07:58:22.281Z"
79 | },
80 | {
81 | "id": 315226,
82 | "name": "Rafael",
83 | "lastname": "Armas",
84 | "birthdate": "2019-01-10T01:49:31.995Z"
85 | },
86 | {
87 | "id": 1409776,
88 | "name": "Lucas",
89 | "lastname": "Ramón",
90 | "birthdate": "2018-12-04T20:43:30.497Z"
91 | },
92 | {
93 | "id": 2058727,
94 | "name": "Estela",
95 | "lastname": "Marroquín",
96 | "birthdate": "2018-12-08T18:24:05.609Z"
97 | },
98 | {
99 | "id": 566659,
100 | "name": "David",
101 | "lastname": "Cornejo",
102 | "birthdate": "2018-10-06T01:34:52.379Z"
103 | },
104 | {
105 | "id": 24249,
106 | "name": "Federico",
107 | "lastname": "Jurado",
108 | "birthdate": "2019-01-13T06:52:42.200Z"
109 | },
110 | {
111 | "id": 1473716,
112 | "name": "Lucas",
113 | "lastname": "Leyva",
114 | "birthdate": "2018-11-28T14:01:36.224Z"
115 | }
116 | ]
117 |
--------------------------------------------------------------------------------
/tests/e2e/helpers/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Cypress Helpers
3 | */
4 |
5 | // TODO: deprecate this file and use ../lib/helpers instead ^_^
6 | export default {
7 | queryElementByProp: ({ type = '', prop, value }) =>
8 | `${type}[${prop}=${value}]`,
9 | }
10 |
--------------------------------------------------------------------------------
/tests/e2e/lib/helpers.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Command Helpers
3 | */
4 |
5 | export default {
6 | createElementQueryWith: ({ type = '', prop, value }) =>
7 | `${type}[${prop}=${value}]`,
8 |
9 | createUrlWithResource: ({ resourceName, path = '' }) =>
10 | `${Cypress.config().baseUrl}#/${resourceName}/${path}`,
11 | }
12 |
--------------------------------------------------------------------------------
/tests/e2e/plugins/index.js:
--------------------------------------------------------------------------------
1 | // https://docs.cypress.io/guides/guides/plugins-guide.html
2 |
3 | module.exports = (on, config) => {
4 | return Object.assign({}, config, {
5 | fixturesFolder: 'tests/e2e/fixtures',
6 | integrationFolder: 'tests/e2e/specs',
7 | screenshotsFolder: 'tests/e2e/screenshots',
8 | videosFolder: 'tests/e2e/videos',
9 | supportFile: 'tests/e2e/support/index.js',
10 | })
11 | }
12 |
--------------------------------------------------------------------------------
/tests/e2e/specs/articles/create.spec.js:
--------------------------------------------------------------------------------
1 | const Factory = require('../../factory')
2 | const UI_CONTENT = require('../../../../src/constants/ui.content.default')
3 | const UI_NAMES = require('../../../../src/constants/ui.element.names')
4 |
5 | describe('Articles: Create Test', () => {
6 | const resourceName = 'articles'
7 | const view = 'create'
8 | const article = {}
9 |
10 | before('Initialises authenticated with a default user', () => {
11 | cy.InitAuthenticatedUser()
12 | })
13 |
14 | before('Visits the create url', () => {
15 | cy.visit(`/#/${resourceName}/${view}`)
16 | })
17 |
18 | before('Generate an article to create', () => {
19 | Object.assign(article, Factory.createArticle())
20 | })
21 |
22 | it('The url path should be articles/create', () => {
23 | cy.url().should('include', `${resourceName}/${view}`)
24 | })
25 |
26 | it('Articles Create View should render title: Articles', () => {
27 | const createViewTitleText = 'Create Article'
28 | const createViewTitleContainer = cy.getElement({
29 | constant: UI_NAMES.RESOURCE_VIEW_CONTAINER_TITLE,
30 | constantParams: { resourceName, view },
31 | elementType: 'div',
32 | elementProp: 'name',
33 | })
34 |
35 | createViewTitleContainer.should('contain', createViewTitleText)
36 | })
37 |
38 | it('The {Title} input is filled when an user types in', () => {
39 | theFieldInputIsFilledWhenAnUserTypesIn('title')
40 | })
41 |
42 | it('The {Content} input is filled when an user types in', () => {
43 | theFieldInputIsFilledWhenAnUserTypesIn('content')
44 | })
45 |
46 | it('Articles Create View should redirect to the List View on a create submit', () => {
47 | const routes = [{ name: view, response: article }, { name: 'list' }]
48 | cy.InitServer({ resourceName, routes, response: article })
49 |
50 | const submitButtonText = UI_CONTENT.CREATE_SUBMIT_BUTTON
51 | const submitButton = cy.getElement({
52 | constant: UI_NAMES.RESOURCE_VIEW_SUBMIT_BUTTON,
53 | constantParams: { resourceName, view },
54 | elementType: 'button',
55 | elementProp: 'name',
56 | })
57 |
58 | submitButton.should('contain', submitButtonText).click()
59 |
60 | cy.server({ enable: false })
61 |
62 | cy.url().should('include', '/articles')
63 | })
64 |
65 | function theFieldInputIsFilledWhenAnUserTypesIn(field) {
66 | const input = cy.getElement({
67 | constant: UI_NAMES.RESOURCE_VIEW_ELEMENT_FIELD,
68 | constantParams: { resourceName, view, field },
69 | elementType: 'input',
70 | elementProp: 'name',
71 | })
72 |
73 | input.type(article[field])
74 | input.should('have.value', article[field])
75 | }
76 | })
77 |
--------------------------------------------------------------------------------
/tests/e2e/specs/articles/delete.spec.js:
--------------------------------------------------------------------------------
1 | const UI_NAMES = require('../../../../src/constants/ui.element.names')
2 |
3 | describe('Articles: Delete Test', () => {
4 | const resourceName = 'articles'
5 | const article = {}
6 |
7 | before('Initialises authenticated with a default user', () => {
8 | cy.InitAuthenticatedUser()
9 | })
10 |
11 | before('Search an article to delete', () => {
12 | cy.fixture(resourceName).then(fixture => {
13 | Object.assign(article, fixture[0])
14 | })
15 | })
16 |
17 | before('Initialises the server', () => {
18 | const routes = [{ name: 'show', response: article }]
19 | cy.InitServer({ resourceName, routes })
20 | })
21 |
22 | before('Visits the Show view url', () => {
23 | const showUrl = `${resourceName}/show/${article.id}`
24 | cy.visit(`/#/${showUrl}`)
25 | cy.url().should('include', showUrl)
26 | cy.server({ enable: false })
27 | })
28 |
29 | it('Press the delete button in the Show view', () => {
30 | const routes = [{ name: 'delete', response: article }, { name: 'list' }]
31 | cy.InitServer({ resourceName, routes })
32 | const deleteButton = cy.getElement({
33 | constant: UI_NAMES.RESOURCE_DELETE_BUTTON,
34 | constantParams: { resourceName },
35 | elementType: 'button',
36 | elementProp: 'name',
37 | })
38 |
39 | deleteButton.click()
40 | cy.server({ enable: false })
41 |
42 | cy.wait(`@${resourceName}/delete/${article.id}`).then(xmlHttpRequest => {
43 | expect(xmlHttpRequest.status).equal(202)
44 | cy.url().should('include', `/${resourceName}`)
45 | })
46 | })
47 | })
48 |
--------------------------------------------------------------------------------
/tests/e2e/specs/articles/show.spec.js:
--------------------------------------------------------------------------------
1 | const { queryElementByProp } = require('../../helpers')
2 | const UI_NAMES = require('../../../../src/constants/ui.element.names')
3 |
4 | describe('Articles: Show Test', () => {
5 | const resourceName = 'articles'
6 | const view = 'show'
7 | const article = {}
8 |
9 | before('Initialises authenticated with a default user', () => {
10 | cy.InitAuthenticatedUser()
11 | })
12 |
13 | before('Search an article to show', () => {
14 | cy.fixture(resourceName).then(fixture => {
15 | // Takes the first element of the fixture to use as subject
16 | Object.assign(article, fixture[0])
17 | })
18 | })
19 |
20 | before('Initialises the server', () => {
21 | // Inits the server with a stubbed get endpoint
22 | const routes = [{ name: view, response: article }]
23 | cy.InitServer({ resourceName, routes })
24 | })
25 |
26 | it('Visits the articles show', () => {
27 | // Exercise: visits the show view
28 | cy.visit(`/#/${resourceName}/${view}/${article.id}`)
29 | cy.server({ enable: false })
30 | // Assertion: the url should match the show view url
31 | cy.url().should('include', `${resourceName}/${view}/${article.id}`)
32 | })
33 |
34 | it('Articles Show View should render title: Articles', () => {
35 | const showViewTitleText = 'Article'
36 |
37 | cy.getElement({
38 | constant: UI_NAMES.RESOURCE_VIEW_CONTAINER_TITLE,
39 | constantParams: { resourceName, view },
40 | elementType: 'div',
41 | elementProp: 'name',
42 | }).should('contain', showViewTitleText)
43 | })
44 |
45 | it('Articles Show View should contain the title field', () => {
46 | articlesShowViewShouldContainTheField('title')
47 | })
48 |
49 | it('Articles Show View should contain the content field', () => {
50 | articlesShowViewShouldContainTheField('content')
51 | })
52 |
53 | /**
54 | * Helper functions
55 | **/
56 | function queryToElementWith(containerType, containerParams) {
57 | const containerName = UI_NAMES[containerType].with(containerParams)
58 | return queryElementByProp({
59 | type: 'div',
60 | prop: 'name',
61 | value: containerName,
62 | })
63 | }
64 |
65 | function queryToElement(containerType) {
66 | return queryToElementWith(containerType, { resourceName, view })
67 | }
68 |
69 | function articlesShowViewShouldContainTheField(field) {
70 | cy.get(queryToElement('RESOURCE_VIEW_CONTAINER_FIELDS')).should(
71 | fieldsContainerRes => {
72 | const fieldContainerElement = queryToElementWith(
73 | 'RESOURCE_VIEW_CONTAINER_FIELD',
74 | {
75 | resourceName,
76 | view,
77 | field,
78 | }
79 | )
80 | const fieldContainer = fieldsContainerRes.find(fieldContainerElement)
81 | expect(fieldContainer).to.contain(article[field])
82 | }
83 | )
84 | }
85 | })
86 |
--------------------------------------------------------------------------------
/tests/e2e/specs/auth/auth.spec.js:
--------------------------------------------------------------------------------
1 | const UI_CONTENT = require('../../../../src/constants/ui.content.default')
2 | const UI_NAMES = require('../../../../src/constants/ui.element.names')
3 |
4 | describe('Auth Test', () => {
5 | const view = 'login'
6 | const user = {
7 | username: 'dev@camba.coop',
8 | password: '123456',
9 | }
10 |
11 | const findInput = ({ constant }) =>
12 | cy.getElement({ constant, elementType: 'input', elementProp: 'name' })
13 |
14 | const findButton = ({ constant }) =>
15 | cy.getElement({ constant, elementType: 'button', elementProp: 'name' })
16 |
17 | const findTypeAndAssert = ({ element, value, condition }) => {
18 | const input = findInput({ constant: element })
19 | input.type(value)
20 | input.should(condition, value)
21 | }
22 |
23 | beforeEach('Visits the auth url', () => {
24 | cy.visit(`/#/${view}`)
25 | })
26 |
27 | it('The url path should be /login', () => {
28 | cy.url().should('include', `/${view}`)
29 | })
30 |
31 | it('Login View should render a title: Sign In', () => {
32 | const createViewTitleText = UI_CONTENT.AUTH_CONTAINER_TITLE
33 | const createViewTitleContainer = cy.getElement({
34 | constant: UI_NAMES.AUTH_CONTAINER_TITLE,
35 | elementType: 'div',
36 | elementProp: 'name',
37 | })
38 |
39 | createViewTitleContainer.should('contain', createViewTitleText)
40 | })
41 |
42 | it('The {username} input is filled when a user types in', () => {
43 | findTypeAndAssert({
44 | element: UI_NAMES.AUTH_USERNAME_INPUT,
45 | value: user.username,
46 | condition: 'have.value',
47 | })
48 | })
49 |
50 | it('The {password} input is filled when a user types in', () => {
51 | findTypeAndAssert({
52 | element: UI_NAMES.AUTH_PASSWORD_INPUT,
53 | value: user.password,
54 | condition: 'have.value',
55 | })
56 | })
57 |
58 | it('The Sign In button is disabled when no username and password were given', () => {
59 | const button = findButton({ constant: UI_NAMES.AUTH_SIGN_IN_BUTTON })
60 | button.should('be.disabled')
61 | })
62 |
63 | it('When a user types a valid username and password, the button is enabled', () => {
64 | findTypeAndAssert({
65 | element: UI_NAMES.AUTH_USERNAME_INPUT,
66 | value: user.username,
67 | condition: 'have.value',
68 | })
69 | findTypeAndAssert({
70 | element: UI_NAMES.AUTH_PASSWORD_INPUT,
71 | value: user.password,
72 | condition: 'have.value',
73 | })
74 | const button = findButton({ constant: UI_NAMES.AUTH_SIGN_IN_BUTTON })
75 | button.should('be.enabled')
76 | })
77 | })
78 |
--------------------------------------------------------------------------------
/tests/e2e/specs/magazines/show.spec.js:
--------------------------------------------------------------------------------
1 | const { InitEntityUtils } = require('../../lib/commands')
2 |
3 | describe('Magazines: Show Action Test', () => {
4 | const resourceName = 'magazines'
5 | const view = 'show'
6 | const magazine = {}
7 | const utils = InitEntityUtils({
8 | resourceName,
9 | view,
10 | })
11 |
12 | before('Initialises authenticated with a default user', () => {
13 | cy.InitAuthenticatedUser()
14 | })
15 |
16 | before('Search a magazine to show', () => {
17 | cy.fixture(resourceName).then(fixture => {
18 | // Takes the first element of the fixture to use as subject
19 | Object.assign(magazine, fixture[0])
20 | })
21 | })
22 |
23 | before('Initialises the server', () => {
24 | // Inits the server with a stubbed get endpoint
25 | const routes = [{ name: view, response: magazine }]
26 | cy.InitServer({ resourceName, routes })
27 | })
28 |
29 | it('Visits the magazines show url and the path should be magazines/show/:id', () => {
30 | // Exercise: visits the show view
31 | cy.visit(`/#/${resourceName}/${view}/${magazine.id}`)
32 | cy.server({ enable: false })
33 | // Assertion: the url should match the show view url
34 | cy.url().should('include', `${resourceName}/${view}/${magazine.id}`)
35 | })
36 |
37 | it('The {Name} input should match the created magazine {name}', () => {
38 | // Setup: Gets the 'name' input element
39 | const input = utils.getInputBy({ field: 'name' })
40 | // Assertion: the input contains the magazine issue content
41 | input.should('have.value', magazine.name)
42 | })
43 |
44 | it('The {Issue} input should match the created magazine {issue}', () => {
45 | // Setup: Gets the 'issue' input element
46 | const input = utils.getInputBy({ field: 'issue' })
47 | // Assertion: the input contains the magazine issue content
48 | input.should('have.value', magazine.issue)
49 | })
50 |
51 | it('The {Publisher} input should match the created magazine {publisher}', () => {
52 | // Setup: Gets the 'publisher' input element
53 | const input = utils.getInputBy({ field: 'publisher' })
54 | // Assertion: the input contains the magazine publisher content
55 | input.should('have.value', magazine.publisher)
56 | })
57 | })
58 |
--------------------------------------------------------------------------------
/tests/e2e/specs/spinner/spinner-create.spec.js:
--------------------------------------------------------------------------------
1 | const UI_NAMES = require('../../../../src/constants/ui.element.names')
2 | import { Types as RequestsTypes } from '../../../../src/store/modules/requests'
3 |
4 | describe('Spinner on a Create View Test', () => {
5 | const testResourceName = 'articles'
6 | const view = 'create'
7 |
8 | before('Initialises authenticated with a default user', () => {
9 | cy.InitAuthenticatedUser()
10 | })
11 |
12 | before('Visits the create url', () => {
13 | cy.visit(`/#/${testResourceName}/${view}`)
14 | })
15 |
16 | it('The spinner should not be visualized when the store property isLoading is set to false', () => {
17 | const { namespace, REQUESTS_SET_LOADING } = RequestsTypes
18 | const mutation = `${namespace}/${REQUESTS_SET_LOADING}`
19 | const isLoading = false
20 | cy.getStore().invoke('commit', mutation, { isLoading })
21 |
22 | const spinnerContainer = cy.getElement({
23 | constant: UI_NAMES.SPINNER_CONTAINER,
24 | elementType: 'div',
25 | elementProp: 'id',
26 | })
27 |
28 | spinnerContainer.should('not.be.visible')
29 | })
30 |
31 | it('The spinner should be visualized when the store property isLoading is set to true', () => {
32 | const { namespace, REQUESTS_SET_LOADING } = RequestsTypes
33 | const mutation = `${namespace}/${REQUESTS_SET_LOADING}`
34 | const isLoading = true
35 | cy.getStore().invoke('commit', mutation, { isLoading })
36 |
37 | const spinnerContainer = cy.getElement({
38 | constant: UI_NAMES.SPINNER_CONTAINER,
39 | elementType: 'div',
40 | elementProp: 'id',
41 | })
42 |
43 | spinnerContainer.should('be.visible')
44 | })
45 | })
46 |
--------------------------------------------------------------------------------
/tests/e2e/specs/spinner/spinner-edit.spec.js:
--------------------------------------------------------------------------------
1 | import Factory from '../../factory'
2 | import UI_NAMES from '../../../../src/constants/ui.element.names'
3 | import { Types as RequestsTypes } from '../../../../src/store/modules/requests'
4 |
5 | describe('Spinner on a Edit View Test', () => {
6 | const resourceName = 'articles'
7 | const view = 'edit'
8 | const article = {}
9 | const newArticle = Factory.createArticle()
10 |
11 | before('Initialises authenticated with a default user', () => {
12 | cy.InitAuthenticatedUser()
13 | })
14 |
15 | before('Search an article to edit', () => {
16 | cy.fixture(resourceName).then(fixture => {
17 | Object.assign(article, fixture[0])
18 | newArticle.id = article.id
19 | })
20 | })
21 |
22 | before('Initialises the mocked serve and visits the edit url', () => {
23 | const response = article
24 | const routes = [{ name: 'edit', response }, { name: 'show', response }]
25 |
26 | cy.InitServer({ resourceName, routes })
27 | cy.visit(`/#/${resourceName}/${view}/${article.id}`)
28 | cy.server({ enable: false })
29 | })
30 |
31 | it('The spinner should not be visualized when the store property isLoading is set to false', () => {
32 | const { namespace, REQUESTS_SET_LOADING } = RequestsTypes
33 | const mutation = `${namespace}/${REQUESTS_SET_LOADING}`
34 | const isLoading = false
35 | cy.getStore().invoke('commit', mutation, { isLoading })
36 |
37 | const spinnerContainer = cy.getElement({
38 | constant: UI_NAMES.SPINNER_CONTAINER,
39 | elementType: 'div',
40 | elementProp: 'id',
41 | })
42 |
43 | spinnerContainer.should('not.be.visible')
44 | })
45 |
46 | it('The spinner should be visualized when the store property isLoading is set to true', () => {
47 | const { namespace, REQUESTS_SET_LOADING } = RequestsTypes
48 | const mutation = `${namespace}/${REQUESTS_SET_LOADING}`
49 | const isLoading = true
50 | cy.getStore().invoke('commit', mutation, { isLoading })
51 |
52 | const spinnerContainer = cy.getElement({
53 | constant: UI_NAMES.SPINNER_CONTAINER,
54 | elementType: 'div',
55 | elementProp: 'id',
56 | })
57 |
58 | spinnerContainer.should('be.visible')
59 | })
60 | })
61 |
--------------------------------------------------------------------------------
/tests/e2e/specs/spinner/spinner-list.spec.js:
--------------------------------------------------------------------------------
1 | import UI_NAMES from '../../../../src/constants/ui.element.names'
2 | import { Types as RequestsTypes } from '../../../../src/store/modules/requests'
3 |
4 | describe('Spinner on a List View Test', () => {
5 | const resourceName = 'articles'
6 | const view = 'list'
7 |
8 | before('Initialises authenticated with a default user', () => {
9 | cy.InitAuthenticatedUser()
10 | })
11 |
12 | before('Visit the list url', () => {
13 | const routes = [{ name: view }]
14 | cy.InitServer({ resourceName, routes })
15 | cy.visit(`/#/${resourceName}/`)
16 | })
17 |
18 | it('The spinner should not be visualized when the store property isLoading is set to false', () => {
19 | const { namespace, REQUESTS_SET_LOADING } = RequestsTypes
20 | const mutation = `${namespace}/${REQUESTS_SET_LOADING}`
21 | const isLoading = false
22 | cy.getStore().invoke('commit', mutation, { isLoading })
23 |
24 | const spinnerContainer = cy.getElement({
25 | constant: UI_NAMES.SPINNER_CONTAINER,
26 | elementType: 'div',
27 | elementProp: 'id',
28 | })
29 |
30 | spinnerContainer.should('not.be.visible')
31 | })
32 |
33 | it('The spinner should be visualized when the store property isLoading is set to true', () => {
34 | const { namespace, REQUESTS_SET_LOADING } = RequestsTypes
35 | const mutation = `${namespace}/${REQUESTS_SET_LOADING}`
36 | const isLoading = true
37 | cy.getStore().invoke('commit', mutation, { isLoading })
38 |
39 | const spinnerContainer = cy.getElement({
40 | constant: UI_NAMES.SPINNER_CONTAINER,
41 | elementType: 'div',
42 | elementProp: 'id',
43 | })
44 |
45 | spinnerContainer.should('be.visible')
46 | })
47 | })
48 |
--------------------------------------------------------------------------------
/tests/e2e/specs/spinner/spinner-show.spec.js:
--------------------------------------------------------------------------------
1 | import UI_NAMES from '../../../../src/constants/ui.element.names'
2 | import { Types as RequestsTypes } from '../../../../src/store/modules/requests'
3 |
4 | describe('Spinner on a Show View Test', () => {
5 | const resourceName = 'articles'
6 | const view = 'show'
7 | const article = {}
8 |
9 | before('Initialises authenticated with a default user', () => {
10 | cy.InitAuthenticatedUser()
11 | })
12 |
13 | before('Search an article to show', () => {
14 | cy.fixture(resourceName).then(fixture => {
15 | // Takes the first element of the fixture to use as subject
16 | Object.assign(article, fixture[0])
17 | })
18 | })
19 |
20 | before('Initialises the server', () => {
21 | // Inits the server with a stubbed get endpoint
22 | const routes = [{ name: view, response: article }]
23 | cy.InitServer({ resourceName, routes })
24 | })
25 |
26 | before('Visit the show url', () => {
27 | cy.visit(`/#/${resourceName}/${view}/${article.id}`)
28 | })
29 |
30 | it('The spinner should not be visualized when the store property isLoading is set to false', () => {
31 | const { namespace, REQUESTS_SET_LOADING } = RequestsTypes
32 | const mutation = `${namespace}/${REQUESTS_SET_LOADING}`
33 | const isLoading = false
34 | cy.getStore().invoke('commit', mutation, { isLoading })
35 |
36 | const spinnerContainer = cy.getElement({
37 | constant: UI_NAMES.SPINNER_CONTAINER,
38 | elementType: 'div',
39 | elementProp: 'id',
40 | })
41 |
42 | spinnerContainer.should('not.be.visible')
43 | })
44 |
45 | it('The spinner should be visualized when the store property isLoading is set to true', () => {
46 | const { namespace, REQUESTS_SET_LOADING } = RequestsTypes
47 | const mutation = `${namespace}/${REQUESTS_SET_LOADING}`
48 | const isLoading = true
49 | cy.getStore().invoke('commit', mutation, { isLoading })
50 |
51 | const spinnerContainer = cy.getElement({
52 | constant: UI_NAMES.SPINNER_CONTAINER,
53 | elementType: 'div',
54 | elementProp: 'id',
55 | })
56 |
57 | spinnerContainer.should('be.visible')
58 | })
59 | })
60 |
--------------------------------------------------------------------------------
/tests/e2e/specs/store/state.spec.js:
--------------------------------------------------------------------------------
1 | import Factory from '../../factory'
2 |
3 | describe('Vuex Store State', () => {
4 | const getStore = () => cy.getStore()
5 | const initialState = {}
6 |
7 | before('Initialises the store', () => {
8 | const _initialState = Factory.createInitialVuexStoreState()
9 | Object.assign(initialState, _initialState)
10 | })
11 | before('Initialises authenticated with a default user', () => {
12 | cy.InitAuthenticatedUser()
13 | .then(authResponse => {
14 | const {
15 | response: {
16 | body: { user },
17 | },
18 | status,
19 | } = authResponse
20 | if (status === 200) {
21 | Object.assign(initialState.auth, { isAuthenticated: true, user })
22 | }
23 | })
24 | .visit('/#/')
25 | })
26 |
27 | it('Should have attributes on initialisation', () => {
28 | const state = 'state'
29 | getStore()
30 | .its(state)
31 | .should('have.keys', Object.keys(initialState))
32 | })
33 |
34 | it('Attribute {auth} should have an intitial configuration', () => {
35 | const attribute = 'auth'
36 | const state = `state.${attribute}`
37 | getStore()
38 | .its(state)
39 | .should('deep.equal', initialState[attribute])
40 | })
41 |
42 | it('Attribute {articles} should have the vuex crud intitial configuration', () => {
43 | const attribute = 'articles'
44 | const state = `state.${attribute}`
45 | getStore()
46 | .its(state)
47 | .should('deep.equal', initialState[attribute])
48 | })
49 |
50 | it('Attribute {magazines} should have the vuex crud initial configuration', () => {
51 | const attribute = 'magazines'
52 | const state = `state.${attribute}`
53 | getStore()
54 | .its(state)
55 | .should('deep.equal', initialState[attribute])
56 | })
57 |
58 | it('Attribute {entities} should be empty', () => {
59 | const attribute = 'entities'
60 | const state = `state.${attribute}`
61 | getStore()
62 | .its(state)
63 | .should('eql', initialState[attribute])
64 | getStore()
65 | .its(state)
66 | .should('be.empty')
67 | })
68 |
69 | it('Attribute {resources} should have attributes initialised', () => {
70 | const attribute = 'resources'
71 | const state = `state.${attribute}`
72 | getStore()
73 | .its(state)
74 | .should('have.keys', ['routes'])
75 | })
76 |
77 | it('Attribute {resources} should have routes initialised', () => {
78 | const attribute = 'resources'
79 | const state = `state.${attribute}`
80 | getStore()
81 | .its(state)
82 | .should('deep.equal', initialState[attribute])
83 | })
84 | })
85 |
--------------------------------------------------------------------------------
/tests/e2e/specs/ui/ui.spec.js:
--------------------------------------------------------------------------------
1 | // https://docs.cypress.io/api/introduction/api.html
2 |
3 | const { queryElementByProp } = require('../../helpers')
4 |
5 | const UI_CONTENT = require('../../../../src/constants/ui.content.default')
6 | const UI_NAMES = require('../../../../src/constants/ui.element.names')
7 |
8 | describe('UI Test', () => {
9 | before('Initialises authenticated with a default user', () => {
10 | cy.InitAuthenticatedUser()
11 | })
12 |
13 | it('Visits the app root url', () => {
14 | cy.visit('/#/')
15 | })
16 |
17 | it('Title should be vue-admin', () => {
18 | cy.title().should('eq', UI_CONTENT.MAIN_TITLE)
19 | })
20 |
21 | it('Toolbar title should be Vue Admin', () => {
22 | const mainToolbarTitleName = UI_NAMES.MAIN_TOOLBAR_TITLE
23 | const mainToolbarTitleElement = queryElementByProp({
24 | type: 'div',
25 | prop: 'name',
26 | value: mainToolbarTitleName,
27 | })
28 |
29 | const expectedMainToolbarTitleText = UI_CONTENT.MAIN_TOOLBAR_TITLE
30 |
31 | cy.get(mainToolbarTitleElement).should(mainToolbarTitle => {
32 | expect(mainToolbarTitle).to.contain(expectedMainToolbarTitleText)
33 | })
34 | })
35 |
36 | it('Toolbar hamburger button should open drawer on click', () => {
37 | const drawerButtonName = UI_NAMES.DRAWER_BUTTON
38 |
39 | const drawerButton = queryElementByProp({
40 | type: 'button',
41 | prop: 'name',
42 | value: drawerButtonName,
43 | })
44 | cy.get(drawerButton).click()
45 | })
46 | })
47 |
--------------------------------------------------------------------------------
/tests/e2e/specs/unauthorized/unauthorized.spec.js:
--------------------------------------------------------------------------------
1 | const Factory = require('../../factory')
2 | const UI_CONTENT = require('../../../../src/constants/ui.content.default')
3 | const UI_NAMES = require('../../../../src/constants/ui.element.names')
4 |
5 | describe('Unauthorized User Test', () => {
6 | const testResourceName = 'articles'
7 | const unauthorizedPath = 'unauthorized'
8 | const userWithoutPermissions = Factory.createUser({ permissions: [] })
9 | const userWithPermissions = Factory.createUser({ permissions: ['admin'] })
10 | const createAuthResponse = user => Factory.createAuthResponse({ user })
11 | const authResponseWithUserWithoutPermissions = createAuthResponse(
12 | userWithoutPermissions
13 | )
14 | const authResponseWithUserWithPermissions = createAuthResponse(
15 | userWithPermissions
16 | )
17 |
18 | it('A user without admin privileges should be redirected when trying to access an unauthorized view', () => {
19 | cy.InitAuthenticatedUser({
20 | authResponse: authResponseWithUserWithoutPermissions,
21 | })
22 | cy.visit(`/#/${testResourceName}/`)
23 | cy.url().should('include', `/#/${unauthorizedPath}`)
24 | })
25 |
26 | it('A user with admin privileges should not be redirected when trying to access an unauthorized view', () => {
27 | cy.InitAuthenticatedUser({
28 | authResponse: authResponseWithUserWithPermissions,
29 | })
30 | cy.visit(`/#/${testResourceName}/`)
31 | cy.url().should('include', `/#/${testResourceName}`)
32 | })
33 |
34 | it('Unauthorized view should have a title', () => {
35 | const defaultHeader = UI_CONTENT.UNAUTHORIZED_HEADER
36 | cy.InitAuthenticatedUser({
37 | authResponse: authResponseWithUserWithoutPermissions,
38 | })
39 | cy.visit(`/#/${testResourceName}/`)
40 | cy.getElement({
41 | constant: UI_NAMES.UNAUTHORIZED_HEADER_CONTAINER,
42 | elementType: 'h2',
43 | elementProp: 'name',
44 | }).should('contain', defaultHeader)
45 | })
46 |
47 | it('Unauthorized view should have a text', () => {
48 | const defaultMessage = UI_CONTENT.UNAUTHORIZED_MESSAGE
49 | cy.InitAuthenticatedUser({
50 | authResponse: authResponseWithUserWithoutPermissions,
51 | })
52 | cy.visit(`/#/${testResourceName}/`)
53 | cy.getElement({
54 | constant: UI_NAMES.UNAUTHORIZED_MESSAGE_CONTAINER,
55 | elementType: 'p',
56 | elementProp: 'name',
57 | }).should('contain', defaultMessage)
58 | })
59 |
60 | it('Unauthorized view should have a button', () => {
61 | const defaultMessage = UI_CONTENT.BUTTON_GO_BACK
62 | cy.InitAuthenticatedUser({
63 | authResponse: authResponseWithUserWithoutPermissions,
64 | })
65 | cy.visit(`/#/${testResourceName}/`)
66 | cy.getElement({
67 | constant: UI_NAMES.BUTTON_GO_BACK,
68 | elementType: 'button',
69 | elementProp: 'name',
70 | }).should('contain', defaultMessage)
71 | })
72 | })
73 |
--------------------------------------------------------------------------------
/tests/e2e/support/commands.js:
--------------------------------------------------------------------------------
1 | import {
2 | authenticate,
3 | InitAuthenticatedUser,
4 | InitEntityUtils,
5 | getElement,
6 | getStore,
7 | } from '../lib/commands'
8 | import InitServer from '../lib/server'
9 |
10 | Cypress.Commands.add('authenticate', args => authenticate(args))
11 | Cypress.Commands.add('getStore', () => getStore())
12 | Cypress.Commands.add('InitAuthenticatedUser', args =>
13 | InitAuthenticatedUser(args)
14 | )
15 | Cypress.Commands.add('InitEntityUtils', args => InitEntityUtils(args))
16 | Cypress.Commands.add('getElement', args => getElement(args))
17 | Cypress.Commands.add('InitServer', args => InitServer(args))
18 |
19 | // ***********************************************
20 | // This example commands.js shows you how to
21 | // create various custom commands and overwrite
22 | // existing commands.
23 | //
24 | // For more comprehensive examples of custom
25 | // commands please read more here:
26 | // https://on.cypress.io/custom-commands
27 | // ***********************************************
28 | //
29 | //
30 | // -- This is a parent command --
31 | // Cypress.Commands.add("login", (email, password) => { ... })
32 | //
33 | //
34 | // -- This is a child command --
35 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
36 | //
37 | //
38 | // -- This is a dual command --
39 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
40 | //
41 | //
42 | // -- This is will overwrite an existing command --
43 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
44 |
--------------------------------------------------------------------------------
/tests/e2e/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | import './commands'
17 |
--------------------------------------------------------------------------------
/tests/unit/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | jest: true,
4 | },
5 | }
6 |
--------------------------------------------------------------------------------
/tests/unit/factory/admin/index.js:
--------------------------------------------------------------------------------
1 | export const createAuthProvider = () => {
2 | return type => {
3 | switch (type) {
4 | default:
5 | return Promise.resolve()
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/tests/unit/factory/auth/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Creates fake credentials for an Auth component
3 | *
4 | * @return {Object} An object with fake credentials
5 | */
6 | export const createCredentials = args => {
7 | const _args = {
8 | username: 'dev@camba.coop',
9 | password: '123456',
10 | }
11 | return Object.assign({}, _args, args)
12 | }
13 |
14 | /**
15 | * Creates fake credentials for an Auth component
16 | *
17 | * @return {Object} An object with fake credentials
18 | */
19 | export const createUser = args => {
20 | const _args = {
21 | username: 'dev@camba.coop',
22 | id: '234567',
23 | permissions: ['admin'],
24 | }
25 | return Object.assign({}, _args, args)
26 | }
27 |
--------------------------------------------------------------------------------
/tests/unit/factory/index.js:
--------------------------------------------------------------------------------
1 | import { createCredentials, createUser } from './auth'
2 | import createStoreWith from './store'
3 | import { createCrudModule } from './store/modules'
4 | import { createAuthProvider } from './admin'
5 | import resource from './resource'
6 |
7 | export default {
8 | createAuthProvider,
9 | createCredentials,
10 | createCrudModule,
11 | createStoreWith,
12 | createUser,
13 | resource,
14 | }
15 |
--------------------------------------------------------------------------------
/tests/unit/factory/resource/index.js:
--------------------------------------------------------------------------------
1 | import responses from './responses'
2 |
3 | export default {
4 | responses,
5 | }
6 |
--------------------------------------------------------------------------------
/tests/unit/factory/resource/responses.js:
--------------------------------------------------------------------------------
1 | const getSingle = () => {
2 | return {
3 | status: 200,
4 | statusText: 'OK',
5 | headers: {
6 | 'content-length': '942',
7 | 'content-type': 'application/json; charset=utf-8',
8 | },
9 | data: {
10 | id: 80,
11 | title: 'Hog Ma.',
12 | content: 'Im Gschicht dahoam See Leit des Freibia ewig hera, Hob auffi',
13 | },
14 | }
15 | }
16 |
17 | export default {
18 | getSingle,
19 | }
20 |
--------------------------------------------------------------------------------
/tests/unit/factory/store/common.utils.js:
--------------------------------------------------------------------------------
1 | export const initialResourcesRoutes = resources => {
2 | return resources.map(resource => {
3 | return {
4 | path: `/${resource}`,
5 | name: resource,
6 | }
7 | })
8 | }
9 |
--------------------------------------------------------------------------------
/tests/unit/factory/store/index.js:
--------------------------------------------------------------------------------
1 | import createInitialStateWith from './initial.state'
2 | import createInitialGettersWith from './initial.getters'
3 | import createInitialMutationsWith from './initial.mutations'
4 |
5 | // Initial vuex crud resources should be added here
6 | const _initialResources = ['articles', 'magazines']
7 |
8 | /**
9 | * Annonymous Function - Creates a simualtion of initial vuex crud store
10 | *
11 | * @param {String} snapshot The name of a component the store should
12 | * be initialised for
13 | * @param {Array} initialResources A list of resources to initialise the store
14 | *
15 | * @return {type} The expected Vuex Crud mocked store for a snapshot
16 | */
17 | export default ({
18 | snapshot = 'default',
19 | initialResources = _initialResources,
20 | }) => {
21 | return {
22 | state: createInitialStateWith({ snapshot, initialResources }),
23 | getters: createInitialGettersWith({ snapshot, initialResources }),
24 | mutations: createInitialMutationsWith({ snapshot }),
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/unit/factory/store/initial.getters.js:
--------------------------------------------------------------------------------
1 | import { initialResourcesRoutes } from './common.utils'
2 | import { Types as EntitiesTypes } from '@store/modules/resources'
3 | import { Types as ResourcesTypes } from '@store/modules/resources'
4 |
5 | /**
6 | * Annonymous Function - Creates a simualtion of initial vuex crud getters
7 | *
8 | * @param {String} snapshot The name of a component the getters should
9 | * be initialised for
10 | * @param {Array} initialResources A list of resources to initialise getters
11 | *
12 | * @return {Object} The expected Vuex Crud mocked getters
13 | */
14 | export default ({ snapshot = 'default', initialResources }) => {
15 | // New custom getters configurations should be added here
16 | const snapshots = {
17 | default: initDefaultGetters,
18 | Resource: initGettersForResource,
19 | }
20 |
21 | // Vuex initial entities getters should be added here
22 | const { namespace: entitiesNamespace, ENTITIES_GET_ENTITY } = EntitiesTypes
23 | const entitiesCrud = {
24 | [`${entitiesNamespace}/${ENTITIES_GET_ENTITY}`]: () => {},
25 | }
26 | // Vuex initial resources getters should be added here
27 | const {
28 | namespace: resourcesNamespace,
29 | RESOURCES_GET_ALL_ROUTES,
30 | } = ResourcesTypes
31 | const resourcesGetters = {
32 | [`${resourcesNamespace}/${RESOURCES_GET_ALL_ROUTES}`]: () => {
33 | return initialResourcesRoutes(initialResources)
34 | },
35 | }
36 |
37 | /**
38 | * initResourcesCrud - Given a list of resources, creates mocked vuex crud
39 | * methods for each of them
40 | *
41 | * @param {Array} resources An array of strings
42 | *
43 | * @return {Object} An object with mocked vuex crud methods
44 | */
45 | function initResourcesGetters(resources) {
46 | const crud = {}
47 | resources.forEach(resource => {
48 | ;(crud[`${resource}/byId`] = id => id),
49 | (crud[`${resource}/isError`] = () => false),
50 | (crud[`${resource}/isLoading`] = () => false),
51 | (crud[`${resource}/list`] = () => [])
52 | })
53 | return crud
54 | }
55 | // Initialises default getters
56 | function initDefaultGetters() {
57 | return {
58 | ...initResourcesGetters(initialResources),
59 | ...entitiesCrud,
60 | ...resourcesGetters,
61 | }
62 | }
63 | // Initialises getters for a Resource component
64 | function initGettersForResource() {
65 | return {
66 | ...initResourcesGetters(initialResources),
67 | }
68 | }
69 |
70 | return snapshots[snapshot]()
71 | }
72 |
--------------------------------------------------------------------------------
/tests/unit/factory/store/initial.mutations.js:
--------------------------------------------------------------------------------
1 | import { Types as ResourcesTypes } from '@store/modules/resources'
2 |
3 | /**
4 | * Annonymous Function - Creates a simualtion of initial vuex crud mutations
5 | *
6 | * @param {String} snapshot The name of a component the mutations should be
7 | * initialised for
8 | *
9 | * @return {Object} The expected Vuex Crud mocked mutations
10 | */
11 | export default ({ snapshot = 'default' }) => {
12 | // New custom mutations configurations should be added here
13 | const snapshots = {
14 | default: initDefaultMutations,
15 | Resource: initMutationsForResource,
16 | }
17 |
18 | // Vuex initial resources mutations should be added here
19 | const { namespace: resourcesNamespace, RESOURCES_ADD_ROUTE } = ResourcesTypes
20 | const resourcesMutations = {
21 | [`${resourcesNamespace}/${RESOURCES_ADD_ROUTE}`]: (state, args) => {
22 | args.addedRouteCallback && args.addedRouteCallback()
23 | },
24 | }
25 |
26 | // Initialises default mutations
27 | function initDefaultMutations() {
28 | return {
29 | ...resourcesMutations,
30 | }
31 | }
32 | // Initialises mutations for a Resource component
33 | function initMutationsForResource() {
34 | return {
35 | ...resourcesMutations,
36 | }
37 | }
38 |
39 | return snapshots[snapshot]()
40 | }
41 |
--------------------------------------------------------------------------------
/tests/unit/factory/store/initial.state.js:
--------------------------------------------------------------------------------
1 | import { initialResourcesRoutes } from './common.utils'
2 |
3 | /**
4 | * Annonymous Function - Creates a simualtion of initial vuex crud state
5 | *
6 | * @param {String} snapshot The name of a component the state should be
7 | * initialised for
8 | * @param {Array} initialResources A list of resources to initialise the state
9 | *
10 | * @return {Object} The expected Vuex Crud mocked state
11 | */
12 | export default ({ snapshot = 'default', initialResources }) => {
13 | // New custom mutations configurations should be added here
14 | const snapshots = {
15 | default: initDefaultState,
16 | Resource: initStateForResource,
17 | }
18 | // Vuex Crud Initial State for a resource
19 | const initialResourceState = {
20 | createError: null,
21 | destroyError: null,
22 | entities: {},
23 | fetchListError: null,
24 | fetchSingleError: null,
25 | isCreating: false,
26 | isDestroying: false,
27 | isFetchingList: false,
28 | isFetchingSingle: false,
29 | isReplacing: false,
30 | isUpdating: false,
31 | list: [],
32 | replaceError: null,
33 | updateError: null,
34 | }
35 | // Vuex Initial State for entities
36 | const initialEntitiesState = {}
37 | // Vuex Initial State for resource routes
38 | const initialResourcesState = {
39 | routes: initialResourcesRoutes(initialResources),
40 | }
41 |
42 | /**
43 | * initResourcesCrud - Given a list of resources, creates mocked vuex crud
44 | * state for each of them
45 | *
46 | * @param {Array} resources An array of strings
47 | *
48 | * @return {Object} An object with mocked vuex crud state
49 | */
50 | function initResourcesState(resources) {
51 | const _resources = {}
52 | resources.forEach(resource => {
53 | _resources[resource] = initialResourceState
54 | })
55 | return _resources
56 | }
57 | // Initialises default state
58 | function initDefaultState() {
59 | return {
60 | ...initResourcesState(initialResources),
61 | entities: initialEntitiesState,
62 | resources: initialResourcesState,
63 | }
64 | }
65 | // Initialises state for a Resource component
66 | function initStateForResource() {
67 | return {}
68 | }
69 |
70 | snapshots[snapshot]()
71 | }
72 |
--------------------------------------------------------------------------------
/tests/unit/factory/store/modules/index.js:
--------------------------------------------------------------------------------
1 | import { createCrudModule as _createCrudModule } from '@store/modules'
2 | import defaults from '@components/Resource/src/defaults'
3 |
4 | export const createCrudModule = args => {
5 | const apiUrl = 'localhost/api/'
6 | return _createCrudModule({
7 | apiUrl,
8 | resourceName: 'resource',
9 | resourceIdName: 'id',
10 | parseResponses: defaults().props.parseResponses,
11 | ...args,
12 | })
13 | }
14 |
--------------------------------------------------------------------------------
/tests/unit/fixtures/admin/index.js:
--------------------------------------------------------------------------------
1 | import { AppLayout, AuthLayout, UnauthorizedLayout } from '@components/Layouts'
2 | import Core from '@components/Core'
3 | import defaults, {
4 | authenticatedDefaults,
5 | unauthenticatedDefaults,
6 | } from '@components/Admin/src/defaults'
7 | import { SimpleSidebar } from '@components/UiComponents'
8 |
9 | export default {
10 | props: {
11 | ...defaults().props,
12 | },
13 | args: {
14 | ...defaults().args,
15 | },
16 | }
17 |
18 | export const Authenticated = {
19 | props: {
20 | layout: AppLayout,
21 | sidebar: SimpleSidebar,
22 | title: 'A toolbar text',
23 | unauthorized: UnauthorizedLayout,
24 | },
25 | args: {
26 | Core,
27 | ...authenticatedDefaults.args,
28 | },
29 | }
30 |
31 | export const Unauthenticated = {
32 | props: {
33 | layout: AuthLayout,
34 | },
35 | args: {
36 | ...unauthenticatedDefaults.args,
37 | },
38 | }
39 |
--------------------------------------------------------------------------------
/tests/unit/fixtures/auth/index.js:
--------------------------------------------------------------------------------
1 | import defaults from '@components/Layouts/src/AuthLayout/defaults'
2 |
3 | export default state => {
4 | const { username: userCredential, password: passwordCredential } = state
5 |
6 | return {
7 | props: {
8 | authFormTitle: defaults().props.authFormTitle,
9 | authFooter: defaults().props.authFooter,
10 | authMainContent: defaults().props.authMainContent,
11 | usernameRules: defaults().props.usernameRules,
12 | passwordRules: defaults().props.passwordRules,
13 | va: {
14 | login: (username, password) => {
15 | return username === userCredential && password === passwordCredential
16 | ? { then: () => {} }
17 | : new Promise(resolve => {
18 | resolve({
19 | response: {
20 | status: 401,
21 | },
22 | })
23 | })
24 | },
25 | },
26 | },
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/unit/fixtures/resource/magazines.js:
--------------------------------------------------------------------------------
1 | import CreateMagazines from '@/../demo/components/magazines/CreateMagazines'
2 | import EditMagazines from '@/../demo/components/magazines/EditMagazines'
3 | import ListMagazines from '@/../demo/components/magazines/ListMagazines'
4 | import ShowMagazines from '@/../demo/components/magazines/ShowMagazines'
5 | import defaults from '@components/Resource/src/defaults'
6 | import { Types } from '@store/modules/resources'
7 |
8 | const { namespace, RESOURCES_ADD_ROUTE } = Types
9 |
10 | export default {
11 | props: Object.assign({}, defaults().props, {
12 | name: 'magazines',
13 | apiUrl: 'http://localhost:8888',
14 | resourceIdName: defaults().props.resourceIdName,
15 | userPermissionsField: defaults().props.userPermissionsField,
16 | create: CreateMagazines,
17 | edit: EditMagazines,
18 | list: ListMagazines,
19 | show: ShowMagazines,
20 | redirect: defaults().props.redirect(),
21 | parseResponses: defaults().props.parseResponses(),
22 | }),
23 | methods: {
24 | storeMethods: {
25 | [`${namespace}/${RESOURCES_ADD_ROUTE}`]: {
26 | params: {
27 | path: '/magazines',
28 | name: 'magazines',
29 | },
30 | },
31 | },
32 | },
33 | }
34 |
--------------------------------------------------------------------------------
/tests/unit/fixtures/ui-components/date.input.js:
--------------------------------------------------------------------------------
1 | import defaults from '@components/UiComponents/DateField/src/defaults'
2 |
3 | export default {
4 | props: {
5 | disabled: defaults().props.disabled,
6 | format: () => {},
7 | name: defaults().props.name,
8 | parse: () => {},
9 | placeholder: 'select a date',
10 | readonly: defaults().props.readonly,
11 | vDatePickerProps: defaults().props.vDatePickerProps(),
12 | vMenuProps: defaults().props.vMenuProps(),
13 | },
14 | }
15 |
--------------------------------------------------------------------------------
/tests/unit/lib/constants.js:
--------------------------------------------------------------------------------
1 | export default {
2 | VUETIFY_TEXT_FIELD_LABEL_DETAILS_CLASS: '.v-messages__message',
3 | }
4 |
--------------------------------------------------------------------------------
/tests/unit/lib/utils/wrapper.js:
--------------------------------------------------------------------------------
1 | const nextTick = wrapper => {
2 | return new Promise(resolve => wrapper.vm.$nextTick(resolve))
3 | }
4 |
5 | const findElemByName = ({ wrapper, el, name, prop = 'name' }) => {
6 | return wrapper.find(`${el}[${prop}="${name}"]`)
7 | }
8 |
9 | const findRef = ({ wrapper, ref }) => wrapper.find({ ref })
10 |
11 | export { findElemByName, findRef, nextTick }
12 |
--------------------------------------------------------------------------------
/tests/unit/specs/components/admin/unauthenticated.spec.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueRouter from 'vue-router'
3 | import Vuex from 'vuex'
4 | import Unauthenticated from '@components/Admin/src/Unauthenticated'
5 | import Factory from '@unit/factory'
6 | import AuthTypes from '@va-auth/types'
7 | import authStore from '@va-auth/store'
8 | import { shallowMount } from '@vue/test-utils'
9 | import { Unauthenticated as unauthenticatedFixture } from '@unit/fixtures/admin'
10 |
11 | describe('Unauthenticated.vue', () => {
12 | const subject = 'Unauthenticated'
13 |
14 | Vue.use(Vuex)
15 |
16 | // subject
17 | let subjectWrapper
18 | // mocks
19 | let mockedRouter
20 | let mockedStore
21 | let mocks
22 | // spies
23 | let storeSpy
24 | // props
25 | let propsData
26 |
27 | function mountSubject() {
28 | subjectWrapper = shallowMount(Unauthenticated, {
29 | mocks,
30 | propsData,
31 | router: mockedRouter,
32 | sync: true,
33 | })
34 | }
35 |
36 | beforeEach(() => {
37 | const routes = [{}]
38 | mockedRouter = new VueRouter(routes)
39 | mockedStore = new Vuex.Store({
40 | modules: {
41 | auth: authStore({ client: () => new Promise(() => {}) }),
42 | },
43 | })
44 | mocks = { $store: mockedStore, $router: mockedRouter }
45 | propsData = {
46 | layout: unauthenticatedFixture.props.layout,
47 | }
48 | storeSpy = {
49 | dispatch: jest.spyOn(mocks.$store, 'dispatch'),
50 | }
51 | })
52 |
53 | afterEach(() => {
54 | subjectWrapper = {}
55 | })
56 |
57 | it('should have props', () => {
58 | mountSubject()
59 |
60 | const props = subjectWrapper.props()
61 |
62 | expect(subjectWrapper.name()).toMatch(subject)
63 | expect(props.layout).toMatchObject(unauthenticatedFixture.props.layout)
64 | })
65 |
66 | it('[Auth] - component is rendered', async () => {
67 | mountSubject()
68 |
69 | const {
70 | props: { layout },
71 | } = unauthenticatedFixture
72 | const authComponent = subjectWrapper.find(layout)
73 |
74 | expect(authComponent.exists()).toBe(true)
75 | })
76 |
77 | it('when the login method is called an [AUTH_LOGIN_REQUEST] action is dispatched', () => {
78 | mountSubject()
79 |
80 | const { namespace, AUTH_LOGIN_REQUEST } = AuthTypes
81 | const action = `${namespace}/${AUTH_LOGIN_REQUEST}`
82 | const { username, password } = Factory.createCredentials()
83 |
84 | subjectWrapper.vm.login(username, password)
85 |
86 | expect(storeSpy.dispatch).toHaveBeenCalledTimes(1)
87 | expect(storeSpy.dispatch).toHaveBeenCalledWith(action, {
88 | username,
89 | password,
90 | })
91 | })
92 | })
93 |
--------------------------------------------------------------------------------
/tests/unit/specs/layouts/applayout.spec.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuetify from 'vuetify'
3 | import Vuex from 'vuex'
4 | import AppLayout from '@components/Layouts/src/AppLayout'
5 | import SimpleSidebar from '@components/UiComponents/Sidebar/SimpleSidebar'
6 | import AuthTypes from '@va-auth/types'
7 | import { mount } from '@vue/test-utils'
8 |
9 | describe('AppLayout', () => {
10 | Vue.use(Vuetify)
11 | Vue.use(Vuex)
12 |
13 | let subjectWrapper
14 | // stubs
15 | let propsData
16 | let vuetify
17 | // mocks
18 | let mocks
19 | let mockedStore
20 |
21 | // Mounts the component
22 | function mountSubject() {
23 | subjectWrapper = mount(AppLayout, {
24 | mocks,
25 | propsData,
26 | vuetify,
27 | })
28 | }
29 |
30 | beforeEach(() => {
31 | AppLayout.install(Vue)
32 | // Configures the subject props
33 | propsData = {
34 | sidebar: SimpleSidebar,
35 | title: 'My AppLayout title',
36 | va: {
37 | getUser: () => {
38 | const { namespace, AUTH_GET_USER } = AuthTypes
39 | return this.$store.getters[`${namespace}/${AUTH_GET_USER}`]
40 | },
41 | logout: () => {
42 | const { namespace: authNamespace, AUTH_LOGOUT_REQUEST } = AuthTypes
43 | const actionName = `${authNamespace}/${AUTH_LOGOUT_REQUEST}`
44 | mockedStore.dispatch(actionName)
45 | },
46 | },
47 | }
48 | vuetify = new Vuetify()
49 | mockedStore = new Vuex.Store({})
50 | mocks = {
51 | $store: mockedStore,
52 | }
53 | })
54 |
55 | it('should have props', () => {
56 | mountSubject()
57 | const appLayoutProps = subjectWrapper.props()
58 |
59 | expect(appLayoutProps.sidebar).toBe(propsData.sidebar)
60 | expect(appLayoutProps.title).toMatch(propsData.title)
61 | expect(appLayoutProps.va).toBe(propsData.va)
62 | })
63 | })
64 |
--------------------------------------------------------------------------------
/tests/unit/specs/layouts/homelayout.spec.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuetify from 'vuetify'
3 | import HomeLayout from '@components/Layouts/src/HomeLayout'
4 | import { mount } from '@vue/test-utils'
5 |
6 | describe('HomeLayout', () => {
7 | Vue.use(Vuetify)
8 |
9 | let subjectWrapper
10 | // stubs
11 | let propsData
12 | let vuetify
13 |
14 | // Mounts the component
15 | function mountSubject() {
16 | subjectWrapper = mount(HomeLayout, {
17 | propsData,
18 | vuetify,
19 | })
20 | }
21 |
22 | beforeEach(() => {
23 | HomeLayout.install(Vue)
24 | vuetify = new Vuetify()
25 | })
26 |
27 | it('should have props', () => {
28 | mountSubject()
29 |
30 | expect(subjectWrapper.text()).toMatch('Welcome to Vue-Admin')
31 | })
32 | })
33 |
--------------------------------------------------------------------------------
/tests/unit/specs/ui-components/date.input.spec.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuetify from 'vuetify'
3 | import { mount } from '@vue/test-utils'
4 | import DateField from '@components/UiComponents/DateField'
5 | import ERROR_MESSAGES from '@constants/error.messages'
6 | import dateInputFixture from '@unit/fixtures/ui-components/date.input.js'
7 |
8 | describe('DateField.vue', () => {
9 | const subject = 'DateField'
10 | // Initialises the vue instance and DateField dependencies
11 | Vue.use(Vuetify)
12 |
13 | let vuetify
14 | let subjectWrapper
15 | let propsData
16 |
17 | beforeEach(() => {
18 | // Configures the subject props
19 | propsData = {
20 | ...dateInputFixture.props,
21 | }
22 | vuetify = new Vuetify()
23 | })
24 |
25 | it('should have default props', () => {
26 | // Exercise: mounts the subject instance
27 | mountSubject()
28 | const props = subjectWrapper.props()
29 | expect(subjectWrapper.name()).toMatch(subject)
30 | expect(props.disabled).toBe(false)
31 | expect(props.format).toBeDefined()
32 | expect(props.name).toMatch(dateInputFixture.props.name)
33 | expect(props.parse).toBeDefined()
34 | expect(props.readonly).toBe(true)
35 | expect(props.vDatePickerProps).toMatchObject(
36 | dateInputFixture.props.vDatePickerProps
37 | )
38 | expect(props.vMenuProps).toMatchObject(dateInputFixture.props.vMenuProps)
39 | })
40 |
41 | it('should have non default props', () => {
42 | mountSubject()
43 | const props = subjectWrapper.props()
44 | expect(props.placeholder).toBe(dateInputFixture.props.placeholder)
45 | })
46 |
47 | it('throws Error when the {format} property is missing', () => {
48 | const prop = 'format'
49 | shouldThrowOnMissingProp({ prop, subject })
50 | })
51 |
52 | it('throws Error when the {parse} proprty is missing', () => {
53 | const prop = 'parse'
54 | shouldThrowOnMissingProp({ prop, subject })
55 | })
56 |
57 | it('throws Error when the {valid} property is missing', () => {
58 | const prop = 'valid'
59 | shouldThrowOnMissingProp({ prop, subject })
60 | })
61 |
62 | /**
63 | * Helper functions
64 | */
65 |
66 | function shouldThrowOnMissingProp({ prop, subject }) {
67 | // Setup: deletes the list prop before mounting
68 | delete propsData[prop]
69 | const { UNDEFINED_PROPERTY } = ERROR_MESSAGES
70 | const at = subject
71 | const message = UNDEFINED_PROPERTY.with({ prop, at })
72 | const spy = jest.spyOn(global.console, 'error').mockImplementation(() => {})
73 |
74 | try {
75 | // Exercise: mounts the subject instance
76 | mountSubject()
77 | } catch (error) {
78 | expect(error.message).toBe(message)
79 | } finally {
80 | spy.mockRestore()
81 | }
82 | }
83 |
84 | // Mounts the component
85 | function mountSubject() {
86 | subjectWrapper = mount(DateField, {
87 | propsData,
88 | vuetify,
89 | })
90 | }
91 | })
92 |
--------------------------------------------------------------------------------
/tests/unit/specs/ui-components/simple.text.spec.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuetify from 'vuetify'
3 | import { SimpleText } from '@components/UiComponents'
4 | import { defaults } from '@components/UiComponents/SimpleText/src/SimpleText'
5 | import Factory from '@unit/factory'
6 | import { shallowMount } from '@vue/test-utils'
7 |
8 | describe('SimpleText.vue', () => {
9 | Vue.use(Vuetify)
10 | const vuetify = new Vuetify()
11 |
12 | let defaultProps
13 | let propsData
14 | let subjectWrapper
15 |
16 | function mountSubject() {
17 | subjectWrapper = shallowMount(SimpleText, {
18 | propsData,
19 | vuetify,
20 | })
21 | }
22 |
23 | beforeEach(() => {
24 | SimpleText.install(Vue)
25 | defaultProps = defaults().props
26 |
27 | propsData = {
28 | parse: value => value,
29 | type: 'p',
30 | value: 'Im an empty content',
31 | }
32 | })
33 |
34 | afterEach(() => {
35 | subjectWrapper = {}
36 | })
37 |
38 | it('should have props', () => {
39 | mountSubject()
40 |
41 | const props = subjectWrapper.props()
42 |
43 | expect(props.type).toMatch(propsData.type)
44 | expect(props.value).toMatch(propsData.value)
45 | })
46 |
47 | it('should use default props when none were provided', () => {
48 | delete propsData.type
49 | delete propsData.parse
50 | delete propsData.value
51 | mountSubject()
52 |
53 | // Asserts to the post-mounting generated props by default
54 | const { parse, type, value } = defaultProps
55 |
56 | const response = Factory.resource.responses.getSingle()
57 |
58 | expect(subjectWrapper.vm._props.parse(response)).toMatchObject(
59 | parse(response)
60 | )
61 | expect(subjectWrapper.vm._props.type).toMatch(type)
62 | expect(subjectWrapper.vm._props.value).toMatch(value)
63 | })
64 |
65 | it('should render a parsed content', () => {
66 | mountSubject()
67 |
68 | const value = subjectWrapper.vm.parse(propsData.value)
69 |
70 | expect(subjectWrapper.text()).toMatch(value)
71 | })
72 | })
73 |
--------------------------------------------------------------------------------
/tests/unit/specs/ui-components/spinner.spec.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuetify from 'vuetify'
3 | import { Spinner } from '@components/UiComponents'
4 | import { defaults } from '@components/UiComponents/Spinner/Spinner'
5 | import { findElemByName } from '@unit/lib/utils/wrapper'
6 | import { shallowMount } from '@vue/test-utils'
7 |
8 | describe('Spinner.vue', () => {
9 | Vue.use(Vuetify)
10 | const vuetify = new Vuetify()
11 |
12 | let defaultProps
13 | let propsData
14 | let subjectWrapper
15 |
16 | function mountSubject() {
17 | subjectWrapper = shallowMount(Spinner, {
18 | propsData,
19 | vuetify,
20 | })
21 | }
22 |
23 | function findSpinnerContainer({ name }) {
24 | return findElemByName({
25 | wrapper: subjectWrapper,
26 | el: 'v-container-stub',
27 | name: name,
28 | prop: 'id',
29 | })
30 | }
31 |
32 | beforeEach(() => {
33 | Spinner.install(Vue)
34 | defaultProps = defaults().props
35 |
36 | propsData = {
37 | isLoading: true,
38 | name: 'a-custom-name',
39 | vProps: {
40 | color: 'warning',
41 | indeterminate: true,
42 | },
43 | }
44 | })
45 |
46 | afterEach(() => {
47 | subjectWrapper = {}
48 | })
49 |
50 | it('should have props', () => {
51 | mountSubject()
52 |
53 | const { isLoading, name, vProps } = propsData
54 | const props = subjectWrapper.props()
55 |
56 | expect(props.isLoading).toBe(isLoading)
57 | expect(props.name).toMatch(name)
58 | expect(props.vProps).toMatchObject(vProps)
59 | })
60 |
61 | it('should have default props', () => {
62 | delete propsData.isLoading
63 | delete propsData.name
64 | delete propsData.vProps
65 | mountSubject()
66 |
67 | const { isLoading, name, vProps } = defaultProps
68 | const props = subjectWrapper.props()
69 |
70 | expect(props.isLoading).toBe(isLoading)
71 | expect(props.name).toMatch(name)
72 | expect(props.vProps).toMatchObject(vProps)
73 | })
74 |
75 | it('should exists when isLoading is true', () => {
76 | mountSubject()
77 | subjectWrapper.setProps({ isLoading: true })
78 |
79 | const container = findSpinnerContainer({ name: propsData.name })
80 |
81 | expect(container.exists()).toBe(true)
82 | })
83 |
84 | it('should not exist when isLoading is false', () => {
85 | mountSubject()
86 | subjectWrapper.setProps({ isLoading: false })
87 |
88 | const container = findSpinnerContainer({ name: defaultProps.name })
89 |
90 | expect(container.exists()).toBe(false)
91 | })
92 | })
93 |
--------------------------------------------------------------------------------
/tests/unit/specs/ui-components/text.field.spec.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuetify from 'vuetify'
3 | import { TextField } from '@components/UiComponents'
4 | import { defaults } from '@components/UiComponents/TextField/src/TextField'
5 | import { findElemByName } from '@unit/lib/utils/wrapper'
6 | import { shallowMount } from '@vue/test-utils'
7 |
8 | describe('TextField.vue', () => {
9 | Vue.use(Vuetify)
10 | const vuetify = new Vuetify()
11 |
12 | let defaultProps
13 | let propsData
14 | let subjectWrapper
15 |
16 | function mountSubject() {
17 | subjectWrapper = shallowMount(TextField, {
18 | propsData,
19 | vuetify,
20 | })
21 | }
22 |
23 | beforeEach(() => {
24 | TextField.install(Vue)
25 | defaultProps = defaults().props
26 |
27 | propsData = {
28 | name: 'my-text-field-name',
29 | placeHolder: 'write your content here',
30 | value: 'Im an empty content',
31 | }
32 | })
33 |
34 | afterEach(() => {
35 | subjectWrapper = {}
36 | })
37 |
38 | it('should have props', () => {
39 | mountSubject()
40 |
41 | const props = subjectWrapper.props()
42 |
43 | expect(props.name).toMatch(propsData.name)
44 | expect(props.placeHolder).toMatch(propsData.placeHolder)
45 | expect(props.value).toMatch(propsData.value)
46 | })
47 |
48 | it('should use default props when none were provided', () => {
49 | delete propsData.name
50 | delete propsData.placeHolder
51 | delete propsData.value
52 | mountSubject()
53 |
54 | // Asserts to the post-mounting generated props by default
55 | const { name, placeHolder, value } = defaultProps
56 |
57 | expect(subjectWrapper.vm._props.name).toMatch(name)
58 | expect(subjectWrapper.vm._props.placeHolder).toMatch(placeHolder)
59 | expect(subjectWrapper.vm._props.value).toMatch(value)
60 | })
61 |
62 | it('should render a parsed content', () => {
63 | mountSubject()
64 |
65 | const textField = findElemByName({
66 | wrapper: subjectWrapper,
67 | el: 'v-text-field-stub',
68 | name: propsData.name,
69 | })
70 | const textFieldSpy = {
71 | emit: jest.spyOn(textField.vm, '$emit'),
72 | }
73 |
74 | subjectWrapper.vm.$nextTick()
75 |
76 | const newValue = 'a new val'
77 | textField.vm.inputValue = newValue
78 |
79 | expect(textFieldSpy.emit).toHaveBeenCalledTimes(1)
80 | expect(textFieldSpy.emit).toHaveBeenCalledWith('change', newValue)
81 | })
82 | })
83 |
--------------------------------------------------------------------------------
/utils/server-test/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "package",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "server.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "node server.js"
9 | },
10 | "dependencies": {
11 | "bavaria-ipsum": "^1.0.3",
12 | "bcrypt": "^5.0.0",
13 | "body-parser": "^1.18.2",
14 | "cors": "^2.8.4",
15 | "express": "^4.16.2",
16 | "fake-data-generator": "^0.1.10",
17 | "jsonwebtoken": "^8.5.0"
18 | },
19 | "author": "",
20 | "license": "ISC"
21 | }
22 |
--------------------------------------------------------------------------------
/utils/server-test/server.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 | const express = require('express')
3 | const bodyParser = require('body-parser')
4 |
5 | const createMagazinesService = require('./services/magazines')
6 | const createArticlesService = require('./services/articles')
7 | const createAuthorsService = require('./services/authors')
8 | const createAuthService = require('./services/auth')
9 |
10 | /* eslint-enable */
11 |
12 | var cors = require('cors')
13 | const app = express()
14 |
15 | app.use(cors())
16 | app.options('*', cors())
17 |
18 | app.use(express.static(__dirname))
19 |
20 | app.use(bodyParser.json())
21 |
22 | createArticlesService(app)
23 | createMagazinesService(app)
24 | createAuthService(app)
25 | createAuthorsService(app)
26 |
27 | const port = process.env.PORT || 8080
28 |
29 | module.exports = app.listen(port, () => {
30 | /* eslint-disable no-console */
31 | console.log(`Server listening on http://localhost:${port}, Ctrl+C to stop`)
32 | /* eslint-enable no-console */
33 | })
34 |
--------------------------------------------------------------------------------
/utils/server-test/services/articles.js:
--------------------------------------------------------------------------------
1 | const Ipsum = require('bavaria-ipsum')
2 |
3 | module.exports = function(app) {
4 | const ipsum = new Ipsum()
5 |
6 | const articles = [
7 | {
8 | id: 1,
9 | title: ipsum.generateSentence(),
10 | content: ipsum.generateParagraph(),
11 | },
12 | {
13 | id: 2,
14 | title: ipsum.generateSentence(),
15 | content: ipsum.generateParagraph(),
16 | },
17 | {
18 | id: 3,
19 | title: ipsum.generateSentence(),
20 | content: ipsum.generateParagraph(),
21 | },
22 | ]
23 |
24 | app.get('/api/articles', (req, res) => {
25 | res.json(articles)
26 | })
27 |
28 | app.get('/api/articles/:id', (req, res) => {
29 | const article = articles.find(a => a.id.toString() === req.params.id)
30 | const index = articles.indexOf(article)
31 |
32 | res.json(articles[index])
33 | })
34 |
35 | app.patch('/api/articles/:id', (req, res) => {
36 | const { body } = req
37 | const article = articles.find(a => a.id.toString() === req.params.id)
38 | const index = articles.indexOf(article)
39 |
40 | if (index >= 0) {
41 | article.title = body.title
42 | article.content = body.content
43 | articles[index] = article
44 | }
45 |
46 | res.json(article)
47 | })
48 |
49 | app.put('/api/articles/:id', (req, res) => {
50 | const { body } = req
51 | const article = articles.find(a => a.id.toString() === req.params.id)
52 | const index = articles.indexOf(article)
53 |
54 | if (index >= 0) {
55 | article.title = body.title
56 | article.content = body.content
57 | articles[index] = article
58 | }
59 |
60 | res.json(article)
61 | })
62 |
63 | app.delete('/api/articles/:id', (req, res) => {
64 | const article = articles.find(a => a.id.toString() === req.params.id)
65 | const index = articles.indexOf(article)
66 |
67 | if (index >= 0) articles.splice(index, 1)
68 |
69 | res.status(202).send()
70 | })
71 |
72 | app.post('/api/articles', (req, res) => {
73 | let id
74 | if (!articles.length) {
75 | id = 0
76 | } else {
77 | id = articles[articles.length - 1].id + 1
78 | }
79 | const { body } = req
80 |
81 | const article = {
82 | id,
83 | title: body.title,
84 | content: body.content,
85 | }
86 |
87 | articles.push(article)
88 |
89 | res.status(201).send(article)
90 | })
91 | }
92 |
--------------------------------------------------------------------------------
/utils/server-test/services/auth.js:
--------------------------------------------------------------------------------
1 | const bcrypt = require('bcrypt')
2 | const jwt = require('jsonwebtoken')
3 |
4 | const secret = 'mySecret'
5 |
6 | module.exports = function(app) {
7 | let whitelist = [
8 | {
9 | id: 123456,
10 | email: 'user@camba.coop',
11 | password: '$2b$08$ZRrJWppetUmKe5Zlc1Mtj.w51ZTmmx2M4YKUdS4QOBHXq2gzF/zyS', // bcrypt.hashSync('123456')
12 | permissions: ['guest'],
13 | token: '',
14 | },
15 | {
16 | id: 234567,
17 | email: 'dev@camba.coop',
18 | password: '$2b$08$ZRrJWppetUmKe5Zlc1Mtj.w51ZTmmx2M4YKUdS4QOBHXq2gzF/zyS', // bcrypt.hashSync('123456')
19 | permissions: ['admin'],
20 | token: '',
21 | },
22 | ]
23 |
24 | app.post('/api/auth', (req, res) => {
25 | const user = whitelist.find(user => user.email === req.headers.username)
26 | if (!user) return res.status(401).send('Invalid user or password')
27 | const isPasswordValid = bcrypt.compareSync(
28 | req.headers.password,
29 | user.password
30 | )
31 | if (!isPasswordValid)
32 | return res.status(401).send('Invalid user or password')
33 | const token = jwt.sign({ id: user.id }, secret, { expiresIn: 3600 })
34 | // Saves the token in the whitelist user array
35 | whitelist = whitelist.map(_user => {
36 | if (_user.id === user.id) {
37 | _user.token = token
38 | return _user
39 | }
40 | return _user
41 | })
42 | const newUser = Object.assign({}, user)
43 | delete newUser.password
44 | delete newUser.token
45 | return res.status(200).send({ auth: true, token, user: newUser })
46 | })
47 |
48 | app.get('/api/auth', (req, res) => {
49 | const token = req.headers.token
50 | if (!token) return res.status(401).send('Invalid token')
51 | jwt.verify(token, secret, (err, decodedToken) => {
52 | if (err) return res.status(401).send('Token is invalid or has expired')
53 | const user = {}
54 | Object.assign(user, whitelist.find(user => user.id === decodedToken.id))
55 | delete user.token
56 | delete user.password
57 | return res.status(200).send({ user })
58 | })
59 | })
60 | }
61 |
--------------------------------------------------------------------------------
/utils/server-test/services/authors.js:
--------------------------------------------------------------------------------
1 | const { generateModel } = require('fake-data-generator')
2 |
3 | module.exports = function(app) {
4 | const model = {
5 | config: {
6 | locale: 'en',
7 | },
8 | model: {
9 | type: 'Object',
10 | value: {
11 | id: {
12 | type: 'randomNumberBetween',
13 | value: [1, 2500000],
14 | },
15 | name: {
16 | type: 'faker',
17 | value: 'name.firstName',
18 | },
19 | lastname: {
20 | type: 'faker',
21 | value: 'name.lastName',
22 | },
23 | birthdate: {
24 | type: 'faker',
25 | value: 'date.between',
26 | options: ['1970-01-01', '1996-12-31'],
27 | },
28 | },
29 | },
30 | }
31 |
32 | const authors = generateModel({
33 | amountArg: 40,
34 | modelArg: model,
35 | inputType: 'object',
36 | outputType: 'object',
37 | })
38 |
39 | app.get('/api/authors', (req, res) => {
40 | res.json(authors)
41 | })
42 |
43 | app.get('/api/authors/:id', (req, res) => {
44 | const author = authors.find(a => a.id.toString() === req.params.id)
45 | const index = authors.indexOf(author)
46 |
47 | res.json(authors[index])
48 | })
49 |
50 | app.patch('/api/authors/:id', (req, res) => {
51 | const { body } = req
52 | const author = authors.find(a => a.id.toString() === req.params.id)
53 | const index = authors.indexOf(author)
54 |
55 | if (index >= 0) {
56 | author.name = body.name
57 | author.lastname = body.lastname
58 | author.birthdate = body.birthdate
59 | authors[index] = author
60 | }
61 |
62 | res.json(author)
63 | })
64 |
65 | app.put('/api/authors/:id', (req, res) => {
66 | const { body } = req
67 | const author = authors.find(a => a.id.toString() === req.params.id)
68 | const index = authors.indexOf(author)
69 |
70 | if (index >= 0) {
71 | author.name = body.name
72 | author.lastname = body.lastname
73 | author.birthdate = body.birthdate
74 | authors[index] = author
75 | }
76 |
77 | res.json(author)
78 | })
79 |
80 | app.delete('/api/authors/:id', (req, res) => {
81 | const author = authors.find(a => a.id.toString() === req.params.id)
82 | const index = authors.indexOf(author)
83 |
84 | if (index >= 0) authors.splice(index, 1)
85 |
86 | res.status(202).send()
87 | })
88 |
89 | app.post('/api/authors', (req, res) => {
90 | let id
91 | if (!authors.length) {
92 | id = 0
93 | } else {
94 | id = authors[authors.length - 1].id + 1
95 | }
96 | const { body } = req
97 |
98 | const author = {
99 | id,
100 | name: body.name,
101 | lastname: body.lastname,
102 | birthdate: body.birthdate,
103 | }
104 |
105 | authors.push(author)
106 |
107 | res.status(201).send(author)
108 | })
109 | }
110 |
--------------------------------------------------------------------------------
/utils/server-test/services/magazines.js:
--------------------------------------------------------------------------------
1 | const Ipsum = require('bavaria-ipsum')
2 |
3 | module.exports = function(app) {
4 | const ipsum = new Ipsum()
5 |
6 | const magazines = [
7 | {
8 | id: 1,
9 | name: 'Console log Oriented Programming',
10 | articles: [1, 2, 3],
11 | issue: '#20',
12 | publisher: ipsum.generateParagraph(1),
13 | },
14 | {
15 | id: 2,
16 | name: ipsum.generateSentence(),
17 | articles: [],
18 | issue: '#13',
19 | publisher: ipsum.generateParagraph(1),
20 | },
21 | {
22 | id: 3,
23 | name: ipsum.generateSentence(),
24 | articles: [],
25 | issue: '#7',
26 | publisher: ipsum.generateParagraph(1),
27 | },
28 | ]
29 |
30 | app.get('/api/magazines', (req, res) => {
31 | res.json(magazines)
32 | })
33 |
34 | app.get('/api/magazines/:id', (req, res) => {
35 | const magazine = magazines.find(a => a.id.toString() === req.params.id)
36 | const index = magazines.indexOf(magazine)
37 |
38 | res.json(magazines[index])
39 | })
40 |
41 | app.patch('/api/magazines/:id', (req, res) => {
42 | const { body } = req
43 | const magazine = magazines.find(a => a.id.toString() === req.params.id)
44 | const index = magazines.indexOf(magazine)
45 |
46 | if (index >= 0) {
47 | magazine.name = body.name
48 | magazine.editorial = body.editorial
49 | magazine.issue = body.issue
50 | magazine.publisher = body.publisher
51 | magazines[index] = magazine
52 | }
53 |
54 | res.json(magazine)
55 | })
56 |
57 | app.put('/api/magazines/:id', (req, res) => {
58 | const { body } = req
59 | const magazine = magazines.find(a => a.id.toString() === req.params.id)
60 | const index = magazines.indexOf(magazine)
61 |
62 | if (index >= 0) {
63 | magazine.name = body.name
64 | magazine.editorial = body.editorial
65 | magazine.issue = body.issue
66 | magazine.publisher = body.publisher
67 | magazines[index] = magazine
68 | }
69 |
70 | res.json(magazine)
71 | })
72 |
73 | app.delete('/api/magazines/:id', (req, res) => {
74 | const magazine = magazines.find(a => a.id.toString() === req.params.id)
75 | const index = magazines.indexOf(magazine)
76 |
77 | if (index >= 0) magazines.splice(index, 1)
78 |
79 | res.status(202).send()
80 | })
81 |
82 | app.post('/api/magazines', (req, res) => {
83 | const id = magazines[magazines.length - 1].id + 1
84 | const { body } = req
85 |
86 | const magazine = {
87 | id,
88 | name: body.name,
89 | editorial: body.editorial,
90 | issue: body.issue,
91 | publisher: body.publisher,
92 | }
93 |
94 | magazines.push(magazine)
95 |
96 | res.status(201).send(magazine)
97 | })
98 | }
99 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | transpileDependencies: ['vuetify'],
5 | css: {
6 | loaderOptions: {
7 | sass: {
8 | implementation: require('sass'),
9 | fiber: require('fibers'),
10 | },
11 | },
12 | },
13 | configureWebpack: {
14 | resolve: {
15 | alias: {
16 | '@assets': path.resolve(__dirname, 'src/assets'),
17 | '@components': path.resolve(__dirname, 'src/components'),
18 | '@constants': path.resolve(__dirname, 'src/constants'),
19 | '@demo': path.resolve(__dirname, 'demo'),
20 | '@e2e': path.resolve(__dirname, 'tests/e2e'),
21 | '@handlers': path.resolve(__dirname, 'src/handlers'),
22 | '@plugins': path.resolve(__dirname, 'src/plugins'),
23 | '@router': path.resolve(__dirname, 'src/router'),
24 | '@store': path.resolve(__dirname, 'src/store'),
25 | '@templates': path.resolve(__dirname, 'src/templates/src'),
26 | '@va-auth': path.resolve(__dirname, 'src/va-auth/src'),
27 | '@validators': path.resolve(__dirname, 'src/validators/src'),
28 | },
29 | },
30 | },
31 | }
32 |
--------------------------------------------------------------------------------