├── .browserslistrc ├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── .nvmrc ├── README.md ├── babel.config.js ├── bitbucket-pipelines.yml ├── jest.config.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── index.html ├── server.js ├── src ├── entities │ ├── article │ │ ├── article.mock.ts │ │ ├── article.spec.ts │ │ ├── article.ts │ │ ├── article.types.ts │ │ └── index.ts │ ├── comment │ │ ├── comment.mock.ts │ │ ├── comment.spec.ts │ │ ├── comment.ts │ │ ├── comment.types.ts │ │ └── index.ts │ └── index.ts ├── main.ts ├── services │ ├── articles │ │ ├── articles.mock.ts │ │ ├── articles.spec.ts │ │ ├── articles.ts │ │ ├── articles.types.ts │ │ └── index.ts │ ├── data.json │ ├── index.ts │ ├── provider.mock.ts │ ├── provider.spec.ts │ ├── provider.ts │ └── provider.types.ts ├── store │ ├── actions │ │ ├── actions.mock.ts │ │ ├── actions.spec.ts │ │ ├── actions.ts │ │ ├── actions.types.ts │ │ └── index.ts │ ├── getters │ │ ├── getters.mock.ts │ │ ├── getters.spec.ts │ │ ├── getters.ts │ │ ├── getters.types.ts │ │ └── index.ts │ ├── index.ts │ ├── mutations │ │ ├── index.ts │ │ ├── mutations.mock.ts │ │ ├── mutations.spec.ts │ │ ├── mutations.ts │ │ └── mutations.types.ts │ ├── storage │ │ ├── index.ts │ │ ├── storage.mock.ts │ │ ├── storage.spec.ts │ │ ├── storage.ts │ │ └── storage.types.ts │ ├── store.mock.ts │ ├── store.ts │ └── store.types.ts └── ui │ ├── app.tsx │ ├── components │ ├── article │ │ ├── article.spec.ts │ │ ├── article.tsx │ │ ├── article.types.ts │ │ └── index.ts │ ├── full │ │ ├── full.spec.ts │ │ ├── full.tsx │ │ ├── full.types.ts │ │ └── index.ts │ └── index.ts │ ├── index.ts │ ├── pages │ ├── article │ │ ├── article.spec.ts │ │ ├── article.tsx │ │ └── index.ts │ ├── home │ │ ├── home.spec.ts │ │ ├── home.tsx │ │ └── index.ts │ └── index.ts │ ├── plugins │ ├── index.ts │ ├── services.ts │ ├── storage.ts │ └── vuetify.ts │ ├── router.ts │ ├── shims-provider.d.ts │ ├── shims-storage.d.ts │ ├── shims-tsx.d.ts │ ├── shims-vue.d.ts │ └── vue-ts-component.ts ├── tsconfig.json └── vue.config.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: [ 7 | 'plugin:vue/essential', 8 | '@vue/standard', 9 | '@vue/typescript/recommended' 10 | ], 11 | parserOptions: { 12 | ecmaVersion: 2020 13 | }, 14 | rules: { 15 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 16 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 17 | semi: [ 18 | 'error', 19 | 'never' 20 | ], 21 | quotes: [ 22 | 'error', 23 | 'single' 24 | ], 25 | '@typescript-eslint/explicit-function-return-type': [ 26 | 'error', 27 | { 28 | allowExpressions: false, 29 | allowTypedFunctionExpressions: true, 30 | allowHigherOrderFunctions: true 31 | } 32 | ], 33 | '@typescript-eslint/interface-name-prefix': [ 34 | 'error', 35 | 'always' 36 | ], 37 | 'no-unused-vars': 'off', // note you must disable the base rule as it can report incorrect errors 38 | '@typescript-eslint/no-unused-vars': [ 39 | 'error', 40 | { 41 | vars: 'all', 42 | args: 'after-used', 43 | ignoreRestSiblings: false 44 | } 45 | ], 46 | 'no-useless-constructor': 'off', 47 | '@typescript-eslint/no-useless-constructor': 'error', 48 | '@typescript-eslint/member-delimiter-style': [ 49 | 'error', 50 | { 51 | multiline: { 52 | delimiter: 'none', 53 | requireLast: false 54 | }, 55 | singleline: { 56 | delimiter: 'semi', 57 | requireLast: false 58 | } 59 | } 60 | ] 61 | }, 62 | overrides: [ 63 | { 64 | files: [ 65 | '**/__tests__/*.{j,t}s?(x)', 66 | '**/tests/unit/**/*.spec.{j,t}s?(x)' 67 | ], 68 | env: { 69 | jest: true 70 | } 71 | } 72 | ] 73 | } 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | 23 | coverage 24 | .vscode -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v12.10.0 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Building Vue Enterprise Application 2 | 3 | > Applying Principles of Clean Architecture to Enterprise Frontend Application 4 | 5 | ## Project setup 6 | - install Node.js (the version is specified in package.json) 7 | - if you use nvm: 8 | ``` 9 | nvm use 10 | ``` 11 | - Install dependencies 12 | ``` 13 | npm install 14 | ``` 15 | 16 | ## Compiles and hot-reloads for development 17 | ``` 18 | npm run serve 19 | ``` 20 | 21 | ## Compiles and minifies for production 22 | ``` 23 | npm run build 24 | ``` 25 | 26 | ## Run your unit tests 27 | ``` 28 | npm run test 29 | ``` 30 | ## Lints and fixes files 31 | ``` 32 | npm run lint 33 | ``` 34 | 35 | ## Deployments 36 | - [Production](https://vue-vuex-ts-services.herokuapp.com) 37 | 38 | ## Tech stack 39 | - Vue/Vuex 40 | - Vuetify 41 | - TypeScript / ESLint 42 | - Jest 43 | 44 | ## Links 45 | [Complete tutorial](https://medium.com/@gregsolo/building-vue-enterprise-application-part-0-overture-6d41bea14236) 46 | 47 | ### Images Credis: 48 | - TheRegisti https://unsplash.com/photos/VEbUF1BnGkA 49 | - Daniel Olah https://unsplash.com/photos/6KQETG8J-zI 50 | - Spencer Davis https://unsplash.com/photos/ipG88UDIQdg 51 | - Victor Ene https://unsplash.com/photos/siOiG_2KBpA 52 | - Alec Krum https://unsplash.com/photos/PkXNYci4p4Y 53 | - Cole Wyland https://unsplash.com/photos/7Dn0hmvnCh8 54 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /bitbucket-pipelines.yml: -------------------------------------------------------------------------------- 1 | image: node:12.10.0 2 | options: 3 | max-time: 10 4 | 5 | pipelines: 6 | branches: 7 | master: 8 | - step: 9 | name: Install and Test and Deploy to Heroku 10 | script: 11 | - npm i 12 | - npm run lint 13 | - npm run test 14 | - git push https://heroku:$HEROKU_API_KEY@git.heroku.com/$HEROKU_APP_NAME.git HEAD 15 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel', 3 | transform: { 4 | '^.+\\.vue$': 'vue-jest', 5 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', 6 | '^.+\\.tsx?$': 'ts-jest' 7 | }, 8 | transformIgnorePatterns: ['/node_modules/'], 9 | moduleNameMapper: { 10 | '^@/(.*)$': '/src/$1' 11 | }, 12 | testMatch: [ 13 | '**/*.spec.(js|jsx|ts|tsx)' 14 | ], 15 | roots: [ 16 | '/src' 17 | ], 18 | collectCoverageFrom: [ 19 | '**/entities/**/*.ts', 20 | '**/services/**/*.ts', 21 | '**/store/**/*.ts', 22 | '**/ui/components/*/**.tsx', 23 | '**/ui/pages/*/**.tsx', 24 | '!**/store.ts', 25 | '!**/*.mock.ts', 26 | '!**/*.types.ts', 27 | '!**/index.ts' 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-vuex-ts", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node server.js", 7 | "serve": "vue-cli-service serve", 8 | "build": "vue-cli-service build", 9 | "lint": "vue-cli-service lint", 10 | "test": "vue-cli-service test:unit --coverage", 11 | "test:watch": "vue-cli-service test:unit --watch --coverage" 12 | }, 13 | "dependencies": { 14 | "connect-history-api-fallback": "1.6.0", 15 | "core-js": "^3.6.4", 16 | "express": "4.17.1", 17 | "vue": "^2.6.11", 18 | "vue-class-component": "^7.2.2", 19 | "vue-property-decorator": "^8.3.0", 20 | "vue-router": "^3.1.5", 21 | "vuetify": "^2.1.0", 22 | "vuex": "^3.1.2" 23 | }, 24 | "devDependencies": { 25 | "@types/jest": "^24.0.19", 26 | "@typescript-eslint/eslint-plugin": "^2.18.0", 27 | "@typescript-eslint/parser": "^2.18.0", 28 | "@vue/cli-plugin-babel": "~4.2.0", 29 | "@vue/cli-plugin-eslint": "~4.2.0", 30 | "@vue/cli-plugin-router": "~4.2.0", 31 | "@vue/cli-plugin-typescript": "~4.2.0", 32 | "@vue/cli-plugin-unit-jest": "~4.2.0", 33 | "@vue/cli-plugin-vuex": "~4.2.0", 34 | "@vue/cli-service": "~4.2.0", 35 | "@vue/eslint-config-standard": "^5.1.0", 36 | "@vue/eslint-config-typescript": "^5.0.1", 37 | "@vue/test-utils": "1.0.0-beta.31", 38 | "eslint": "^6.7.2", 39 | "eslint-plugin-import": "^2.20.1", 40 | "eslint-plugin-node": "^11.0.0", 41 | "eslint-plugin-promise": "^4.2.1", 42 | "eslint-plugin-standard": "^4.0.0", 43 | "eslint-plugin-vue": "^6.1.2", 44 | "pre-commit": "1.2.2", 45 | "sass": "^1.19.0", 46 | "sass-loader": "^8.0.0", 47 | "typescript": "~3.7.5", 48 | "vue-cli-plugin-vuetify": "2.0.4", 49 | "vue-template-compiler": "^2.6.11", 50 | "vuetify-loader": "^1.3.0" 51 | }, 52 | "engines": { 53 | "node": "12.10.0", 54 | "npm": "6.10.3" 55 | }, 56 | "pre-commit": [ 57 | "lint", 58 | "test" 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soloschenko-grigoriy/vue-vuex-ts/f668dd66839f0cb8ca885bb1e62bb44af6085b89/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 12 | 13 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const path = require('path') 3 | const express = require('express') 4 | const history = require('connect-history-api-fallback') 5 | 6 | const app = express() 7 | 8 | app.use(history()) 9 | 10 | app.use(express.static(path.join(__dirname, 'dist'))) 11 | const port = process.env.PORT || 5000 12 | app.listen(port) 13 | console.log('server started ' + port) 14 | -------------------------------------------------------------------------------- /src/entities/article/article.mock.ts: -------------------------------------------------------------------------------- 1 | import { IArticleData, IArticle } from './article.types' 2 | import { Article } from './article' 3 | 4 | export const mockArticlesData = (): IArticleData[] => [{ 5 | id: 1, 6 | title: 'title', 7 | content: 'content', 8 | short: 'short', 9 | createdAt: '2019-12-19T11:54:04 +05:00', 10 | isActive: false, 11 | picture: 'http://image.img', 12 | tags: ['z', 'x', 'y'], 13 | comments: [] 14 | }, { 15 | id: 2, 16 | title: 'title2', 17 | content: 'content2', 18 | short: 'short2', 19 | createdAt: '2019-12-07T11:54:04 +05:00', 20 | isActive: true, 21 | picture: 'http://image.img', 22 | tags: ['z', 'x', 'y'], 23 | comments: [] 24 | }] 25 | 26 | export const mockArticles = (data: IArticleData[] = mockArticlesData()): IArticle[] => data.map(item => new Article(item)) 27 | -------------------------------------------------------------------------------- /src/entities/article/article.spec.ts: -------------------------------------------------------------------------------- 1 | import { Article } from './article' 2 | import { mockCommentsData, Comment } from '../comment' 3 | import { mockArticlesData } from './article.mock' 4 | 5 | describe('>>> Article', () => { 6 | describe('>> constructor', () => { 7 | it('should instantiate id only if it was provided', () => { 8 | const entity1 = new Article({ 9 | ...mockArticlesData()[0], 10 | id: 1 11 | }) 12 | 13 | expect(entity1.id).toBe(1) 14 | 15 | const entity2 = new Article({ 16 | ...mockArticlesData()[0], 17 | id: undefined 18 | }) 19 | 20 | expect(entity2.id).toBeUndefined() 21 | }) 22 | 23 | it('should instantiate createdAt only if it was provided', () => { 24 | const entity1 = new Article({ 25 | ...mockArticlesData()[0], 26 | createdAt: '2019-12-19T11:54:04 +05:00' 27 | }) 28 | 29 | expect(entity1.createdAt).toBe('2019-12-19T11:54:04 +05:00') 30 | 31 | const entity2 = new Article({ 32 | ...mockArticlesData()[0], 33 | createdAt: undefined 34 | }) 35 | 36 | expect(entity2.createdAt).toBeUndefined() 37 | }) 38 | 39 | it('should instantiate Comments', () => { 40 | const entity1 = new Article({ 41 | ...mockArticlesData()[0], 42 | comments: mockCommentsData() 43 | }) 44 | 45 | expect(entity1.comments[0] instanceof Comment).toBeTruthy() 46 | }) 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /src/entities/article/article.ts: -------------------------------------------------------------------------------- 1 | import { IArticle, IArticleData } from './article.types' 2 | import { IComment, Comment } from '../comment' 3 | 4 | export class Article implements IArticle { 5 | readonly id?: number 6 | readonly title: string 7 | readonly content: string 8 | readonly short: string 9 | readonly createdAt?: Date | string 10 | readonly isActive: boolean 11 | readonly picture: string 12 | readonly tags: string[] 13 | readonly comments: IComment[] 14 | 15 | constructor (data: IArticleData) { 16 | if (data.id) { 17 | this.id = data.id 18 | } 19 | 20 | this.title = data.title 21 | this.content = data.content 22 | this.short = data.short 23 | this.createdAt = data.createdAt 24 | this.isActive = data.isActive 25 | this.picture = data.picture 26 | this.tags = data.tags 27 | 28 | if (data.createdAt) { 29 | this.createdAt = data.createdAt 30 | } 31 | 32 | this.comments = data.comments.map(comment => new Comment(comment)) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/entities/article/article.types.ts: -------------------------------------------------------------------------------- 1 | import { IComment, ICommentData } from '@/entities' 2 | 3 | export interface IArticleData { 4 | readonly id?: number 5 | readonly title: string 6 | readonly content: string 7 | readonly short: string 8 | readonly createdAt?: Date | string 9 | readonly isActive: boolean 10 | readonly picture: string 11 | readonly tags: string[] 12 | 13 | readonly comments: ICommentData[] 14 | } 15 | 16 | export interface IArticle extends IArticleData { 17 | readonly comments: IComment[] 18 | } 19 | -------------------------------------------------------------------------------- /src/entities/article/index.ts: -------------------------------------------------------------------------------- 1 | export * from './article' 2 | export * from './article.types' 3 | export * from './article.mock' 4 | -------------------------------------------------------------------------------- /src/entities/comment/comment.mock.ts: -------------------------------------------------------------------------------- 1 | import { IComment, ICommentData } from './comment.types' 2 | import { Comment } from './comment' 3 | 4 | export const mockCommentsData = (): ICommentData[] => [{ 5 | id: 1, 6 | title: 'title', 7 | content: 'content', 8 | author: 'author', 9 | createdAt: '2019-12-19T11:54:04 +05:00' 10 | }, { 11 | id: 2, 12 | title: 'title2', 13 | content: 'content2', 14 | author: 'author', 15 | createdAt: '2019-12-07T11:54:04 +05:00' 16 | }] 17 | 18 | export const mockComments = (data: ICommentData[] = mockCommentsData()): IComment[] => data.map(item => new Comment(item)) 19 | -------------------------------------------------------------------------------- /src/entities/comment/comment.spec.ts: -------------------------------------------------------------------------------- 1 | import { Comment } from './comment' 2 | import { mockCommentsData, mockComments } from './comment.mock' 3 | 4 | describe('>>> Comment', () => { 5 | it('should instantiate id only if it was provided', () => { 6 | const entity1 = new Comment({ 7 | ...mockCommentsData()[0], 8 | id: 1 9 | }) 10 | 11 | expect(entity1.id).toBe(1) 12 | 13 | const entity2 = new Comment({ 14 | ...mockCommentsData()[0], 15 | id: undefined 16 | }) 17 | 18 | expect(entity2.id).toBeUndefined() 19 | }) 20 | 21 | it('should instantiate createdAt only if it was provided', () => { 22 | const entity1 = new Comment({ 23 | ...mockCommentsData()[0], 24 | createdAt: '2019-12-19T11:54:04 +05:00' 25 | }) 26 | 27 | expect(entity1.createdAt).toBe('2019-12-19T11:54:04 +05:00') 28 | 29 | const entity2 = new Comment({ 30 | ...mockCommentsData()[0], 31 | createdAt: undefined 32 | }) 33 | 34 | expect(entity2.createdAt).toBeUndefined() 35 | }) 36 | 37 | describe('>> validate', () => { 38 | it('should fail validation if title is empty', () => { 39 | const entity = new Comment({ 40 | ...mockCommentsData()[0], 41 | title: '' 42 | }) 43 | 44 | expect(entity.validate()).toBeFalsy() 45 | }) 46 | 47 | it('should fail validation if title is too long', () => { 48 | const entity = new Comment({ 49 | ...mockCommentsData()[0], 50 | title: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit' 51 | }) 52 | 53 | expect(entity.validate()).toBeFalsy() 54 | }) 55 | 56 | it('should fail validation if author name is empty', () => { 57 | const entity = new Comment({ 58 | ...mockCommentsData()[0], 59 | author: '' 60 | }) 61 | 62 | expect(entity.validate()).toBeFalsy() 63 | }) 64 | 65 | it('should fail validation if author name is too long', () => { 66 | const entity = new Comment({ 67 | ...mockCommentsData()[0], 68 | author: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit' 69 | }) 70 | 71 | expect(entity.validate()).toBeFalsy() 72 | }) 73 | 74 | it('should fail validation if content is empty', () => { 75 | const entity = new Comment({ 76 | ...mockCommentsData()[0], 77 | content: '' 78 | }) 79 | 80 | expect(entity.validate()).toBeFalsy() 81 | }) 82 | 83 | it('should fail validation if content is too long', () => { 84 | const entity = new Comment({ 85 | ...mockCommentsData()[0], 86 | content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed vel ante eget eros molestie rutrum ut ut odio. Nullam quis facilisis augue. Suspendisse potenti. Maecenas lobortis, nisl eget tempor convallis, leo est egestas nisi, a feugiat justo ipsum eget dolor. Duis in condimentum nisl, dapibus posuere leo. Nulla interdum ligula et hendrerit semper. Integer id nulla laoreet, auctor dolor non, tempus arcu. Ut scelerisque dolor vel consequat bibendum. Fusce fermentum, sapien non tincidunt egestas, quam ex ultrices risus, non dictum purus enim imperdiet dui. Quisque eget pulvinar enim, varius bibendum turpis. Aliquam vulputate posuere leo non euismod. Curabitur pretium cursus elit sed blandit. ' 87 | }) 88 | 89 | expect(entity.validate()).toBeFalsy() 90 | }) 91 | 92 | it('should return true if entity is valid', () => { 93 | const entity = mockComments()[0] 94 | expect(entity.validate()).toBeTruthy() 95 | }) 96 | }) 97 | }) 98 | -------------------------------------------------------------------------------- /src/entities/comment/comment.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IComment, 3 | ICommentData, 4 | COMMENTS_MAX_TITLE_LENGTH, 5 | COMMENTS_MAX_AUTHOR_LENGTH, 6 | COMMENTS_MAX_CONTENT_LENGTH 7 | } from './comment.types' 8 | export class Comment implements IComment { 9 | readonly id?: number 10 | readonly title: string 11 | readonly content: string 12 | readonly author: string 13 | readonly createdAt?: Date | string 14 | constructor (data: ICommentData) { 15 | if (data.id) { 16 | this.id = data.id 17 | } 18 | this.title = data.title 19 | this.content = data.content 20 | this.author = data.author 21 | if (data.createdAt) { 22 | this.createdAt = data.createdAt 23 | } 24 | } 25 | 26 | validate (): boolean { 27 | if (!this.title || this.title.length > COMMENTS_MAX_TITLE_LENGTH) { 28 | return false 29 | } 30 | if (!this.author || this.author.length > COMMENTS_MAX_AUTHOR_LENGTH) { 31 | return false 32 | } 33 | if (!this.content || this.content.length > COMMENTS_MAX_CONTENT_LENGTH) { 34 | return false 35 | } 36 | return true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/entities/comment/comment.types.ts: -------------------------------------------------------------------------------- 1 | export interface ICommentData { 2 | readonly id?: number 3 | readonly title: string 4 | readonly content: string 5 | readonly author: string 6 | readonly createdAt?: Date | string 7 | } 8 | 9 | export interface IComment extends ICommentData { 10 | validate(): boolean 11 | } 12 | 13 | export const COMMENTS_MAX_TITLE_LENGTH = 10 14 | export const COMMENTS_MAX_AUTHOR_LENGTH = 10 15 | export const COMMENTS_MAX_CONTENT_LENGTH = 255 16 | -------------------------------------------------------------------------------- /src/entities/comment/index.ts: -------------------------------------------------------------------------------- 1 | export * from './comment' 2 | export * from './comment.types' 3 | export * from './comment.mock' 4 | -------------------------------------------------------------------------------- /src/entities/index.ts: -------------------------------------------------------------------------------- 1 | export * from './article' 2 | export * from './comment' 3 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { App, router, vuetify } from './ui' 3 | import { makeStore } from './store' 4 | import { prepareServices, prepareStorage } from './ui/plugins' 5 | 6 | Vue.config.productionTip = false 7 | 8 | const store = makeStore() 9 | 10 | prepareStorage(store) 11 | prepareServices(store) 12 | 13 | new Vue({ 14 | router, 15 | store, 16 | vuetify, 17 | render: (h): Vue.VNode => h(App) 18 | }).$mount('#app') 19 | -------------------------------------------------------------------------------- /src/services/articles/articles.mock.ts: -------------------------------------------------------------------------------- 1 | import { IArticlesServiceMock } from './articles.types' 2 | 3 | export const mockArticlesService = (): IArticlesServiceMock => ({ 4 | getAll: jest.fn(), 5 | getOneById: jest.fn(), 6 | createComment: jest.fn() 7 | }) 8 | -------------------------------------------------------------------------------- /src/services/articles/articles.spec.ts: -------------------------------------------------------------------------------- 1 | import { mockArticles, mockArticlesData, mockComments } from '@/entities' 2 | import { ArticlesService } from './articles' 3 | 4 | describe('>>> Articles Service', () => { 5 | const service = new ArticlesService(mockArticlesData()) 6 | 7 | describe('>> getMany', () => { 8 | it('should return all data', () => { 9 | expect(service.getAll()).toEqual(mockArticlesData()) 10 | }) 11 | }) 12 | 13 | describe('>> getOneById', () => { 14 | it('should return one article by provided id', () => { 15 | const article = mockArticles()[1] 16 | const id = article.id as number 17 | expect(service.getOneById(id)).toEqual(article) 18 | }) 19 | 20 | it('should return undefined if no article found', () => { 21 | expect(service.getOneById(111111)).toBeUndefined() 22 | }) 23 | }) 24 | 25 | describe('>> createComment', () => { 26 | const data = mockComments()[0] 27 | it('should add new comment to provided article and return it', () => { 28 | const article = mockArticles()[0] 29 | const id = article.id as number 30 | const commentsAmount = article.comments.length 31 | 32 | const newArticle = service.createComment(id, data) 33 | expect(newArticle.id).toEqual(article.id) 34 | expect(newArticle.comments.length).toBe(commentsAmount + 1) 35 | }) 36 | 37 | it('should throw an error if article with provided id doesn\'t exist', () => { 38 | expect(() => { service.createComment(11111, data) }).toThrow() 39 | }) 40 | 41 | it('should throw an error if comment is not valid', () => { 42 | const data = { 43 | ...mockComments()[0], 44 | title: '' 45 | } 46 | expect(() => { service.createComment(1, data) }).toThrow() 47 | }) 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /src/services/articles/articles.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IArticle, 3 | IArticleData, 4 | Article, 5 | ICommentData, 6 | Comment 7 | } from '@/entities' 8 | import { IArticlesService } from './articles.types' 9 | 10 | export class ArticlesService implements IArticlesService { 11 | constructor (private readonly data: IArticleData[]) { } 12 | 13 | getAll (): IArticleData[] { 14 | return this.data 15 | } 16 | 17 | getOneById (id: number): IArticle | undefined { 18 | const data = this.data.find(article => article.id === id) 19 | if (!data) { 20 | return 21 | } 22 | return new Article(data) 23 | } 24 | 25 | createComment (articleId: number, commentData: ICommentData): IArticle { 26 | const article = this.getOneById(articleId) 27 | if (!article) { 28 | throw new Error('Article doesn\'t exist in DB!') 29 | } 30 | 31 | const comment = new Comment(commentData) 32 | if (!comment.validate()) { 33 | throw new Error('Comment data is not valid') 34 | } 35 | 36 | article.comments.push(comment) 37 | 38 | return article 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/services/articles/articles.types.ts: -------------------------------------------------------------------------------- 1 | import { IArticle, ICommentData, IArticleData } from '@/entities' 2 | 3 | export interface IArticlesService { 4 | getAll (): IArticleData[] 5 | getOneById (id: number): IArticle | undefined 6 | createComment (id: number, data: ICommentData): IArticle 7 | } 8 | 9 | export interface IArticlesServiceMock { 10 | getAll: jest.Mock 11 | getOneById: jest.Mock 12 | createComment: jest.Mock 13 | } 14 | -------------------------------------------------------------------------------- /src/services/articles/index.ts: -------------------------------------------------------------------------------- 1 | export * from './articles' 2 | export * from './articles.types' 3 | export * from './articles.mock' 4 | -------------------------------------------------------------------------------- /src/services/data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "isActive": false, 5 | "title": "veniam laborum et tempor exercitation", 6 | "short": "Esse excepteur dolor exercitation in proident sunt. Irure minim labore aliqua culpa laborum. Laborum mollit irure quis est deserunt.", 7 | "content": "Esse excepteur dolor exercitation in proident sunt. Irure minim labore aliqua culpa laborum. Laborum mollit irure quis est deserunt. Aute deserunt voluptate nisi Lorem deserunt aute dolore eiusmod cillum sint cillum. Laborum est dolore deserunt consectetur Lorem pariatur aliqua id. Eu magna culpa enim dolore ea ullamco.\r\nLabore laborum cupidatat est Lorem incididunt sit. Non nisi est non sit. Enim dolor excepteur consequat tempor tempor voluptate Lorem mollit in adipisicing proident qui. Anim tempor aliquip qui magna exercitation. Proident ea anim labore aute.\r\nSunt reprehenderit occaecat nulla labore sit magna sint sint incididunt eiusmod deserunt elit et. Sint velit laboris sit eu amet laboris ad deserunt commodo. Ex ut sunt laboris minim elit veniam deserunt proident ullamco labore Lorem ullamco do.\r\nQuis eu cupidatat ut minim aliqua esse ex laborum labore ipsum. Nisi id amet reprehenderit tempor quis laboris quis esse aliqua ad elit labore amet ut. Sint voluptate occaecat non dolor.\r\nSunt officia sit ipsum esse cillum dolor quis esse. Est qui aute mollit nulla aliquip irure minim occaecat et. Mollit reprehenderit sunt id et cillum esse occaecat nulla ea aliquip fugiat consectetur in sit. Ea cillum consectetur mollit amet tempor.\r\n", 8 | "picture": "https://images.unsplash.com/photo-1581591546349-f29007542ed4?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1950&q=80", 9 | "createdAt": "2019-12-19T11:54:04 +05:00", 10 | "comments": [{ 11 | "id": 2, 12 | "author": "Wynn Davenport", 13 | "title": "anim mollit ad elit qui", 14 | "content": "Aliqua enim cupidatat exercitation ea sint non elit ex sint ea. Sunt nisi ut qui occaecat reprehenderit consectetur nulla ipsum incididunt. Adipisicing non qui consectetur qui eu eiusmod veniam officia exercitation aliqua dolore elit ad. Eu et et minim pariatur Lorem tempor anim eiusmod magna pariatur cupidatat. Minim ut consectetur duis dolore magna est laboris commodo anim. Velit Lorem minim cupidatat est exercitation aliqua. Ea dolore sit labore ut magna adipisicing officia.\r\nSit ullamco et ipsum veniam qui pariatur aliquip reprehenderit voluptate voluptate sint in laboris ex. Eu quis ex esse cillum officia labore labore enim excepteur. Cillum mollit laborum ex aliquip cupidatat culpa nisi exercitation excepteur consequat in velit.\r\nAliquip enim nulla ullamco in Lorem cupidatat dolor tempor ea do cupidatat elit ullamco. Dolor culpa cupidatat aute cillum fugiat sit exercitation reprehenderit culpa aute in dolor duis. Aute dolore culpa velit in eiusmod consequat sunt minim do esse laborum magna commodo consectetur. Nulla mollit esse irure esse laborum do cillum ipsum ipsum sint do nisi enim ea. Esse amet ullamco non Lorem officia laborum nostrud magna Lorem in eu aliqua excepteur Lorem.\r\nOccaecat do anim commodo nostrud amet ullamco nisi mollit deserunt ea. Anim ipsum culpa est ullamco enim id fugiat velit. Irure nisi consequat minim nulla minim irure aliqua in nisi tempor.\r\nOfficia aute sunt fugiat veniam ipsum et consequat irure cupidatat adipisicing sunt pariatur consequat. Tempor nostrud anim enim labore duis reprehenderit proident. Ex dolor ipsum minim ipsum dolore magna non dolor cillum dolore eu. Consequat anim aute labore non ea anim. Fugiat irure aute velit officia elit. Esse cupidatat amet commodo quis anim. Culpa commodo nostrud consequat anim consectetur.\r\n", 15 | "createdAt": "2018-09-21T05:10:02 +04:00" 16 | }, 17 | { 18 | "id": 3, 19 | "isActive": true, 20 | "author": "Maura Santiago", 21 | "title": "id do exercitation nostrud esse", 22 | "content": "Amet elit quis cillum ullamco velit. Qui ipsum officia est cupidatat ullamco. Aliquip aute aliqua fugiat mollit deserunt commodo do. Duis reprehenderit Lorem consequat Lorem consectetur enim aute cupidatat elit.\r\nAnim minim sit consectetur elit ea eiusmod officia ad deserunt. Dolore nulla labore amet minim quis. Tempor non dolore magna do qui ullamco cillum magna deserunt consequat. Sint voluptate dolor aliqua laborum consequat eu adipisicing. Proident laboris aliquip eiusmod ut labore Lorem. Laborum non duis commodo occaecat sunt aute irure. Ullamco elit commodo aute voluptate.\r\nEiusmod duis incididunt fugiat veniam magna fugiat amet ut elit tempor incididunt. Cupidatat aute Lorem quis incididunt. Adipisicing occaecat consequat laboris Lorem commodo nulla. Est in eiusmod consectetur laboris non. Eu incididunt pariatur do cupidatat officia ex tempor anim.\r\nMagna aliquip velit ullamco aute amet veniam nulla culpa laborum. Minim irure et fugiat non occaecat magna elit veniam. Tempor consectetur pariatur ea ullamco dolor incididunt cupidatat laboris exercitation.\r\nSunt velit esse magna labore dolor magna in eu nisi non esse. Occaecat aliqua irure magna magna minim. Eu esse duis tempor ipsum. Ad ex qui non deserunt aliqua reprehenderit est labore enim culpa ex consequat et. Est dolor tempor ut officia fugiat et laborum id incididunt.\r\n", 23 | "createdAt": "2017-06-11T10:12:04 +04:00" 24 | }], 25 | "tags": [ 26 | "mollit", 27 | "qui", 28 | "nulla", 29 | "non", 30 | "eu", 31 | "irure", 32 | "irure" 33 | ] 34 | }, 35 | { 36 | "id": 2, 37 | "isActive": false, 38 | "title": "et veniam ipsum ea consequat", 39 | "short": "Tempor tempor magna est proident qui sit elit pariatur proident. Veniam ut excepteur fugiat Lorem pariatur nisi do.", 40 | "content": "Tempor tempor magna est proident qui sit elit pariatur proident. Veniam ut excepteur fugiat Lorem pariatur nisi do. Enim adipisicing sint minim exercitation exercitation aliqua ullamco dolore nostrud. Sunt proident eiusmod Lorem esse cillum in dolore elit aliqua officia. Ipsum irure eu ullamco magna. Cillum Lorem pariatur ex dolor deserunt ex duis nulla voluptate. Aliqua est incididunt aute anim incididunt consectetur mollit mollit excepteur mollit eiusmod.\r\nId cillum sint est exercitation incididunt cupidatat. Aliquip occaecat culpa ipsum voluptate voluptate enim eiusmod commodo ut ea exercitation voluptate mollit. Ad mollit nostrud nostrud occaecat minim laborum ex pariatur voluptate laboris enim laborum ullamco esse. Ipsum proident fugiat voluptate voluptate qui esse sit id ex. Velit aliquip nostrud sint nisi in. Et velit non minim aliquip ea veniam proident esse et pariatur dolore elit enim minim. Commodo culpa ipsum commodo in pariatur reprehenderit qui.\r\nDolor mollit consectetur cillum proident ea eiusmod qui sint aliqua eu qui ex in veniam. Aliquip ex do enim elit occaecat consequat incididunt anim reprehenderit laboris. Laborum commodo mollit non occaecat irure minim in nulla. Amet incididunt sunt cupidatat exercitation cupidatat aliquip adipisicing aliquip sint. Sint duis sit quis fugiat magna enim minim do qui excepteur laborum. Commodo ullamco excepteur voluptate ullamco incididunt sit nostrud quis ex magna tempor pariatur. Reprehenderit sunt occaecat minim eu anim sit laboris magna fugiat Lorem ad qui voluptate ex.\r\nAd non irure laborum nisi nisi proident velit proident incididunt cupidatat reprehenderit incididunt cupidatat. Consequat irure mollit sint labore dolore excepteur adipisicing commodo mollit. Aliqua veniam non eiusmod culpa. Deserunt incididunt exercitation commodo quis tempor irure adipisicing est occaecat proident ea nisi. Sit consequat anim dolor laborum proident laboris ipsum eiusmod et velit incididunt. Labore est veniam excepteur et sunt ea.\r\nEa amet minim esse sit Lorem ut consectetur. In anim elit labore anim sunt aute quis enim cupidatat culpa laboris. Dolor eu voluptate aliquip cillum reprehenderit tempor aliqua commodo dolore.\r\n", 41 | "picture": "https://images.unsplash.com/photo-1581610186406-5f6e9f9edbc1?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1950&q=80", 42 | "createdAt": "2018-03-27T04:36:13 +04:00", 43 | "comments": [{ 44 | "id": 4, 45 | "author": "Rosemary Hall", 46 | "title": "aliqua proident veniam culpa duis", 47 | "content": "In nisi ex pariatur Lorem pariatur. Nisi Lorem commodo irure quis enim ullamco qui laboris adipisicing deserunt aliqua deserunt occaecat. Aute ad qui ea deserunt excepteur exercitation eiusmod non. Lorem eu officia ut et laboris ut elit mollit aliqua mollit ad. Quis aliqua voluptate dolore deserunt pariatur mollit ad irure sunt fugiat occaecat exercitation irure. Elit qui sunt exercitation Lorem laborum sit excepteur magna est. Ullamco sunt aliqua anim ipsum culpa et.\r\nLaboris consectetur exercitation aute nostrud. Cillum laboris eu mollit veniam occaecat. Ex amet ipsum pariatur nulla. Aliquip ex aliquip ea aliquip elit eu deserunt Lorem pariatur voluptate dolor et cillum. Eiusmod culpa reprehenderit amet do non officia et aliquip.\r\nOfficia mollit eiusmod in fugiat exercitation nostrud nisi commodo. Minim sit ipsum ipsum do nisi aliquip sunt velit elit eiusmod dolor et. Anim ea deserunt dolore incididunt excepteur pariatur dolore anim ex deserunt irure adipisicing sint laborum. Mollit sit tempor nisi tempor. Id consequat dolor id incididunt ut dolore elit.\r\nEnim cillum exercitation quis eu mollit sunt quis nostrud. Consequat sint velit consectetur ex ad nostrud voluptate quis qui. In anim dolore ex et mollit voluptate sunt Lorem. Aliqua minim ad nisi aliqua eu quis id enim.\r\nDo mollit amet ex ex mollit quis cupidatat. Commodo sunt ipsum sint consequat eu sit occaecat voluptate proident consequat amet ea. Minim velit ut officia magna laborum aliqua in voluptate ipsum. Duis nulla cillum ex nisi minim mollit.\r\n", 48 | "createdAt": "2019-03-31T02:13:40 +04:00" 49 | }, 50 | { 51 | "id": 5, 52 | "author": "Lowery Horn", 53 | "title": "incididunt duis Lorem mollit minim", 54 | "content": "Dolor eu aliquip excepteur sint irure incididunt. Tempor do mollit ut ut nostrud laborum ipsum. Laboris cillum irure adipisicing eu est qui ut enim dolor cupidatat qui dolor eu cupidatat.\r\nVoluptate culpa consectetur laborum ex anim pariatur est qui duis aute. Est occaecat fugiat proident eu aute eu. Duis magna consectetur ullamco aute et consectetur consectetur anim nisi. Sit esse culpa minim elit eiusmod pariatur quis non. Deserunt exercitation velit culpa fugiat id sit adipisicing. Ad aliqua consectetur sit sint enim adipisicing esse aliqua. Magna irure aute occaecat qui ad minim proident ut laborum magna eiusmod occaecat sunt tempor.\r\nNulla proident minim eu ullamco do elit sit voluptate incididunt aliquip pariatur adipisicing incididunt cupidatat. Fugiat qui exercitation Lorem est. Cillum irure commodo deserunt est culpa adipisicing laboris sit laboris anim adipisicing aute et. Id proident laboris laborum irure elit fugiat nisi aute.\r\nCommodo laboris sint proident nisi do ea quis cillum elit velit. Adipisicing mollit est sint esse Lorem ad est labore in est ut eu. Commodo occaecat consequat magna excepteur. Ipsum deserunt ut culpa eu cillum laborum adipisicing. Enim in pariatur ad magna qui sint aliqua. Cillum excepteur anim exercitation ullamco ad. Do non anim sunt et exercitation cillum amet Lorem dolor consectetur.\r\nEst ipsum proident exercitation nisi ut ex ad ullamco nulla nostrud Lorem. Commodo sint cupidatat eiusmod sit anim voluptate irure veniam sit anim laboris commodo anim excepteur. Excepteur non ex aliquip ex incididunt consequat aliqua et sunt excepteur fugiat excepteur et. Adipisicing elit fugiat sit cillum consequat do sint magna. Ipsum labore commodo anim ad minim esse ut.\r\n", 55 | "createdAt": "2015-04-25T07:09:50 +04:00" 56 | }], 57 | "tags": [ 58 | "consectetur", 59 | "aliqua", 60 | "est", 61 | "nisi", 62 | "ullamco", 63 | "ea", 64 | "eu" 65 | ] 66 | }, 67 | { 68 | "id": 3, 69 | "isActive": true, 70 | "title": "velit qui officia velit est", 71 | "short": "Enim laborum duis esse sit aute tempor. Ea id irure esse do sunt.", 72 | "content": "Enim laborum duis esse sit aute tempor. Ea id irure esse do sunt. Esse est proident fugiat voluptate id id. Dolore incididunt minim cillum tempor eiusmod consequat occaecat nisi mollit id sunt amet irure. Quis Lorem aliqua eiusmod elit exercitation cupidatat. Veniam elit elit do exercitation do officia est.\r\nIpsum aute eiusmod commodo et officia. Adipisicing velit officia velit id voluptate quis consectetur labore adipisicing adipisicing. Consectetur enim culpa aliqua ullamco do commodo cillum duis eu sint. Commodo anim quis do incididunt deserunt sit duis consectetur irure deserunt minim do aliqua. Ipsum nisi do voluptate adipisicing incididunt deserunt ex Lorem eu. Laboris sint tempor sit duis proident nisi reprehenderit exercitation dolor exercitation.\r\nCulpa commodo eu duis est anim fugiat ut reprehenderit. Ea nulla mollit exercitation ad reprehenderit magna ipsum ullamco anim officia ipsum et magna aliquip. Esse ea ad proident Lorem eiusmod enim deserunt. Cillum quis pariatur consequat dolore aute mollit reprehenderit.\r\nSint eiusmod sunt consequat est laboris aliquip aliquip dolor. Commodo eiusmod consectetur consequat aliqua ea. Esse fugiat est culpa irure dolor non nulla Lorem nisi. Proident magna laboris incididunt ipsum dolore minim et laboris labore cillum nostrud anim consectetur. Dolor et aliqua minim commodo non veniam laboris dolor adipisicing sunt. Minim irure laborum velit veniam pariatur ad nisi. Aliquip in nisi deserunt cupidatat et.\r\nEnim duis amet aliquip veniam minim irure elit deserunt. Duis sint magna labore sunt deserunt nostrud et incididunt tempor in culpa cillum id. Aliqua quis excepteur consectetur elit est duis aliqua. Deserunt eiusmod nisi magna quis. Deserunt laboris et enim adipisicing adipisicing do ex deserunt anim aliqua anim nisi. Cillum commodo ut deserunt proident occaecat in ipsum Lorem eu nisi culpa incididunt.\r\n", 73 | "picture": "https://images.unsplash.com/photo-1581568684505-c2a418399e00?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1000&q=80", 74 | "createdAt": "2014-11-11T07:29:55 +05:00", 75 | "comments": [{ 76 | "id": 6, 77 | "author": "Carrillo Ruiz", 78 | "title": "voluptate cupidatat eiusmod Lorem sunt", 79 | "content": "Veniam proident exercitation dolor minim consectetur sit. Elit commodo ad fugiat qui. Lorem veniam exercitation id excepteur commodo reprehenderit et id. Cillum non aliqua dolor proident dolor laboris deserunt excepteur ex nulla anim eiusmod. Commodo eiusmod mollit tempor Lorem consectetur est ipsum eiusmod excepteur Lorem nisi ullamco Lorem. Nostrud enim non aliqua fugiat culpa. Est adipisicing cupidatat consectetur enim.\r\nExercitation quis et deserunt esse Lorem minim ipsum nisi magna. Consequat excepteur nostrud Lorem do aliquip id. Cupidatat amet ad sint adipisicing incididunt dolor amet excepteur Lorem. Anim minim Lorem eu occaecat id tempor ullamco consectetur duis eiusmod ipsum ipsum.\r\nVelit aute aute ullamco nulla. Ipsum nulla nostrud officia consectetur ex proident sit. Duis reprehenderit fugiat occaecat velit qui aute. Irure amet laborum irure cupidatat aute voluptate ut ipsum adipisicing deserunt. Culpa minim consequat nisi anim nisi aliqua deserunt qui incididunt. Tempor consequat ut eu sunt aliquip nulla. Quis mollit qui laboris dolor esse aute nisi reprehenderit labore.\r\nExercitation aute nulla ullamco in qui cupidatat magna tempor laborum pariatur. Do tempor laboris ullamco qui pariatur duis aute nostrud quis excepteur veniam laborum fugiat nostrud. Amet consectetur non eiusmod dolor aliquip commodo ad eiusmod sit commodo voluptate ad. Sunt Lorem reprehenderit ad pariatur dolore id. Dolore labore tempor adipisicing Lorem ea laboris eu labore ut reprehenderit duis nostrud commodo. Eu dolore dolor officia voluptate ad proident consectetur adipisicing. Cillum occaecat officia officia id laboris qui est deserunt minim officia veniam sit.\r\nProident laboris qui qui esse incididunt deserunt qui anim. Cupidatat eiusmod do ullamco elit. Officia tempor aute non nostrud occaecat id laborum qui aute. Dolor nostrud voluptate velit veniam incididunt consequat ea sunt incididunt. Veniam incididunt quis ex pariatur.\r\n", 80 | "createdAt": "2014-03-02T03:20:50 +05:00" 81 | }, 82 | { 83 | "id": 7, 84 | "author": "Francis Crosby", 85 | "title": "esse elit laborum pariatur ut", 86 | "content": "Nostrud nulla elit est mollit. Ex dolore aute nostrud velit amet aute eiusmod consequat officia. Esse quis pariatur quis exercitation incididunt sunt aliquip amet mollit ea. Sint fugiat culpa laborum adipisicing eiusmod eiusmod.\r\nQuis enim anim sit sit ex fugiat enim cillum aliqua laboris enim. Laborum Lorem adipisicing occaecat in aliqua qui nulla. Consequat minim elit tempor laborum commodo id irure eu. Officia anim ea reprehenderit laboris duis Lorem adipisicing ullamco anim. Est incididunt consequat voluptate cillum deserunt laboris elit culpa tempor sunt. Fugiat sit nisi do sit ea ipsum veniam mollit dolor voluptate magna proident proident velit.\r\nAmet magna veniam do id aute consequat officia proident. Ipsum magna culpa adipisicing exercitation sit velit dolor culpa. Laborum aute et nulla sit elit ullamco officia tempor ex minim. Nulla nostrud incididunt anim elit minim exercitation. Veniam ex quis in excepteur ullamco minim nisi. Laboris eu ex veniam excepteur in anim. Occaecat consequat consectetur cupidatat consequat tempor.\r\nDo proident fugiat labore non sunt consectetur do veniam ut cupidatat. Lorem occaecat ipsum fugiat quis nisi quis. Culpa consequat occaecat Lorem magna nulla consequat nisi mollit voluptate nisi. Anim deserunt consectetur dolore commodo irure consequat. Id qui qui velit proident. Aute eiusmod pariatur do laboris dolore et excepteur id eu ipsum consequat excepteur irure fugiat.\r\nDolore nulla consectetur ea aliqua sit in elit. Est quis eiusmod ex id in qui. Dolore officia ipsum ex voluptate ullamco sit consectetur ipsum labore. Nostrud ex consequat laborum nisi excepteur non Lorem pariatur exercitation. Cupidatat occaecat irure proident deserunt.\r\n", 87 | "createdAt": "2018-12-08T08:16:32 +05:00" 88 | }, 89 | { 90 | "id": 8, 91 | "author": "Edwina Beasley", 92 | "title": "ipsum anim qui magna enim", 93 | "content": "Sunt Lorem nostrud anim ullamco dolore est pariatur non ad ullamco cupidatat. Esse incididunt occaecat anim ullamco esse ullamco nostrud consectetur enim laboris deserunt anim officia fugiat. Non cupidatat magna deserunt sunt est officia enim enim dolor eiusmod velit. Qui tempor aliqua consequat dolor dolore nisi qui eu qui est do culpa. Dolor irure aliqua quis est dolor culpa consectetur.\r\nQuis dolor excepteur occaecat do irure est voluptate laboris sunt pariatur officia. Et proident exercitation nulla ut aliquip deserunt mollit. Deserunt voluptate cillum eiusmod irure velit. Velit proident ex consequat do exercitation aliqua enim. Ad elit exercitation nostrud nostrud voluptate nulla proident sit labore mollit cillum voluptate nulla qui. Commodo sint reprehenderit pariatur pariatur ipsum ut ea enim esse.\r\nDuis qui ex ipsum laboris ipsum. Deserunt ipsum sunt anim ea. Lorem officia adipisicing consectetur commodo ipsum qui officia reprehenderit incididunt. Ad labore elit amet proident laborum ut sint nostrud ea Lorem ad id. Ea laboris consectetur elit reprehenderit elit sunt Lorem velit commodo Lorem. Occaecat fugiat occaecat sint laboris consectetur aliquip Lorem laboris non ex anim. Adipisicing consectetur mollit cillum duis eiusmod deserunt cillum cupidatat labore.\r\nNostrud aute do velit est reprehenderit laboris nostrud ullamco incididunt. Aliquip enim incididunt ex duis tempor non. Qui ad eu do mollit cupidatat dolore cillum fugiat. Anim commodo reprehenderit sint laborum commodo laboris deserunt reprehenderit eiusmod ea. Ipsum adipisicing veniam commodo esse cupidatat tempor duis incididunt. Officia sunt Lorem ea veniam Lorem reprehenderit dolor.\r\nQui laborum culpa laboris enim pariatur. Pariatur est in magna irure. Consectetur consequat cupidatat ex exercitation anim qui do. Esse ex laboris est veniam. Esse Lorem labore eu et sunt consequat dolore proident tempor qui. Laboris laboris aliquip reprehenderit elit veniam occaecat irure adipisicing sit officia laborum ullamco veniam. Minim mollit non et enim ipsum id minim non duis magna excepteur cupidatat pariatur.\r\n", 94 | "createdAt": "2014-12-13T12:48:02 +05:00" 95 | }], 96 | "tags": [ 97 | "laboris", 98 | "aliquip", 99 | "duis", 100 | "magna", 101 | "ex", 102 | "eu", 103 | "fugiat" 104 | ] 105 | }, 106 | { 107 | "id": 4, 108 | "isActive": true, 109 | "title": "et officia in cillum exercitation", 110 | "short": "Amet cupidatat laborum irure duis laborum. Nostrud esse incididunt mollit commodo laboris fugiat deserunt minim culpa nostrud nulla velit quis.", 111 | "content": "Amet cupidatat laborum irure duis laborum. Nostrud esse incididunt mollit commodo laboris fugiat deserunt minim culpa nostrud nulla velit quis. Commodo fugiat ea consectetur ipsum officia do reprehenderit eiusmod id laboris cupidatat. Tempor labore laboris ut cupidatat. Proident ullamco culpa in proident nulla esse qui excepteur ut reprehenderit nulla consectetur non.\r\nVoluptate nulla veniam nostrud magna ipsum dolor sit consectetur ullamco incididunt exercitation. Officia qui exercitation laborum occaecat exercitation cillum dolore cillum laborum fugiat eiusmod. In culpa labore ut in exercitation elit nisi et do pariatur. Ea id exercitation ipsum duis pariatur est culpa eiusmod anim ex sit mollit. Incididunt voluptate laboris irure aliqua. Ipsum culpa in cupidatat sunt elit do Lorem ad ipsum ad Lorem.\r\nAute elit tempor incididunt elit eu cupidatat. Lorem eiusmod ex aliqua deserunt sunt incididunt eu veniam ea ipsum. Consectetur dolore labore aute nostrud reprehenderit dolor esse duis culpa ex minim exercitation. Adipisicing deserunt do sint aliqua ipsum cillum esse fugiat enim non ipsum minim ea. Esse nulla ullamco dolor eu ullamco esse consectetur sunt aute labore tempor veniam anim amet. Dolor ex dolore veniam ullamco ipsum non.\r\nDuis ex cillum culpa voluptate mollit reprehenderit incididunt. Enim anim consectetur incididunt consectetur Lorem esse. Magna consequat exercitation ea culpa sunt velit sunt tempor laborum sit ipsum cillum duis. Laborum minim laborum in officia pariatur id excepteur minim labore cupidatat enim. Officia esse nisi cupidatat irure. Officia culpa nulla cupidatat tempor do irure enim nostrud. Excepteur enim laboris non sint ut culpa sint.\r\nAd pariatur adipisicing aliquip officia velit non nostrud. Ullamco fugiat velit tempor ipsum aliquip enim amet incididunt ex. Irure sint cillum sint pariatur ex sit in laboris commodo fugiat aute ex incididunt. Eiusmod cupidatat esse cupidatat ea dolor nulla et cupidatat consequat amet ea excepteur veniam labore.\r\n", 112 | "picture": "https://images.unsplash.com/photo-1581543092318-2b3d529d95ef?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1000&q=80", 113 | "createdAt": "2019-03-17T08:56:57 +04:00", 114 | "comments": [{ 115 | "id": 9, 116 | "author": "Juliana Holt", 117 | "title": "officia deserunt adipisicing amet culpa", 118 | "content": "Proident occaecat quis do ad sit eiusmod Lorem. Nostrud sunt veniam dolor ut dolore minim consectetur amet eiusmod nisi aliquip culpa ut Lorem. Deserunt est eiusmod mollit sunt eu.\r\nLorem duis nostrud deserunt voluptate est nostrud occaecat. Culpa minim officia duis est elit veniam dolore ex magna. Laboris esse sunt labore laboris elit est Lorem. Laborum consectetur esse duis aliquip et.\r\nVelit esse labore veniam irure ut sint cupidatat enim eiusmod eiusmod. Pariatur ut duis voluptate sint. Tempor fugiat duis dolor adipisicing ut deserunt voluptate aliquip dolor ipsum in. Aliquip ad ipsum pariatur quis. Amet incididunt deserunt ad eu deserunt eiusmod nostrud cupidatat elit culpa velit. Nulla mollit enim cillum tempor. Dolore pariatur sit aute ullamco labore mollit et incididunt.\r\nDolore officia amet eu voluptate anim esse dolor. Excepteur et amet cillum nisi eu incididunt voluptate ex cillum do fugiat cupidatat dolor. Mollit ipsum anim ullamco in in exercitation voluptate.\r\nLabore aliqua qui amet dolor magna quis. Id magna sint sit minim et anim aliqua. Qui commodo laborum aute incididunt laborum Lorem enim tempor adipisicing eiusmod reprehenderit magna sunt eiusmod. In amet sit labore dolor et velit est amet proident. Culpa nulla amet non eu.\r\n", 119 | "createdAt": "2016-02-24T04:37:40 +05:00" 120 | }], 121 | "tags": [ 122 | "dolore", 123 | "ullamco", 124 | "est", 125 | "eiusmod", 126 | "irure", 127 | "est", 128 | "esse" 129 | ] 130 | }, 131 | { 132 | "id": 5, 133 | "isActive": true, 134 | "title": "excepteur consequat pariatur", 135 | "short": "Sunt nulla anim mollit nulla exercitation cupidatat ex laborum Lorem. Ut nisi tempor non pariatur laboris velit est.", 136 | "content": "Sunt nulla anim mollit nulla exercitation cupidatat ex laborum Lorem. Ut nisi tempor non pariatur laboris velit est. Qui deserunt cupidatat aute sint exercitation consectetur aliquip anim. Adipisicing id cillum consectetur magna anim adipisicing aute. Quis velit ipsum cupidatat aliquip voluptate adipisicing minim.\r\nDuis nostrud aliquip ex cupidatat officia eu cillum enim in. Ea veniam ex id nulla voluptate dolore ad magna consequat cillum nulla exercitation. Duis velit sunt commodo laboris laborum esse laboris minim cupidatat sint. Exercitation et duis tempor occaecat ex esse et mollit. Est proident id ut pariatur dolore culpa enim irure. Commodo veniam consectetur ut ipsum consectetur velit adipisicing elit minim labore adipisicing est aliqua irure.\r\nOccaecat ullamco mollit est nostrud est excepteur occaecat pariatur ullamco et tempor mollit aliquip deserunt. Est veniam ut adipisicing tempor id eu magna sunt amet est non ad quis mollit. Commodo et irure minim nulla duis nisi mollit id reprehenderit est sit ipsum. Nisi commodo exercitation laborum ex nostrud proident ad veniam eiusmod cupidatat. In culpa ex enim voluptate dolor cillum eiusmod. Ut veniam tempor proident fugiat ipsum et minim exercitation incididunt.\r\nOccaecat laborum eu sint magna tempor cillum exercitation ipsum voluptate non adipisicing. Cillum ut ex non qui et eiusmod irure amet in eu ea labore eiusmod. Officia velit consequat pariatur dolore. Nostrud commodo ad ullamco incididunt consectetur ut consequat consectetur Lorem excepteur. Consectetur minim dolor eiusmod nulla aute qui tempor Lorem eu est sit proident nostrud cillum. Cupidatat deserunt aute occaecat qui est aute nulla nulla laboris in esse.\r\nEsse ipsum consectetur eiusmod mollit laboris magna eu aliquip non Lorem id sit ut. Non magna Lorem amet eu quis minim laborum. Laboris consectetur minim qui excepteur Lorem ipsum culpa dolor anim reprehenderit duis. Est fugiat sint nostrud magna quis. In adipisicing nulla anim consectetur deserunt excepteur proident dolore labore sint.\r\n", 137 | "picture": "https://images.unsplash.com/photo-1581562444313-98be7b621dc2?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1000&q=80", 138 | "createdAt": "2014-10-24T09:19:48 +04:00", 139 | "comments": [], 140 | "tags": [ 141 | "nulla", 142 | "tempor", 143 | "irure", 144 | "id", 145 | "eiusmod", 146 | "sunt", 147 | "pariatur" 148 | ] 149 | }, 150 | { 151 | "id": 6, 152 | "isActive": true, 153 | "title": "incididunt ex consequat incididunt deserunt", 154 | "short": "Labore non duis est nulla pariatur eiusmod Lorem id aliqua cillum ullamco ullamco. Aute qui quis ullamco in nulla sit nulla.", 155 | "content": "Labore non duis est nulla pariatur eiusmod Lorem id aliqua cillum ullamco ullamco. Aute qui quis ullamco in nulla sit nulla. Esse ullamco adipisicing sit nulla ea Lorem.\r\nAmet adipisicing aliqua nostrud esse culpa labore. Excepteur eiusmod anim labore officia do in mollit minim dolore veniam eu enim. Exercitation do aliquip anim amet sit aliquip cillum mollit commodo fugiat deserunt ea.\r\nIn dolore irure pariatur qui commodo magna veniam aliqua ad. Veniam laborum laboris do officia nisi consectetur sint quis officia. Labore culpa irure id minim do.\r\nTempor sint sunt irure aute qui do ex fugiat esse duis ea. Pariatur commodo do dolore occaecat occaecat ipsum dolor deserunt commodo eu velit sit proident. Veniam veniam consectetur occaecat ea veniam occaecat adipisicing mollit officia. Mollit cupidatat ad minim elit aute nostrud anim est nostrud dolore dolore eu. Voluptate excepteur minim aliqua in minim.\r\nNostrud consectetur excepteur Lorem qui. Eu pariatur qui culpa officia mollit incididunt magna dolore sint do magna officia. Laborum aute aliquip elit incididunt in. Voluptate quis laboris deserunt quis. Dolore velit do in aute dolor.\r\n", 156 | "picture": "https://images.unsplash.com/photo-1581540527720-89222d4fabcf?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=934&q=80", 157 | "createdAt": "2015-07-15T07:25:52 +04:00", 158 | "comments": [{ 159 | "id": 10, 160 | "author": "Verna Hardy", 161 | "title": "dolor pariatur voluptate ullamco in", 162 | "content": "Ullamco ut amet dolore aliquip exercitation labore magna. Ea ea ipsum non do pariatur aliquip qui amet amet fugiat fugiat qui cillum. Ea quis sint Lorem nisi sint nostrud cillum tempor id enim aliquip ut sint dolor. Laborum proident elit labore mollit cupidatat eiusmod. Tempor est ea occaecat cillum. Ad occaecat ipsum do mollit velit officia fugiat culpa incididunt esse dolore. Fugiat ipsum ad quis aliquip non laboris est pariatur magna occaecat esse ad culpa.\r\nVoluptate cillum deserunt non ut cillum consequat reprehenderit velit labore. Id aliquip cillum pariatur amet id nulla nostrud do incididunt fugiat non sunt duis eiusmod. Elit excepteur voluptate laboris aute incididunt.\r\nDo non nisi consequat dolore. In amet commodo eu eu est officia nisi tempor aute sunt cillum ullamco. Nulla est minim magna velit qui labore est deserunt sunt reprehenderit labore mollit enim sunt. Reprehenderit mollit non aliqua commodo deserunt commodo enim nisi deserunt quis.\r\nSint et elit et aliquip fugiat ad aliqua sit nulla cillum culpa veniam nulla non. Ipsum enim ipsum culpa Lorem. Elit aliqua occaecat nisi cupidatat. Commodo ullamco ipsum Lorem sint. Eiusmod proident tempor culpa dolor officia ea. Lorem voluptate voluptate proident pariatur.\r\nEu labore irure irure reprehenderit adipisicing id duis sunt anim ipsum commodo anim esse ad. Cupidatat dolore ullamco anim veniam qui nostrud culpa tempor sint laboris minim elit commodo duis. In voluptate dolor et sit reprehenderit. Dolore cillum nisi officia dolore magna consectetur nostrud nisi deserunt esse qui nulla occaecat.\r\n", 163 | "createdAt": "2018-05-31T12:30:18 +04:00" 164 | }, 165 | { 166 | "id": 11, 167 | "author": "Roth Valenzuela", 168 | "title": "consequat eiusmod pariatur esse ad", 169 | "content": "Amet non aliqua eiusmod culpa esse ipsum ex pariatur culpa nostrud sunt do deserunt. Pariatur laborum esse incididunt sit cillum ea fugiat tempor dolore sit. Aliquip ad Lorem ex ea nisi. Est dolore reprehenderit eu mollit excepteur quis ullamco. Sunt veniam consequat cupidatat id dolor ipsum Lorem labore excepteur sint.\r\nOccaecat velit ad in non labore quis sunt magna. Sit elit labore qui proident fugiat qui dolor eiusmod id. Lorem est laborum consequat eiusmod sint aute dolore irure enim elit. Minim dolore aute ut pariatur enim esse in sit. Eiusmod ullamco esse Lorem occaecat officia irure voluptate deserunt anim in exercitation.\r\nAliquip amet esse incididunt Lorem officia est esse fugiat quis. Excepteur esse id laboris irure pariatur in velit irure. Sint amet pariatur nulla do reprehenderit tempor quis adipisicing consequat magna esse proident. Dolore irure duis ea ipsum dolor esse dolore esse et sit laboris reprehenderit aliquip. Consectetur velit et veniam sunt exercitation cillum ipsum ex ea. Sunt pariatur dolor commodo eu adipisicing ea sint occaecat commodo laborum qui velit.\r\nConsectetur deserunt esse sint aliquip velit eu proident tempor minim dolor culpa exercitation eu. Velit ad nulla laborum aliqua magna ea deserunt minim laboris ut irure veniam. Do enim do in do ipsum eu consectetur tempor. Minim enim excepteur in eiusmod do est tempor est consequat duis tempor non incididunt adipisicing. Voluptate mollit sint irure magna sint cillum enim dolore aliqua Lorem magna minim. Pariatur aute sit do ea et sit.\r\nConsectetur laboris fugiat nisi aliquip enim duis deserunt. Duis excepteur enim aliqua reprehenderit sit. Consectetur commodo velit nostrud quis est ipsum velit fugiat culpa cupidatat. Occaecat consequat ex sunt sit officia culpa aliqua adipisicing aute anim nostrud. Sint exercitation laborum occaecat mollit deserunt enim eiusmod nulla. Lorem duis ullamco excepteur aliquip reprehenderit officia veniam culpa. Consectetur irure non deserunt minim occaecat cupidatat sunt.\r\n", 170 | "createdAt": "2019-01-27T08:00:22 +05:00" 171 | }, 172 | { 173 | "id": 12, 174 | "author": "Pennington Nelson", 175 | "title": "amet magna mollit occaecat velit", 176 | "content": "Cillum sit nulla ullamco dolore veniam proident sit voluptate laboris eu. Incididunt ipsum officia amet qui sint sunt in proident fugiat qui sit labore cupidatat Lorem. Ut voluptate adipisicing sit ex enim mollit consequat commodo exercitation ea.\r\nExercitation aliquip sint ad culpa et irure tempor laboris dolor do deserunt voluptate enim. Lorem nostrud dolore occaecat magna in id ad do. Fugiat sunt laborum est qui tempor ipsum nulla nostrud ullamco aliquip. Exercitation commodo aliquip esse dolore enim ea Lorem laborum officia veniam laboris pariatur magna esse. Do pariatur nostrud id est excepteur irure excepteur consectetur nostrud nostrud sunt adipisicing.\r\nEiusmod quis sint laboris qui nisi elit esse consectetur anim quis. Nulla officia cupidatat in irure nisi labore proident laboris cillum officia officia voluptate laboris. Mollit enim voluptate pariatur sit exercitation dolore ex do. Quis incididunt id sit minim est occaecat qui ex. Velit velit proident sint enim dolore laboris ad laboris et ut exercitation sunt reprehenderit. Elit ad voluptate ex voluptate sunt reprehenderit tempor. Deserunt esse labore nisi magna labore incididunt consectetur.\r\nVoluptate qui irure exercitation tempor esse. Magna amet exercitation excepteur labore laboris sunt laborum qui incididunt velit elit. Pariatur amet exercitation eu tempor occaecat laboris adipisicing fugiat nulla.\r\nAute aliquip esse tempor est aliquip magna dolor enim. Cillum commodo cupidatat proident consequat do do. Proident anim irure ad proident deserunt culpa sint aute nulla aliqua sit consectetur non esse. Adipisicing sit nulla adipisicing ad proident tempor velit labore aute. Qui pariatur veniam voluptate et labore amet mollit consequat deserunt aliqua incididunt irure. In minim occaecat consectetur id adipisicing incididunt.\r\n", 177 | "createdAt": "2016-05-19T01:10:16 +04:00" 178 | }], 179 | "tags": [ 180 | "labore", 181 | "voluptate", 182 | "exercitation", 183 | "duis", 184 | "magna", 185 | "deserunt", 186 | "velit" 187 | ] 188 | } 189 | ] -------------------------------------------------------------------------------- /src/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './articles' 2 | export * from './provider' 3 | export * from './provider.types' 4 | export * from './provider.mock' 5 | -------------------------------------------------------------------------------- /src/services/provider.mock.ts: -------------------------------------------------------------------------------- 1 | import { IProviderMock } from './provider.types' 2 | import { mockArticlesService } from './articles' 3 | 4 | export const mockProvider = (): IProviderMock => ({ 5 | articles: mockArticlesService() 6 | }) 7 | -------------------------------------------------------------------------------- /src/services/provider.spec.ts: -------------------------------------------------------------------------------- 1 | import { provider } from './provider' 2 | import { ArticlesService } from './articles' 3 | 4 | jest.mock('./articles') 5 | describe('>> Provider', () => { 6 | it('should instantiate Articles Service', () => { 7 | provider() 8 | expect(ArticlesService.prototype.constructor).toBeCalled() 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /src/services/provider.ts: -------------------------------------------------------------------------------- 1 | import { ArticlesService } from './articles' 2 | import data from './data.json' 3 | import { IProvider } from './provider.types' 4 | 5 | export const provider = (): IProvider => ({ 6 | articles: new ArticlesService(data) 7 | }) 8 | -------------------------------------------------------------------------------- /src/services/provider.types.ts: -------------------------------------------------------------------------------- 1 | import { IArticlesService, IArticlesServiceMock } from './articles' 2 | 3 | export interface IProvider { 4 | articles: IArticlesService 5 | } 6 | 7 | export interface IProviderMock { 8 | articles: IArticlesServiceMock 9 | } 10 | -------------------------------------------------------------------------------- /src/store/actions/actions.mock.ts: -------------------------------------------------------------------------------- 1 | import { ICreateCommentActionPayload, IActionsMock } from './actions.types' 2 | import { ActionContext } from 'vuex' 3 | import { IState, IRootState } from '../store.types' 4 | import { mockRootState, mockState } from '../store.mock' 5 | import { mockGetters } from '../getters' 6 | import { mockComments } from '@/entities' 7 | 8 | export const mockActionsContext = (): ActionContext => ({ 9 | commit: jest.fn(), 10 | dispatch: jest.fn(), 11 | state: mockState(), 12 | getters: mockGetters(), 13 | rootState: mockRootState(), 14 | rootGetters: {} 15 | }) 16 | 17 | export const mockCreateCommentActionPayload = (articleId = 0, comment = mockComments()[0]): ICreateCommentActionPayload => ({ 18 | articleId, 19 | comment 20 | }) 21 | 22 | export const mockActions = (): IActionsMock => ({ 23 | fetchArticles: jest.fn(), 24 | createComment: jest.fn() 25 | }) 26 | -------------------------------------------------------------------------------- /src/store/actions/actions.spec.ts: -------------------------------------------------------------------------------- 1 | import { mockActionsContext, mockCreateCommentActionPayload } from './actions.mock' 2 | import { mockComments, mockArticlesData, Article } from '@/entities' 3 | import { actions } from './actions' 4 | import { mockStore } from '../store.mock' 5 | 6 | describe('>>> Actions', () => { 7 | describe('>> fetchArticles', () => { 8 | const store = mockStore() 9 | const context = mockActionsContext() 10 | const articles = mockArticlesData() 11 | 12 | it('should fetch all data and then commit articles mutation', () => { 13 | store.$services.articles.getAll.mockReturnValueOnce(articles) 14 | 15 | actions.fetchArticles.bind(store)(context) 16 | 17 | expect(store.$services.articles.getAll).toBeCalled() 18 | expect(store.$storage.mutations.fetchArticles).toBeCalledWith(articles) 19 | }) 20 | 21 | it('should call service to create new comment and then creation mutation', () => { 22 | const payload = mockCreateCommentActionPayload() 23 | const article = new Article(articles[0]) 24 | const comment = mockComments()[0] 25 | article.comments.push(comment) 26 | 27 | store.$services.articles.createComment.mockReturnValueOnce(article) 28 | 29 | actions.createComment.bind(store)(context, payload) 30 | 31 | expect(store.$services.articles.createComment).toBeCalledWith(payload.articleId, payload.comment) 32 | expect(store.$storage.mutations.createComment).toBeCalledWith({ article, comment }) 33 | }) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /src/store/actions/actions.ts: -------------------------------------------------------------------------------- 1 | import { Context, ICreateCommentActionPayload, IActions } from './actions.types' 2 | 3 | export const actions: IActions = { 4 | fetchArticles (): void { 5 | this.$storage.mutations.fetchArticles(this.$services.articles.getAll()) 6 | }, 7 | 8 | createComment (context: Context, payload: ICreateCommentActionPayload): void { 9 | const article = this.$services.articles.createComment(payload.articleId, payload.comment) 10 | 11 | this.$storage.mutations.createComment({ article, comment: payload.comment }) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/store/actions/actions.types.ts: -------------------------------------------------------------------------------- 1 | import { ICommentData } from '@/entities' 2 | import { ActionContext, ActionTree, Store } from 'vuex' 3 | import { IState, IRootState } from '../store.types' 4 | 5 | export type Context = ActionContext 6 | 7 | export interface ICreateCommentActionPayload { 8 | articleId: number 9 | comment: ICommentData 10 | } 11 | 12 | export interface IActions extends ActionTree { 13 | fetchArticles (this: Store, context: Context): void 14 | createComment (this: Store, context: Context, payload: ICreateCommentActionPayload): void 15 | } 16 | 17 | export interface IActionsMock extends IActions { 18 | fetchArticles: jest.Mock 19 | createComment: jest.Mock 20 | } 21 | -------------------------------------------------------------------------------- /src/store/actions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './actions' 2 | export * from './actions.types' 3 | export * from './actions.mock' 4 | -------------------------------------------------------------------------------- /src/store/getters/getters.mock.ts: -------------------------------------------------------------------------------- 1 | import { IGettersMock } from './getters.types' 2 | 3 | export const mockGetters = (): IGettersMock => ({ 4 | getAllArticles: jest.fn(), 5 | getOneArticlesById: jest.fn() 6 | }) 7 | -------------------------------------------------------------------------------- /src/store/getters/getters.spec.ts: -------------------------------------------------------------------------------- 1 | import { getters } from './getters' 2 | import { mockState, mockStore } from '../store.mock' 3 | 4 | describe('>>> Getters', () => { 5 | const state = mockState() 6 | const store = mockStore() 7 | 8 | describe('>> getAllArticles', () => { 9 | it('should return all articles from the store', () => { 10 | expect(getters.getAllArticles.bind(store)(state)).toStrictEqual(state.articles) 11 | }) 12 | }) 13 | 14 | describe('>> getOneArticlesById', () => { 15 | it('should return one article by provided id from the store', () => { 16 | const id = state.articles[0].id as number 17 | expect(getters.getOneArticlesById.bind(store)(state)(id)).toStrictEqual(state.articles[0]) 18 | }) 19 | 20 | it('should return undefined if store has no such article', () => { 21 | expect(getters.getOneArticlesById.bind(store)(state)(1111)).toBeUndefined() 22 | }) 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /src/store/getters/getters.ts: -------------------------------------------------------------------------------- 1 | import { IArticle, Article } from '@/entities' 2 | import { IState } from '../store.types' 3 | import { IGetters } from './getters.types' 4 | 5 | export const getters: IGetters = { 6 | getAllArticles (state: IState): IArticle[] { 7 | return state.articles.map(data => new Article(data)) 8 | }, 9 | 10 | getOneArticlesById: (state: IState) => (id: number): IArticle | undefined => { 11 | const data = state.articles.find(article => article.id === id) 12 | if (!data) { 13 | return 14 | } 15 | 16 | return new Article(data) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/store/getters/getters.types.ts: -------------------------------------------------------------------------------- 1 | import { IState, IRootState } from '../store.types' 2 | import { IArticle } from '@/entities' 3 | import { GetterTree, Store } from 'vuex' 4 | 5 | export interface IGetters extends GetterTree { 6 | getAllArticles (this: Store, state: IState): IArticle[] 7 | getOneArticlesById (this: Store, state: IState): (id: number) => IArticle | undefined 8 | } 9 | 10 | export interface IGettersMock { 11 | getAllArticles: jest.Mock 12 | getOneArticlesById: jest.Mock<(id: number) => IArticle | undefined> 13 | } 14 | -------------------------------------------------------------------------------- /src/store/getters/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getters' 2 | export * from './getters.types' 3 | export * from './getters.mock' 4 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | export * from './store' 2 | export * from './store.types' 3 | export * from './actions' 4 | export * from './mutations' 5 | export * from './getters' 6 | export * from './storage' 7 | -------------------------------------------------------------------------------- /src/store/mutations/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mutations' 2 | export * from './mutations.types' 3 | export * from './mutations.mock' 4 | -------------------------------------------------------------------------------- /src/store/mutations/mutations.mock.ts: -------------------------------------------------------------------------------- 1 | import { IMutationsMock, IMutationsCreateCommentMutationPayload } from './mutations.types' 2 | import { mockCommentsData, mockArticles } from '@/entities' 3 | 4 | export const mockMutations = (): IMutationsMock => ({ 5 | fetchArticles: jest.fn(), 6 | createComment: jest.fn() 7 | }) 8 | 9 | export const mockCreateCommentMutationPayload = (article = mockArticles()[0], comment = mockCommentsData()[0]): IMutationsCreateCommentMutationPayload => ({ 10 | article, 11 | comment 12 | }) 13 | -------------------------------------------------------------------------------- /src/store/mutations/mutations.spec.ts: -------------------------------------------------------------------------------- 1 | import { mutations } from './mutations' 2 | import { mockState, mockStore } from '../store.mock' 3 | import { mockArticles, mockArticlesData } from '@/entities' 4 | import { mockCreateCommentMutationPayload } from './mutations.mock' 5 | 6 | describe('>>> Mutations', () => { 7 | describe('>> fetchArticles', () => { 8 | it('should add all provided articles tot the store', () => { 9 | const state = mockState() 10 | const payload = mockArticlesData() 11 | const expected = { 12 | ...state, 13 | articles: [ 14 | ...state.articles, 15 | ...payload 16 | ] 17 | } 18 | 19 | mutations.fetchArticles.bind(mockStore())(state, payload) 20 | 21 | expect(state).toEqual(expected) 22 | }) 23 | }) 24 | 25 | describe('>> createComment', () => { 26 | const state = mockState() 27 | const payload = mockCreateCommentMutationPayload() 28 | 29 | it('should create new article if it doesn\'t exist in the store yet', () => { 30 | state.articles = [] 31 | 32 | mutations.createComment.bind(mockStore())(state, payload) 33 | 34 | expect(state.articles).toEqual([payload.article]) 35 | }) 36 | 37 | it('should create add new comment to the article in the store ', () => { 38 | const article = mockArticles()[0] 39 | 40 | state.articles = [article] 41 | 42 | const expected = { 43 | ...article, 44 | comments: [payload.comment] 45 | } 46 | 47 | mutations.createComment.bind(mockStore())(state, payload) 48 | 49 | expect(state.articles[0]).toEqual(expected) 50 | }) 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /src/store/mutations/mutations.ts: -------------------------------------------------------------------------------- 1 | import { IMutations, IMutationsCreateCommentMutationPayload } from './mutations.types' 2 | import { IArticleData } from '@/entities' 3 | import { IState } from '../store.types' 4 | 5 | export const mutations: IMutations = { 6 | fetchArticles (state: IState, payload: IArticleData[]): void { 7 | payload.map(article => state.articles.push(article)) 8 | }, 9 | 10 | createComment (state: IState, payload: IMutationsCreateCommentMutationPayload): void { 11 | const article = state.articles.find(article => article.id === payload.article.id) 12 | if (!article) { 13 | state.articles.push(payload.article) 14 | return 15 | } 16 | 17 | article.comments.push(payload.comment) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/store/mutations/mutations.types.ts: -------------------------------------------------------------------------------- 1 | import { IArticle, ICommentData, IArticleData } from '@/entities' 2 | import { IState } from '../store.types' 3 | import { MutationTree, Store } from 'vuex' 4 | 5 | export interface IMutations extends MutationTree { 6 | fetchArticles (this: Store, state: IState, payload: IArticleData[]): void 7 | createComment (this: Store, state: IState, payload: IMutationsCreateCommentMutationPayload): void 8 | } 9 | 10 | export interface IMutationsMock extends IMutations { 11 | fetchArticles: jest.Mock 12 | createComment: jest.Mock 13 | } 14 | 15 | export interface IMutationsCreateCommentMutationPayload { 16 | article: IArticle 17 | comment: ICommentData 18 | } 19 | -------------------------------------------------------------------------------- /src/store/storage/index.ts: -------------------------------------------------------------------------------- 1 | export * from './storage' 2 | export * from './storage.types' 3 | export * from './storage.mock' 4 | -------------------------------------------------------------------------------- /src/store/storage/storage.mock.ts: -------------------------------------------------------------------------------- 1 | import { IStorageMock } from './storage.types' 2 | 3 | export const mockStorage = (): IStorageMock => ({ 4 | getters: { 5 | getAllArticles: jest.fn(), 6 | getOneArticlesById: jest.fn() 7 | }, 8 | actions: { 9 | fetchArticles: jest.fn(), 10 | createComment: jest.fn() 11 | }, 12 | mutations: { 13 | fetchArticles: jest.fn(), 14 | createComment: jest.fn() 15 | } 16 | }) 17 | -------------------------------------------------------------------------------- /src/store/storage/storage.spec.ts: -------------------------------------------------------------------------------- 1 | import { makeStorage } from './storage' 2 | import { mockStore } from '../store.mock' 3 | import { mockArticles } from '@/entities' 4 | import { mockCreateCommentActionPayload } from '../actions/actions.mock' 5 | import { mockCreateCommentMutationPayload } from '../mutations/mutations.mock' 6 | 7 | describe('>>> Storage', () => { 8 | const store = mockStore() 9 | const storage = makeStorage(store) 10 | 11 | describe('>> Getters', () => { 12 | it('should proxy getAllArticles', () => { 13 | expect(storage.getters.getAllArticles()).toBe(store.getters.getAllArticles) 14 | }) 15 | 16 | it('should proxy getOneArticlesById', () => { 17 | const id = 111 18 | const result = storage.getters.getOneArticlesById(id) 19 | expect(store.getters.getOneArticlesById).toBeCalledWith(id) 20 | expect(result).toBe(store.getters.getOneArticlesById(id)) 21 | }) 22 | }) 23 | 24 | describe('>> Actions', () => { 25 | it('should proxy fetchArticles', () => { 26 | storage.actions.fetchArticles() 27 | expect(store.dispatch).toBeCalledWith('fetchArticles') 28 | }) 29 | 30 | it('should proxy createComment', () => { 31 | const payload = mockCreateCommentActionPayload() 32 | storage.actions.createComment(payload) 33 | expect(store.dispatch).toBeCalledWith('createComment', payload) 34 | }) 35 | }) 36 | 37 | describe('>> Mutations', () => { 38 | it('should proxy fetchArticles', () => { 39 | const payload = mockArticles() 40 | storage.mutations.fetchArticles(payload) 41 | expect(store.commit).toBeCalledWith('fetchArticles', payload) 42 | }) 43 | 44 | it('should proxy createComment', () => { 45 | const payload = mockCreateCommentMutationPayload() 46 | storage.mutations.createComment(payload) 47 | expect(store.commit).toBeCalledWith('createComment', payload) 48 | }) 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /src/store/storage/storage.ts: -------------------------------------------------------------------------------- 1 | import { IStorage } from './storage.types' 2 | import { ICreateCommentActionPayload } from '../actions' 3 | import { IStore } from '../store.types' 4 | import { IArticle, IArticleData } from '@/entities' 5 | import { IMutationsCreateCommentMutationPayload } from '../mutations' 6 | 7 | export const makeStorage = (store: IStore): IStorage => ({ 8 | getters: { 9 | getAllArticles (): IArticle[] { 10 | return store.getters.getAllArticles 11 | }, 12 | 13 | getOneArticlesById (id: number): IArticle | undefined { 14 | return store.getters.getOneArticlesById(id) 15 | } 16 | }, 17 | 18 | actions: { 19 | fetchArticles (): void { 20 | store.dispatch('fetchArticles') 21 | }, 22 | createComment (payload: ICreateCommentActionPayload): void { 23 | store.dispatch('createComment', payload) 24 | } 25 | }, 26 | 27 | mutations: { 28 | fetchArticles (payload: IArticleData[]): void { 29 | store.commit('fetchArticles', payload) 30 | }, 31 | createComment (payload: IMutationsCreateCommentMutationPayload): void { 32 | store.commit('createComment', payload) 33 | } 34 | } 35 | }) 36 | -------------------------------------------------------------------------------- /src/store/storage/storage.types.ts: -------------------------------------------------------------------------------- 1 | import { ICreateCommentActionPayload } from '../actions' 2 | import { IArticle, IArticleData } from '@/entities' 3 | import { IMutationsCreateCommentMutationPayload } from '../mutations' 4 | 5 | export interface IStorage { 6 | getters: { 7 | getAllArticles (): IArticle[] 8 | getOneArticlesById (id: number): IArticle | undefined 9 | } 10 | 11 | actions: { 12 | fetchArticles (): void 13 | createComment (payload: ICreateCommentActionPayload): void 14 | } 15 | 16 | mutations: { 17 | fetchArticles (payload: IArticleData[]): void 18 | createComment (payload: IMutationsCreateCommentMutationPayload): void 19 | } 20 | } 21 | 22 | export interface IStorageMock extends IStorage { 23 | getters: { 24 | getAllArticles: jest.Mock 25 | getOneArticlesById: jest.Mock 26 | } 27 | 28 | actions: { 29 | fetchArticles: jest.Mock 30 | createComment: jest.Mock 31 | } 32 | 33 | mutations: { 34 | fetchArticles: jest.Mock 35 | createComment: jest.Mock 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/store/store.mock.ts: -------------------------------------------------------------------------------- 1 | import { IState, IRootState, IStoreMock } from './store.types' 2 | import { mockArticles } from '@/entities' 3 | import { mockActions } from './actions' 4 | import { mockMutations } from './mutations' 5 | import { mockGetters } from './getters' 6 | import { mockProvider } from '@/services' 7 | import { mockStorage } from './storage' 8 | 9 | export const mockRootState = (): IRootState => ({ 10 | version: 0 11 | }) 12 | 13 | export const mockState = (): IState => ({ 14 | articles: mockArticles(), 15 | version: 0 16 | }) 17 | 18 | export const mockStore = (): IStoreMock => ({ 19 | $storage: mockStorage(), 20 | $services: mockProvider(), 21 | state: mockState(), 22 | actions: mockActions(), 23 | mutations: mockMutations(), 24 | getters: mockGetters(), 25 | replaceState: jest.fn(), 26 | dispatch: jest.fn(), 27 | commit: jest.fn(), 28 | subscribe: jest.fn(), 29 | subscribeAction: jest.fn(), 30 | watch: jest.fn(), 31 | registerModule: jest.fn(), 32 | unregisterModule: jest.fn(), 33 | hotUpdate: jest.fn() 34 | }) 35 | -------------------------------------------------------------------------------- /src/store/store.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex, { Store } from 'vuex' 3 | import { actions } from './actions' 4 | import { mutations } from './mutations' 5 | import { getters } from './getters' 6 | import { IState } from './store.types' 7 | 8 | Vue.use(Vuex) 9 | 10 | export const makeStore = (): Store => new Vuex.Store({ 11 | state: { 12 | articles: [], 13 | version: 1 14 | }, 15 | actions, 16 | mutations, 17 | getters 18 | }) 19 | -------------------------------------------------------------------------------- /src/store/store.types.ts: -------------------------------------------------------------------------------- 1 | import { IArticleData } from '@/entities' 2 | import { Store } from 'vuex' 3 | import { IActionsMock } from './actions' 4 | import { IMutationsMock } from './mutations' 5 | import { IGettersMock } from './getters' 6 | import { IProviderMock } from '@/services' 7 | import { IStorageMock } from './storage' 8 | 9 | export interface IRootState { 10 | version: number 11 | } 12 | 13 | export interface IState extends IRootState { 14 | articles: IArticleData[] 15 | } 16 | 17 | export type IStore = Store 18 | 19 | export interface IStoreMock extends IStore { 20 | $storage: IStorageMock 21 | $services: IProviderMock 22 | state: IState 23 | actions: IActionsMock 24 | mutations: IMutationsMock 25 | getters: IGettersMock 26 | } 27 | -------------------------------------------------------------------------------- /src/ui/app.tsx: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Component from 'vue-class-component' 3 | 4 | @Component({ 5 | render (): Vue.VNode { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | ) 14 | } 15 | }) 16 | export class App extends Vue {} 17 | -------------------------------------------------------------------------------- /src/ui/components/article/article.spec.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuetify from 'vuetify' 3 | import { ArticleComponent } from './article' 4 | import { mount, createLocalVue, Wrapper } from '@vue/test-utils' 5 | import VueRouter from 'vue-router' 6 | import { mockArticles } from '@/entities' 7 | 8 | Vue.use(Vuetify) 9 | Vue.use(VueRouter) 10 | 11 | const localVue = createLocalVue() 12 | const article = mockArticles()[0] 13 | 14 | describe('>>> Article Component', () => { 15 | let wrapper: Wrapper 16 | 17 | beforeEach(() => { 18 | wrapper = mount(ArticleComponent, { 19 | localVue, 20 | vuetify: new Vuetify(), 21 | router: new VueRouter(), 22 | propsData: { article } 23 | }) 24 | }) 25 | 26 | it('should render article', () => { 27 | expect(wrapper.find('.v-card__title').text()).toBe(article.title) 28 | expect(wrapper.find('.v-card__text').text()).toBe(article.short) 29 | expect(wrapper.find('.v-btn').attributes('href')).toBe(`#/articles/${article.id}`) 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /src/ui/components/article/article.tsx: -------------------------------------------------------------------------------- 1 | import Component from 'vue-class-component' 2 | import { VueComponent } from '@/ui/vue-ts-component' 3 | import { IArticleProps } from './article.types' 4 | 5 | @Component({ 6 | props: { 7 | article: { type: Object, required: true } 8 | }, 9 | render (this: ArticleComponent): Vue.VNode { 10 | const article = this.$props.article 11 | return ( 12 | 13 | 14 | 15 | { article.title } 16 | 17 | 18 |

{ article.short }

19 |
20 | 21 | 22 | Read more 23 | 24 | 25 |
26 |
27 | ) 28 | } 29 | }) 30 | export class ArticleComponent extends VueComponent { 31 | getUrl (id?: number): string { 32 | return `/articles/${id}` 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/ui/components/article/article.types.ts: -------------------------------------------------------------------------------- 1 | import { IArticle } from '@/entities' 2 | 3 | export interface IArticleProps { 4 | article: IArticle 5 | } 6 | -------------------------------------------------------------------------------- /src/ui/components/article/index.ts: -------------------------------------------------------------------------------- 1 | export * from './article' 2 | export * from './article.types' 3 | -------------------------------------------------------------------------------- /src/ui/components/full/full.spec.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuetify from 'vuetify' 3 | import { FullComponent } from './full' 4 | import { mount, createLocalVue, Wrapper } from '@vue/test-utils' 5 | import VueRouter from 'vue-router' 6 | import { mockStorage } from '@/store' 7 | import { mockArticles, mockComments } from '@/entities' 8 | 9 | Vue.use(Vuetify) 10 | Vue.use(VueRouter) 11 | 12 | const localVue = createLocalVue() 13 | const article = mockArticles()[0] 14 | 15 | const comment1 = mockComments()[0] 16 | const comment2 = mockComments()[1] 17 | article.comments.push(comment1) 18 | article.comments.push(comment2) 19 | 20 | describe('>>> Article Component', () => { 21 | let wrapper: Wrapper 22 | 23 | beforeEach(() => { 24 | wrapper = mount(FullComponent, { 25 | localVue, 26 | vuetify: new Vuetify(), 27 | router: new VueRouter(), 28 | mocks: { 29 | $storage: mockStorage() 30 | }, 31 | propsData: { article } 32 | }) 33 | }) 34 | 35 | it('should render article', () => { 36 | expect(wrapper.find('.v-card__title').text()).toBe(article.title) 37 | expect(wrapper.find('.v-card__text').text()).toContain(article.content) 38 | }) 39 | 40 | it('should render comments', () => { 41 | expect(wrapper.findAll('.v-list-item').length).toBe(article.comments.length) 42 | }) 43 | 44 | it('should create new comment when form is submitted', () => { 45 | const title = 'title' 46 | const author = 'author' 47 | const content = 'content' 48 | 49 | wrapper.findAll('.v-input').at(0).vm.$emit('input', author) 50 | wrapper.findAll('.v-input').at(1).vm.$emit('input', title) 51 | wrapper.findAll('.v-input').at(2).vm.$emit('input', content) 52 | 53 | wrapper.find('.v-form').vm.$emit('submit', { preventDefault: jest.fn() }) 54 | expect(wrapper.vm.$storage.actions.createComment).toBeCalledWith({ 55 | articleId: article.id as number, 56 | comment: { 57 | title, 58 | author, 59 | content 60 | } 61 | }) 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /src/ui/components/full/full.tsx: -------------------------------------------------------------------------------- 1 | import Component from 'vue-class-component' 2 | import { VueComponent } from '@/ui/vue-ts-component' 3 | import { IFullProps } from './full.types' 4 | import { 5 | COMMENTS_MAX_TITLE_LENGTH, 6 | COMMENTS_MAX_AUTHOR_LENGTH, 7 | COMMENTS_MAX_CONTENT_LENGTH 8 | } from '@/entities' 9 | 10 | @Component({ 11 | props: { 12 | article: { type: Object, required: true } 13 | }, 14 | render (this: FullComponent): Vue.VNode { 15 | const article = this.$props.article 16 | 17 | return ( 18 | 19 | 20 | 21 | {article.title} 22 | 23 | 24 |

{article.content}

25 |

Created At: {this.getDate(article.createdAt as string)}

26 |

Tags: {article.tags.join(', ')}

27 |
28 |
29 | 30 | Comments 31 | 32 | 33 | 34 | {article.comments.map(comment => ( 35 | 36 | 37 | mdi-account 38 | 39 | 40 | {comment.title} by {comment.author} 41 | {comment.content} 42 | 43 | 44 | ))} 45 | 46 | 47 | 48 | 49 | 50 | New Comment 51 | 52 | { this.isValid = v }} 55 | onSubmit={this.onSubmit} 56 | > 57 | 58 | 59 | { this.author = v }} 63 | counter={COMMENTS_MAX_AUTHOR_LENGTH} 64 | label="Author name" 65 | required 66 | outlined 67 | > 68 | 69 | 70 | { this.title = v }} 74 | counter={COMMENTS_MAX_TITLE_LENGTH} 75 | label="Title" 76 | required 77 | outlined 78 | > 79 | 80 | 81 | { this.content = v }} 85 | counter={COMMENTS_MAX_CONTENT_LENGTH} 86 | label="Content" 87 | required 88 | outlined 89 | > 90 | 91 | 92 | Submit 93 | 94 | 95 | 96 |
97 | ) 98 | } 99 | }) 100 | export class FullComponent extends VueComponent { 101 | private isValid = false 102 | private author = '' 103 | private title = '' 104 | private content = '' 105 | 106 | private authorRules = [ 107 | (v: string): boolean | string => !!v || 'Author name is required', 108 | (v: string): boolean | string => v.length <= COMMENTS_MAX_AUTHOR_LENGTH || 'Author name must be less than 10 characters' 109 | ] 110 | 111 | private titleRules = [ 112 | (v: string): boolean | string => !!v || 'Title is required', 113 | (v: string): boolean | string => v.length <= COMMENTS_MAX_TITLE_LENGTH || 'Title must be less than 10 characters' 114 | ] 115 | 116 | private contentRules = [ 117 | (v: string): boolean | string => !!v || 'Content is required', 118 | (v: string): boolean | string => v.length <= COMMENTS_MAX_CONTENT_LENGTH || 'Content must be less than 10 characters' 119 | ] 120 | 121 | private getDate (timestamp: string): string { 122 | return timestamp.substring(0, timestamp.indexOf('T')) 123 | } 124 | 125 | private onSubmit (e: Event): void { 126 | e.preventDefault() 127 | this.$storage.actions.createComment({ 128 | articleId: this.$props.article.id as number, 129 | comment: { 130 | title: this.title.trim(), 131 | author: this.author.trim(), 132 | content: this.content.trim() 133 | } 134 | }) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/ui/components/full/full.types.ts: -------------------------------------------------------------------------------- 1 | import { IArticle } from '@/entities' 2 | 3 | export interface IFullProps { 4 | article: IArticle 5 | } 6 | -------------------------------------------------------------------------------- /src/ui/components/full/index.ts: -------------------------------------------------------------------------------- 1 | export * from './full' 2 | export * from './full.types' 3 | -------------------------------------------------------------------------------- /src/ui/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './article' 2 | export * from './full' 3 | -------------------------------------------------------------------------------- /src/ui/index.ts: -------------------------------------------------------------------------------- 1 | // export * from './components' 2 | export * from './pages' 3 | export * from './plugins' 4 | export * from './router' 5 | export * from './app' 6 | -------------------------------------------------------------------------------- /src/ui/pages/article/article.spec.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuetify from 'vuetify' 3 | import { ArticlePage } from './article' 4 | import { mount, createLocalVue, Wrapper } from '@vue/test-utils' 5 | import VueRouter from 'vue-router' 6 | import { mockStorage } from '@/store' 7 | import { mockArticles } from '@/entities' 8 | 9 | Vue.use(Vuetify) 10 | Vue.use(VueRouter) 11 | 12 | const localVue = createLocalVue() 13 | const storage = mockStorage() 14 | 15 | storage.getters.getOneArticlesById.mockReturnValueOnce(mockArticles()[0]) 16 | 17 | describe('>>> ArticlePage', () => { 18 | let wrapper: Wrapper 19 | 20 | beforeEach(() => { 21 | wrapper = mount(ArticlePage, { 22 | localVue, 23 | vuetify: new Vuetify(), 24 | router: new VueRouter(), 25 | mocks: { 26 | $storage: storage 27 | } 28 | }) 29 | }) 30 | 31 | it('should render container and row', () => { 32 | expect(wrapper.find('.container')).toBeDefined() 33 | expect(wrapper.find('.row')).toBeDefined() 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /src/ui/pages/article/article.tsx: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Component from 'vue-class-component' 3 | import { IArticle } from '@/entities' 4 | import { FullComponent } from '@/ui/components' 5 | 6 | @Component({ 7 | mounted (): void { 8 | this.$storage.actions.fetchArticles() 9 | }, 10 | 11 | render (this: ArticlePage): Vue.VNode { 12 | return ( 13 | 14 | 15 | 16 | {this.article && } 17 | 18 | 19 | 20 | ) 21 | } 22 | }) 23 | export class ArticlePage extends Vue { 24 | private get article (): IArticle | undefined { 25 | return this.$storage.getters.getOneArticlesById(Number(this.$route.params.id)) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/ui/pages/article/index.ts: -------------------------------------------------------------------------------- 1 | export * from './article' 2 | -------------------------------------------------------------------------------- /src/ui/pages/home/home.spec.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuetify from 'vuetify' 3 | import { HomePage } from './home' 4 | import { mount, createLocalVue, Wrapper } from '@vue/test-utils' 5 | import VueRouter from 'vue-router' 6 | import { mockStorage } from '@/store' 7 | import { mockArticles } from '@/entities' 8 | 9 | Vue.use(Vuetify) 10 | Vue.use(VueRouter) 11 | 12 | const localVue = createLocalVue() 13 | const storage = mockStorage() 14 | 15 | storage.getters.getAllArticles.mockReturnValueOnce(mockArticles()) 16 | 17 | describe('>>> HomePage', () => { 18 | let wrapper: Wrapper 19 | 20 | beforeEach(() => { 21 | wrapper = mount(HomePage, { 22 | localVue, 23 | vuetify: new Vuetify(), 24 | router: new VueRouter(), 25 | mocks: { 26 | $storage: storage 27 | } 28 | }) 29 | }) 30 | 31 | it('should render container and row', () => { 32 | expect(wrapper.find('.container')).toBeDefined() 33 | expect(wrapper.find('.row')).toBeDefined() 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /src/ui/pages/home/home.tsx: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Component from 'vue-class-component' 3 | import { IArticle } from '@/entities' 4 | import { ArticleComponent } from '@/ui/components' 5 | 6 | @Component({ 7 | mounted (): void { 8 | this.$storage.actions.fetchArticles() 9 | }, 10 | 11 | render (this: HomePage) { 12 | return ( 13 | 14 | 15 | {this.articles.map(article => ())} 16 | 17 | 18 | ) 19 | } 20 | }) 21 | export class HomePage extends Vue { 22 | private get articles (): IArticle[] { 23 | return this.$storage.getters.getAllArticles() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/ui/pages/home/index.ts: -------------------------------------------------------------------------------- 1 | export * from './home' 2 | -------------------------------------------------------------------------------- /src/ui/pages/index.ts: -------------------------------------------------------------------------------- 1 | export * from './home' 2 | export * from './article' 3 | -------------------------------------------------------------------------------- /src/ui/plugins/index.ts: -------------------------------------------------------------------------------- 1 | export * from './vuetify' 2 | export * from './services' 3 | export * from './storage' 4 | -------------------------------------------------------------------------------- /src/ui/plugins/services.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { provider } from '@/services' 3 | import { IStore } from '@/store' 4 | 5 | export const prepareServices = (store: IStore): void => { 6 | store.$services = provider() 7 | 8 | Vue.mixin({ 9 | beforeCreate () { 10 | this.$services = store.$services 11 | } 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /src/ui/plugins/storage.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { makeStorage, IStore } from '@/store' 3 | 4 | export const prepareStorage = (store: IStore): void => { 5 | store.$storage = makeStorage(store) 6 | 7 | Vue.mixin({ 8 | beforeCreate () { 9 | this.$storage = store.$storage 10 | } 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /src/ui/plugins/vuetify.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuetify, { 3 | VApp, 4 | VAppBar, 5 | VContent, 6 | VContainer, 7 | VCol, 8 | VRow, 9 | VCard, 10 | VCardActions, 11 | VImg, 12 | VCardText, 13 | VCardTitle, 14 | VList, 15 | VListItem, 16 | VListItemTitle, 17 | VListItemSubtitle, 18 | VListItemContent, 19 | VListItemGroup, 20 | VListItemIcon, 21 | VIcon, 22 | VTextField, 23 | VForm, 24 | VBtn 25 | } from 'vuetify/lib' 26 | 27 | Vue.use(Vuetify, { 28 | components: { 29 | VApp, 30 | VAppBar, 31 | VContent, 32 | VContainer, 33 | VCol, 34 | VRow, 35 | VCard, 36 | VCardActions, 37 | VImg, 38 | VCardText, 39 | VCardTitle, 40 | VList, 41 | VListItem, 42 | VListItemTitle, 43 | VListItemSubtitle, 44 | VListItemContent, 45 | VListItemGroup, 46 | VListItemIcon, 47 | VIcon, 48 | VTextField, 49 | VForm, 50 | VBtn 51 | } 52 | }) 53 | 54 | export const vuetify = new Vuetify({}) 55 | -------------------------------------------------------------------------------- /src/ui/router.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import { HomePage, ArticlePage } from '@/ui/pages' 4 | 5 | Vue.use(VueRouter) 6 | 7 | const routes = [ 8 | { 9 | path: '/', 10 | name: 'Home', 11 | component: HomePage 12 | }, { 13 | path: '/articles/:id', 14 | name: 'Article', 15 | component: ArticlePage 16 | } 17 | ] 18 | 19 | export const router = new VueRouter({ 20 | mode: 'history', 21 | base: process.env.BASE_URL, 22 | routes 23 | }) 24 | -------------------------------------------------------------------------------- /src/ui/shims-provider.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/interface-name-prefix */ 2 | import { IProvider } from '@/services' 3 | 4 | declare module 'vue/types/options' { 5 | interface ComponentOptions { 6 | services?: IProvider 7 | } 8 | } 9 | 10 | declare module 'vue/types/vue' { 11 | interface Vue { 12 | $services: IProvider 13 | } 14 | } 15 | 16 | declare module 'vuex/types/index' { 17 | interface Store { 18 | $services: IProvider 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/ui/shims-storage.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/interface-name-prefix */ 2 | import { IStorage } from '@/store' 3 | 4 | declare module 'vue/types/options' { 5 | interface ComponentOptions { 6 | storage?: IStorage 7 | } 8 | } 9 | 10 | declare module 'vue/types/vue' { 11 | interface Vue { 12 | $storage: IStorage 13 | } 14 | } 15 | 16 | declare module 'vuex/types/index' { 17 | interface Store { 18 | $storage: IStorage 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/ui/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | /* eslint-disable @typescript-eslint/interface-name-prefix */ 3 | import Vue, { VNode } from 'vue' 4 | 5 | declare global { 6 | namespace JSX { 7 | // tslint:disable no-empty-interface 8 | interface Element extends VNode { } 9 | // tslint:disable no-empty-interface 10 | interface ElementClass extends Vue { } 11 | interface IntrinsicElements { 12 | [elem: string]: any 13 | } 14 | interface ElementAttributesProperty { $props: {} } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/ui/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue' 3 | export default Vue 4 | } 5 | -------------------------------------------------------------------------------- /src/ui/vue-ts-component.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | export class VueComponent

extends Vue { 4 | $props!: P 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "resolveJsonModule": true, 14 | "baseUrl": ".", 15 | "typeRoots": [ 16 | "./node_modules/@types", 17 | "./node_modules/vuetify/types" 18 | ], 19 | "types": [ 20 | "webpack-env", 21 | "jest", 22 | "vuetify" 23 | ], 24 | "paths": { 25 | "@/*": [ 26 | "src/*" 27 | ] 28 | }, 29 | "lib": [ 30 | "esnext", 31 | "dom", 32 | "dom.iterable", 33 | "scripthost" 34 | ] 35 | }, 36 | "include": [ 37 | "src/**/*.ts", 38 | "src/**/*.tsx", 39 | "src/**/*.vue", 40 | "tests/**/*.ts", 41 | "tests/**/*.tsx" 42 | ], 43 | "exclude": [ 44 | "node_modules" 45 | ] 46 | } -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | lintOnSave: false, 3 | transpileDependencies: [ 4 | 'vuetify' 5 | ] 6 | } 7 | --------------------------------------------------------------------------------