Repository: thanhchungbtc/vue-shopping-clean-architecture Branch: master Commit: 5d6d49811f9c Files: 76 Total size: 201.0 KB Directory structure: gitextract_4fmq2k0l/ ├── .browserslistrc ├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── README.md ├── babel.config.js ├── coverage/ │ ├── clover.xml │ ├── coverage-final.json │ ├── lcov-report/ │ │ ├── base.css │ │ ├── block-navigation.js │ │ ├── index.html │ │ ├── prettify.css │ │ ├── prettify.js │ │ ├── sorter.js │ │ └── src/ │ │ ├── app/ │ │ │ ├── components/ │ │ │ │ ├── Product.vue.html │ │ │ │ ├── ProductList.vue.html │ │ │ │ └── index.html │ │ │ └── store/ │ │ │ ├── cart.ts.html │ │ │ ├── index.html │ │ │ ├── index.ts.html │ │ │ └── product.ts.html │ │ ├── data/ │ │ │ └── inMemoryRepository/ │ │ │ ├── cartRepository.ts.html │ │ │ ├── index.html │ │ │ └── productRepository.ts.html │ │ ├── di.ts.html │ │ ├── index.html │ │ └── usecases/ │ │ └── interactor/ │ │ ├── addItemToCart.ts.html │ │ ├── getAllProduct.ts.html │ │ ├── getTotalCartItem.ts.html │ │ └── index.html │ └── lcov.info ├── cypress.json ├── jest.config.js ├── package.json ├── public/ │ ├── index.html │ └── robots.txt ├── src/ │ ├── app/ │ │ ├── App.vue │ │ ├── assets/ │ │ │ └── app.css │ │ ├── components/ │ │ │ ├── CartPreview.vue │ │ │ ├── NavBar.vue │ │ │ ├── Product.vue │ │ │ └── ProductList.vue │ │ ├── main.ts │ │ ├── plugins/ │ │ │ └── vuetify.ts │ │ ├── registerServiceWorker.ts │ │ ├── router/ │ │ │ └── index.ts │ │ ├── shims-tsx.d.ts │ │ ├── shims-vue.d.ts │ │ ├── store/ │ │ │ ├── cart.ts │ │ │ ├── index.ts │ │ │ └── product.ts │ │ └── views/ │ │ ├── About.vue │ │ ├── Checkout.vue │ │ └── Home.vue │ ├── data/ │ │ └── inMemoryRepository/ │ │ ├── cartRepository.ts │ │ └── productRepository.ts │ ├── di.ts │ ├── domain/ │ │ └── entity/ │ │ └── index.ts │ ├── main.ts │ └── usecases/ │ ├── interactor/ │ │ ├── addItemToCart.ts │ │ ├── getAllProduct.ts │ │ ├── getTotalCartItem.ts │ │ └── proceedCheckout.ts │ └── repository/ │ ├── cartRepository.ts │ └── productRepository.ts ├── tests/ │ ├── e2e/ │ │ ├── .eslintrc.js │ │ ├── plugins/ │ │ │ └── index.js │ │ ├── specs/ │ │ │ └── test.js │ │ └── support/ │ │ ├── commands.js │ │ └── index.js │ └── unit/ │ ├── components/ │ │ ├── Product.spec.ts │ │ └── ProductList.spec.ts │ └── store/ │ ├── cart.spec.ts │ └── product.spec.ts ├── tsconfig.json └── vue.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .browserslistrc ================================================ > 1% last 2 versions ================================================ FILE: .eslintrc.js ================================================ module.exports = { root: true, env: { node: true }, 'extends': [ 'plugin:vue/essential', 'eslint:recommended', '@vue/typescript/recommended' ], parserOptions: { ecmaVersion: 2020 }, rules: { 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', '@typescript-eslint/ban-ts-ignore': 'off', '@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/no-empty-function': 'off', }, overrides: [ { files: [ '**/__tests__/*.{j,t}s?(x)', '**/tests/unit/**/*.spec.{j,t}s?(x)' ], env: { mocha: true } } ] } ================================================ FILE: .gitignore ================================================ .DS_Store node_modules /dist /tests/e2e/videos/ /tests/e2e/screenshots/ # local env files .env.local .env.*.local # Log files npm-debug.log* yarn-debug.log* yarn-error.log* # Editor directories and files .idea .vscode *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: .travis.yml ================================================ language: node_js node_js: - 12 install: yarn script: - yarn build - yarn test:unit ================================================ FILE: README.md ================================================ # Clean Architecture VueJS Clean architecture with vue, shopping cart demo [Demo](https://thanhchungbtc.github.io/vue-shopping) ![](https://travis-ci.org/thanhchungbtc/vue-shopping-clean-architecture.svg?branch=master) ![](./github/screenshot1.png) ![](./github/screenshot2.png) ## Development ```sh yarn serve ``` ## Production ```sh yarn build ``` ## Unit test ```sh yarn test:unit ``` ## Description This is an example of implementation of Clean Architecture in Vue It has major of 4 layers: - Entity layer - Repository layer - Usecase layer - Application layer - where the ui happend ![](./github/clean-architecture.jpg) ## Tools used - `inversify`\ Dependency injection for typescript ```ts container .bind("CartRepository") .to(CartRepositoryImpl) .inSingletonScope(); ``` Usage ```ts constructor( @inject("CartRepository") private cartRepository: CartRepository ) {} ``` - `vuetify`\ Material design ui library - `vuex-module-decorators`\ Access `vuex` store in a type-safety way Instead of writing as ```ts this.$store.dispatch('cart/addProductToCart', {product: this.product, quantity: 1}) ``` We write ```ts const cartStore = getModule(CartStore, this.$store) cartStore.addProductToCart({product: this.product, quantity: 1}) ``` ## TODO - [x] In memory repository - [ ] Use pouchDB to persist user's cart data ================================================ FILE: babel.config.js ================================================ module.exports = { presets: [ '@vue/cli-plugin-babel/preset' ] } ================================================ FILE: coverage/clover.xml ================================================ ================================================ FILE: coverage/coverage-final.json ================================================ {"C:\\Users\\B7911\\Desktop\\vue-shopping-clean-architecture\\src\\di.ts": {"path":"C:\\Users\\B7911\\Desktop\\vue-shopping-clean-architecture\\src\\di.ts","statementMap":{"0":{"start":{"line":1,"column":0},"end":{"line":1,"column":null}},"1":{"start":{"line":2,"column":0},"end":{"line":2,"column":null}},"2":{"start":{"line":3,"column":0},"end":{"line":3,"column":null}},"3":{"start":{"line":6,"column":0},"end":{"line":6,"column":null}},"4":{"start":{"line":7,"column":0},"end":{"line":7,"column":null}},"5":{"start":{"line":8,"column":0},"end":{"line":8,"column":null}},"6":{"start":{"line":9,"column":0},"end":{"line":9,"column":null}},"7":{"start":{"line":10,"column":0},"end":{"line":10,"column":null}},"8":{"start":{"line":12,"column":18},"end":{"line":12,"column":22}},"9":{"start":{"line":14,"column":0},"end":{"line":14,"column":9}},"10":{"start":{"line":18,"column":0},"end":{"line":18,"column":9}},"11":{"start":{"line":23,"column":0},"end":{"line":23,"column":9}},"12":{"start":{"line":27,"column":0},"end":{"line":27,"column":9}},"13":{"start":{"line":31,"column":0},"end":{"line":31,"column":9}},"14":{"start":{"line":36,"column":21},"end":{"line":36,"column":35}}},"fnMap":{},"branchMap":{},"s":{"0":4,"1":4,"2":4,"3":4,"4":4,"5":4,"6":4,"7":4,"8":4,"9":4,"10":4,"11":4,"12":4,"13":4,"14":4},"f":{},"b":{}} ,"C:\\Users\\B7911\\Desktop\\vue-shopping-clean-architecture\\src\\app\\components\\Product.vue": {"path":"C:\\Users\\B7911\\Desktop\\vue-shopping-clean-architecture\\src\\app\\components\\Product.vue","statementMap":{"0":{"start":{"line":19,"column":0},"end":{"line":19,"column":null}},"1":{"start":{"line":20,"column":0},"end":{"line":20,"column":null}},"2":{"start":{"line":30,"column":0},"end":{"line":30,"column":null}},"3":{"start":{"line":37,"column":0},"end":{"line":37,"column":null}},"4":{"start":{"line":38,"column":0},"end":{"line":41,"column":null}},"5":{"start":{"line":40,"column":0},"end":{"line":40,"column":null}}},"fnMap":{"0":{"name":"(anonymous_1)","decl":{"start":{"line":29,"column":0},"end":{"line":29,"column":null}},"loc":{"start":{"line":29,"column":0},"end":{"line":33,"column":null}}},"1":{"name":"(anonymous_2)","decl":{"start":{"line":36,"column":0},"end":{"line":36,"column":null}},"loc":{"start":{"line":36,"column":0},"end":{"line":42,"column":null}}},"2":{"name":"(anonymous_3)","decl":{"start":{"line":38,"column":0},"end":{"line":38,"column":null}},"loc":{"start":{"line":38,"column":0},"end":{"line":41,"column":null}}}},"branchMap":{},"s":{"0":2,"1":2,"2":3,"3":0,"4":0,"5":0},"f":{"0":3,"1":0,"2":0},"b":{}} ,"C:\\Users\\B7911\\Desktop\\vue-shopping-clean-architecture\\src\\app\\components\\ProductList.vue": {"path":"C:\\Users\\B7911\\Desktop\\vue-shopping-clean-architecture\\src\\app\\components\\ProductList.vue","statementMap":{"0":{"start":{"line":11,"column":0},"end":{"line":11,"column":null}},"1":{"start":{"line":12,"column":0},"end":{"line":12,"column":null}},"2":{"start":{"line":13,"column":0},"end":{"line":13,"column":null}},"3":{"start":{"line":14,"column":0},"end":{"line":14,"column":null}},"4":{"start":{"line":24,"column":0},"end":{"line":24,"column":null}},"5":{"start":{"line":28,"column":0},"end":{"line":28,"column":null}},"6":{"start":{"line":33,"column":0},"end":{"line":33,"column":null}}},"fnMap":{"0":{"name":"(anonymous_1)","decl":{"start":{"line":23,"column":0},"end":{"line":23,"column":null}},"loc":{"start":{"line":23,"column":0},"end":{"line":25,"column":null}}},"1":{"name":"(anonymous_2)","decl":{"start":{"line":27,"column":0},"end":{"line":27,"column":null}},"loc":{"start":{"line":27,"column":0},"end":{"line":29,"column":null}}},"2":{"name":"(anonymous_3)","decl":{"start":{"line":32,"column":0},"end":{"line":32,"column":null}},"loc":{"start":{"line":32,"column":0},"end":{"line":36,"column":null}}}},"branchMap":{},"s":{"0":1,"1":1,"2":1,"3":1,"4":1,"5":1,"6":1},"f":{"0":1,"1":1,"2":1},"b":{}} ,"C:\\Users\\B7911\\Desktop\\vue-shopping-clean-architecture\\src\\app\\store\\cart.ts": {"path":"C:\\Users\\B7911\\Desktop\\vue-shopping-clean-architecture\\src\\app\\store\\cart.ts","statementMap":{"0":{"start":{"line":1,"column":0},"end":{"line":1,"column":null}},"1":{"start":{"line":2,"column":0},"end":{"line":2,"column":null}},"2":{"start":{"line":19,"column":0},"end":{"line":19,"column":null}},"3":{"start":{"line":20,"column":9},"end":{"line":20,"column":25}},"4":{"start":{"line":24,"column":4},"end":{"line":24,"column":11}},"5":{"start":{"line":24,"column":44},"end":{"line":24,"column":65}},"6":{"start":{"line":28,"column":4},"end":{"line":28,"column":11}},"7":{"start":{"line":29,"column":21},"end":{"line":29,"column":null}},"8":{"start":{"line":36,"column":16},"end":{"line":36,"column":21}},"9":{"start":{"line":36,"column":42},"end":{"line":36,"column":null}},"10":{"start":{"line":37,"column":4},"end":{"line":41,"column":null}},"11":{"start":{"line":38,"column":6},"end":{"line":38,"column":11}},"12":{"start":{"line":40,"column":6},"end":{"line":40,"column":11}},"13":{"start":{"line":46,"column":4},"end":{"line":46,"column":10}},"14":{"start":{"line":47,"column":4},"end":{"line":47,"column":9}},"15":{"start":{"line":21,"column":31},"end":{"line":21,"column":69}},"16":{"start":{"line":35,"column":2},"end":{"line":35,"column":9}},"17":{"start":{"line":45,"column":2},"end":{"line":45,"column":8}},"18":{"start":{"line":19,"column":13},"end":{"line":19,"column":22}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":19,"column":0},"end":{"line":19,"column":13}},"loc":{"start":{"line":19,"column":0},"end":{"line":52,"column":null}}},"1":{"name":"(anonymous_1)","decl":{"start":{"line":23,"column":2},"end":{"line":23,"column":6}},"loc":{"start":{"line":23,"column":19},"end":{"line":25,"column":null}}},"2":{"name":"(anonymous_2)","decl":{"start":{"line":24,"column":29},"end":{"line":24,"column":30}},"loc":{"start":{"line":24,"column":44},"end":{"line":24,"column":65}}},"3":{"name":"(anonymous_3)","decl":{"start":{"line":27,"column":2},"end":{"line":27,"column":6}},"loc":{"start":{"line":27,"column":17},"end":{"line":32,"column":null}}},"4":{"name":"(anonymous_4)","decl":{"start":{"line":29,"column":6},"end":{"line":29,"column":7}},"loc":{"start":{"line":29,"column":21},"end":{"line":29,"column":null}}},"5":{"name":"(anonymous_5)","decl":{"start":{"line":35,"column":2},"end":{"line":35,"column":9}},"loc":{"start":{"line":35,"column":20},"end":{"line":42,"column":null}}},"6":{"name":"(anonymous_6)","decl":{"start":{"line":36,"column":37},"end":{"line":36,"column":38}},"loc":{"start":{"line":36,"column":42},"end":{"line":36,"column":null}}},"7":{"name":"(anonymous_7)","decl":{"start":{"line":45,"column":2},"end":{"line":45,"column":8}},"loc":{"start":{"line":45,"column":71},"end":{"line":51,"column":null}}}},"branchMap":{"0":{"loc":{"start":{"line":37,"column":4},"end":{"line":41,"column":null}},"type":"if","locations":[{"start":{"line":37,"column":4},"end":{"line":41,"column":null}},{"start":{"line":37,"column":4},"end":{"line":41,"column":null}}]}},"s":{"0":3,"1":3,"2":3,"3":3,"4":0,"5":0,"6":0,"7":0,"8":4,"9":2,"10":4,"11":1,"12":3,"13":4,"14":4,"15":3,"16":3,"17":3,"18":3},"f":{"0":3,"1":0,"2":0,"3":0,"4":0,"5":4,"6":2,"7":4},"b":{"0":[1,3]}} ,"C:\\Users\\B7911\\Desktop\\vue-shopping-clean-architecture\\src\\app\\store\\index.ts": {"path":"C:\\Users\\B7911\\Desktop\\vue-shopping-clean-architecture\\src\\app\\store\\index.ts","statementMap":{"0":{"start":{"line":1,"column":0},"end":{"line":1,"column":null}},"1":{"start":{"line":2,"column":0},"end":{"line":2,"column":null}},"2":{"start":{"line":3,"column":0},"end":{"line":3,"column":null}},"3":{"start":{"line":4,"column":0},"end":{"line":4,"column":null}},"4":{"start":{"line":5,"column":0},"end":{"line":5,"column":null}},"5":{"start":{"line":7,"column":0},"end":{"line":7,"column":4}},"6":{"start":{"line":14,"column":14},"end":{"line":14,"column":18}},"7":{"start":{"line":21,"column":21},"end":{"line":21,"column":31}},"8":{"start":{"line":22,"column":18},"end":{"line":22,"column":28}},"9":{"start":{"line":24,"column":15},"end":{"line":24,"column":null}}},"fnMap":{},"branchMap":{},"s":{"0":2,"1":2,"2":2,"3":2,"4":2,"5":2,"6":2,"7":2,"8":2,"9":2},"f":{},"b":{}} ,"C:\\Users\\B7911\\Desktop\\vue-shopping-clean-architecture\\src\\app\\store\\product.ts": {"path":"C:\\Users\\B7911\\Desktop\\vue-shopping-clean-architecture\\src\\app\\store\\product.ts","statementMap":{"0":{"start":{"line":1,"column":0},"end":{"line":1,"column":null}},"1":{"start":{"line":3,"column":0},"end":{"line":3,"column":null}},"2":{"start":{"line":15,"column":0},"end":{"line":15,"column":null}},"3":{"start":{"line":16,"column":9},"end":{"line":16,"column":28}},"4":{"start":{"line":22,"column":4},"end":{"line":22,"column":9}},"5":{"start":{"line":27,"column":17},"end":{"line":27,"column":28}},"6":{"start":{"line":28,"column":4},"end":{"line":28,"column":9}},"7":{"start":{"line":18,"column":31},"end":{"line":18,"column":69}},"8":{"start":{"line":21,"column":2},"end":{"line":21,"column":10}},"9":{"start":{"line":26,"column":2},"end":{"line":26,"column":8}},"10":{"start":{"line":15,"column":13},"end":{"line":15,"column":25}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":15,"column":0},"end":{"line":15,"column":13}},"loc":{"start":{"line":15,"column":0},"end":{"line":30,"column":null}}},"1":{"name":"(anonymous_1)","decl":{"start":{"line":21,"column":2},"end":{"line":21,"column":10}},"loc":{"start":{"line":21,"column":27},"end":{"line":23,"column":null}}},"2":{"name":"(anonymous_2)","decl":{"start":{"line":26,"column":2},"end":{"line":26,"column":8}},"loc":{"start":{"line":26,"column":18},"end":{"line":29,"column":null}}}},"branchMap":{},"s":{"0":3,"1":3,"2":3,"3":3,"4":4,"5":2,"6":2,"7":3,"8":3,"9":3,"10":3},"f":{"0":3,"1":4,"2":2},"b":{}} ,"C:\\Users\\B7911\\Desktop\\vue-shopping-clean-architecture\\src\\data\\inMemoryRepository\\cartRepository.ts": {"path":"C:\\Users\\B7911\\Desktop\\vue-shopping-clean-architecture\\src\\data\\inMemoryRepository\\cartRepository.ts","statementMap":{"0":{"start":{"line":1,"column":0},"end":{"line":1,"column":null}},"1":{"start":{"line":2,"column":0},"end":{"line":2,"column":null}},"2":{"start":{"line":3,"column":0},"end":{"line":3,"column":null}},"3":{"start":{"line":7,"column":0},"end":{"line":7,"column":null}},"4":{"start":{"line":8,"column":10},"end":{"line":8,"column":27}},"5":{"start":{"line":11,"column":4},"end":{"line":11,"column":9}},"6":{"start":{"line":12,"column":4},"end":{"line":12,"column":11}},"7":{"start":{"line":15,"column":8},"end":{"line":15,"column":null}},"8":{"start":{"line":21,"column":16},"end":{"line":21,"column":null}},"9":{"start":{"line":22,"column":4},"end":{"line":22,"column":11}},"10":{"start":{"line":24,"column":16},"end":{"line":24,"column":null}},"11":{"start":{"line":7,"column":21},"end":{"line":7,"column":39}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":7,"column":0},"end":{"line":7,"column":21}},"loc":{"start":{"line":7,"column":0},"end":{"line":27,"column":null}}},"1":{"name":"(anonymous_1)","decl":{"start":{"line":10,"column":9},"end":{"line":10,"column":22}},"loc":{"start":{"line":10,"column":57},"end":{"line":18,"column":null}}},"2":{"name":"(anonymous_2)","decl":{"start":{"line":14,"column":10},"end":{"line":14,"column":15}},"loc":{"start":{"line":14,"column":15},"end":{"line":16,"column":null}}},"3":{"name":"(anonymous_3)","decl":{"start":{"line":20,"column":9},"end":{"line":20,"column":25}},"loc":{"start":{"line":20,"column":25},"end":{"line":26,"column":null}}},"4":{"name":"(anonymous_4)","decl":{"start":{"line":24,"column":10},"end":{"line":24,"column":16}},"loc":{"start":{"line":24,"column":16},"end":{"line":24,"column":null}}}},"branchMap":{},"s":{"0":4,"1":4,"2":4,"3":4,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":8},"f":{"0":0,"1":0,"2":0,"3":0,"4":0},"b":{}} ,"C:\\Users\\B7911\\Desktop\\vue-shopping-clean-architecture\\src\\data\\inMemoryRepository\\productRepository.ts": {"path":"C:\\Users\\B7911\\Desktop\\vue-shopping-clean-architecture\\src\\data\\inMemoryRepository\\productRepository.ts","statementMap":{"0":{"start":{"line":1,"column":0},"end":{"line":1,"column":null}},"1":{"start":{"line":3,"column":0},"end":{"line":3,"column":null}},"2":{"start":{"line":5,"column":0},"end":{"line":5,"column":null}},"3":{"start":{"line":8,"column":0},"end":{"line":8,"column":null}},"4":{"start":{"line":12,"column":20},"end":{"line":12,"column":null}},"5":{"start":{"line":23,"column":4},"end":{"line":23,"column":11}},"6":{"start":{"line":24,"column":21},"end":{"line":24,"column":null}},"7":{"start":{"line":25,"column":4},"end":{"line":33,"column":null}},"8":{"start":{"line":25,"column":17},"end":{"line":25,"column":20}},"9":{"start":{"line":26,"column":6},"end":{"line":26,"column":14}},"10":{"start":{"line":34,"column":4},"end":{"line":34,"column":9}},"11":{"start":{"line":38,"column":4},"end":{"line":38,"column":11}},"12":{"start":{"line":39,"column":16},"end":{"line":39,"column":null}},"13":{"start":{"line":8,"column":21},"end":{"line":8,"column":42}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":11,"column":2},"end":{"line":11,"column":null}},"loc":{"start":{"line":11,"column":2},"end":{"line":35,"column":null}}},"1":{"name":"(anonymous_1)","decl":{"start":{"line":37,"column":9},"end":{"line":37,"column":15}},"loc":{"start":{"line":37,"column":15},"end":{"line":41,"column":null}}},"2":{"name":"(anonymous_2)","decl":{"start":{"line":39,"column":10},"end":{"line":39,"column":16}},"loc":{"start":{"line":39,"column":16},"end":{"line":39,"column":null}}}},"branchMap":{},"s":{"0":4,"1":4,"2":4,"3":4,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":8},"f":{"0":0,"1":0,"2":0},"b":{}} ,"C:\\Users\\B7911\\Desktop\\vue-shopping-clean-architecture\\src\\usecases\\interactor\\addItemToCart.ts": {"path":"C:\\Users\\B7911\\Desktop\\vue-shopping-clean-architecture\\src\\usecases\\interactor\\addItemToCart.ts","statementMap":{"0":{"start":{"line":2,"column":0},"end":{"line":2,"column":null}},"1":{"start":{"line":11,"column":0},"end":{"line":11,"column":null}},"2":{"start":{"line":13,"column":38},"end":{"line":13,"column":68}},"3":{"start":{"line":18,"column":4},"end":{"line":18,"column":11}},"4":{"start":{"line":11,"column":13},"end":{"line":11,"column":30}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":12,"column":2},"end":{"line":12,"column":null}},"loc":{"start":{"line":13,"column":68},"end":{"line":15,"column":null}}},"1":{"name":"(anonymous_1)","decl":{"start":{"line":17,"column":2},"end":{"line":17,"column":9}},"loc":{"start":{"line":17,"column":44},"end":{"line":19,"column":null}}}},"branchMap":{},"s":{"0":4,"1":4,"2":0,"3":0,"4":4},"f":{"0":0,"1":0},"b":{}} ,"C:\\Users\\B7911\\Desktop\\vue-shopping-clean-architecture\\src\\usecases\\interactor\\getAllProduct.ts": {"path":"C:\\Users\\B7911\\Desktop\\vue-shopping-clean-architecture\\src\\usecases\\interactor\\getAllProduct.ts","statementMap":{"0":{"start":{"line":3,"column":0},"end":{"line":3,"column":null}},"1":{"start":{"line":11,"column":0},"end":{"line":11,"column":null}},"2":{"start":{"line":13,"column":40},"end":{"line":13,"column":70}},"3":{"start":{"line":17,"column":4},"end":{"line":17,"column":11}},"4":{"start":{"line":11,"column":13},"end":{"line":11,"column":30}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":12,"column":2},"end":{"line":12,"column":null}},"loc":{"start":{"line":13,"column":70},"end":{"line":14,"column":null}}},"1":{"name":"(anonymous_1)","decl":{"start":{"line":16,"column":2},"end":{"line":16,"column":9}},"loc":{"start":{"line":16,"column":9},"end":{"line":18,"column":null}}}},"branchMap":{},"s":{"0":4,"1":4,"2":0,"3":0,"4":4},"f":{"0":0,"1":0},"b":{}} ,"C:\\Users\\B7911\\Desktop\\vue-shopping-clean-architecture\\src\\usecases\\interactor\\getTotalCartItem.ts": {"path":"C:\\Users\\B7911\\Desktop\\vue-shopping-clean-architecture\\src\\usecases\\interactor\\getTotalCartItem.ts","statementMap":{"0":{"start":{"line":2,"column":0},"end":{"line":2,"column":null}},"1":{"start":{"line":10,"column":0},"end":{"line":10,"column":null}},"2":{"start":{"line":12,"column":38},"end":{"line":12,"column":68}},"3":{"start":{"line":17,"column":4},"end":{"line":17,"column":11}},"4":{"start":{"line":10,"column":13},"end":{"line":10,"column":33}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":11,"column":2},"end":{"line":11,"column":null}},"loc":{"start":{"line":12,"column":68},"end":{"line":14,"column":null}}},"1":{"name":"(anonymous_1)","decl":{"start":{"line":16,"column":2},"end":{"line":16,"column":9}},"loc":{"start":{"line":16,"column":9},"end":{"line":18,"column":null}}}},"branchMap":{},"s":{"0":4,"1":4,"2":0,"3":0,"4":4},"f":{"0":0,"1":0},"b":{}} } ================================================ FILE: coverage/lcov-report/base.css ================================================ body, html { margin:0; padding: 0; height: 100%; } body { font-family: Helvetica Neue, Helvetica, Arial; font-size: 14px; color:#333; } .small { font-size: 12px; } *, *:after, *:before { -webkit-box-sizing:border-box; -moz-box-sizing:border-box; box-sizing:border-box; } h1 { font-size: 20px; margin: 0;} h2 { font-size: 14px; } pre { font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; margin: 0; padding: 0; -moz-tab-size: 2; -o-tab-size: 2; tab-size: 2; } a { color:#0074D9; text-decoration:none; } a:hover { text-decoration:underline; } .strong { font-weight: bold; } .space-top1 { padding: 10px 0 0 0; } .pad2y { padding: 20px 0; } .pad1y { padding: 10px 0; } .pad2x { padding: 0 20px; } .pad2 { padding: 20px; } .pad1 { padding: 10px; } .space-left2 { padding-left:55px; } .space-right2 { padding-right:20px; } .center { text-align:center; } .clearfix { display:block; } .clearfix:after { content:''; display:block; height:0; clear:both; visibility:hidden; } .fl { float: left; } @media only screen and (max-width:640px) { .col3 { width:100%; max-width:100%; } .hide-mobile { display:none!important; } } .quiet { color: #7f7f7f; color: rgba(0,0,0,0.5); } .quiet a { opacity: 0.7; } .fraction { font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 10px; color: #555; background: #E8E8E8; padding: 4px 5px; border-radius: 3px; vertical-align: middle; } div.path a:link, div.path a:visited { color: #333; } table.coverage { border-collapse: collapse; margin: 10px 0 0 0; padding: 0; } table.coverage td { margin: 0; padding: 0; vertical-align: top; } table.coverage td.line-count { text-align: right; padding: 0 5px 0 20px; } table.coverage td.line-coverage { text-align: right; padding-right: 10px; min-width:20px; } table.coverage td span.cline-any { display: inline-block; padding: 0 5px; width: 100%; } .missing-if-branch { display: inline-block; margin-right: 5px; border-radius: 3px; position: relative; padding: 0 4px; background: #333; color: yellow; } .skip-if-branch { display: none; margin-right: 10px; position: relative; padding: 0 4px; background: #ccc; color: white; } .missing-if-branch .typ, .skip-if-branch .typ { color: inherit !important; } .coverage-summary { border-collapse: collapse; width: 100%; } .coverage-summary tr { border-bottom: 1px solid #bbb; } .keyline-all { border: 1px solid #ddd; } .coverage-summary td, .coverage-summary th { padding: 10px; } .coverage-summary tbody { border: 1px solid #bbb; } .coverage-summary td { border-right: 1px solid #bbb; } .coverage-summary td:last-child { border-right: none; } .coverage-summary th { text-align: left; font-weight: normal; white-space: nowrap; } .coverage-summary th.file { border-right: none !important; } .coverage-summary th.pct { } .coverage-summary th.pic, .coverage-summary th.abs, .coverage-summary td.pct, .coverage-summary td.abs { text-align: right; } .coverage-summary td.file { white-space: nowrap; } .coverage-summary td.pic { min-width: 120px !important; } .coverage-summary tfoot td { } .coverage-summary .sorter { height: 10px; width: 7px; display: inline-block; margin-left: 0.5em; background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; } .coverage-summary .sorted .sorter { background-position: 0 -20px; } .coverage-summary .sorted-desc .sorter { background-position: 0 -10px; } .status-line { height: 10px; } /* yellow */ .cbranch-no { background: yellow !important; color: #111; } /* dark red */ .red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } .low .chart { border:1px solid #C21F39 } .highlighted, .highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ background: #C21F39 !important; } /* medium red */ .cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } /* light red */ .low, .cline-no { background:#FCE1E5 } /* light green */ .high, .cline-yes { background:rgb(230,245,208) } /* medium green */ .cstat-yes { background:rgb(161,215,106) } /* dark green */ .status-line.high, .high .cover-fill { background:rgb(77,146,33) } .high .chart { border:1px solid rgb(77,146,33) } /* dark yellow (gold) */ .status-line.medium, .medium .cover-fill { background: #f9cd0b; } .medium .chart { border:1px solid #f9cd0b; } /* light yellow */ .medium { background: #fff4c2; } .cstat-skip { background: #ddd; color: #111; } .fstat-skip { background: #ddd; color: #111 !important; } .cbranch-skip { background: #ddd !important; color: #111; } span.cline-neutral { background: #eaeaea; } .coverage-summary td.empty { opacity: .5; padding-top: 4px; padding-bottom: 4px; line-height: 1; color: #888; } .cover-fill, .cover-empty { display:inline-block; height: 12px; } .chart { line-height: 0; } .cover-empty { background: white; } .cover-full { border-right: none !important; } pre.prettyprint { border: none !important; padding: 0 !important; margin: 0 !important; } .com { color: #999 !important; } .ignore-none { color: #999; font-weight: normal; } .wrapper { min-height: 100%; height: auto !important; height: 100%; margin: 0 auto -48px; } .footer, .push { height: 48px; } ================================================ FILE: coverage/lcov-report/block-navigation.js ================================================ /* eslint-disable */ var jumpToCode = (function init() { // Classes of code we would like to highlight in the file view var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; // Elements to highlight in the file listing view var fileListingElements = ['td.pct.low']; // We don't want to select elements that are direct descendants of another match var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` // Selecter that finds elements on the page to which we can jump var selector = fileListingElements.join(', ') + ', ' + notSelector + missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` // The NodeList of matching elements var missingCoverageElements = document.querySelectorAll(selector); var currentIndex; function toggleClass(index) { missingCoverageElements .item(currentIndex) .classList.remove('highlighted'); missingCoverageElements.item(index).classList.add('highlighted'); } function makeCurrent(index) { toggleClass(index); currentIndex = index; missingCoverageElements.item(index).scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' }); } function goToPrevious() { var nextIndex = 0; if (typeof currentIndex !== 'number' || currentIndex === 0) { nextIndex = missingCoverageElements.length - 1; } else if (missingCoverageElements.length > 1) { nextIndex = currentIndex - 1; } makeCurrent(nextIndex); } function goToNext() { var nextIndex = 0; if ( typeof currentIndex === 'number' && currentIndex < missingCoverageElements.length - 1 ) { nextIndex = currentIndex + 1; } makeCurrent(nextIndex); } return function jump(event) { switch (event.which) { case 78: // n case 74: // j goToNext(); break; case 66: // b case 75: // k case 80: // p goToPrevious(); break; } }; })(); window.addEventListener('keydown', jumpToCode); ================================================ FILE: coverage/lcov-report/index.html ================================================ Code coverage report for All files

All files

73.39% Statements 80/109
100% Branches 2/2
35.48% Functions 11/31
72.73% Lines 72/99

Press n or j to go to the next uncovered block, b, p or k for the previous block.

File Statements Branches Functions Lines
src
100% 15/15 100% 0/0 100% 0/0 100% 15/15
src/app/components
76.92% 10/13 100% 0/0 66.67% 4/6 76.92% 10/13
src/app/store
90% 36/40 100% 2/2 63.64% 7/11 91.67% 33/36
src/data/inMemoryRepository
38.46% 10/26 100% 0/0 0% 0/8 34.78% 8/23
src/usecases/interactor
60% 9/15 100% 0/0 0% 0/6 50% 6/12
================================================ FILE: coverage/lcov-report/prettify.css ================================================ .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} ================================================ FILE: coverage/lcov-report/prettify.js ================================================ /* eslint-disable */ window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); ================================================ FILE: coverage/lcov-report/sorter.js ================================================ /* eslint-disable */ var addSorting = (function() { 'use strict'; var cols, currentSort = { index: 0, desc: false }; // returns the summary table element function getTable() { return document.querySelector('.coverage-summary'); } // returns the thead element of the summary table function getTableHeader() { return getTable().querySelector('thead tr'); } // returns the tbody element of the summary table function getTableBody() { return getTable().querySelector('tbody'); } // returns the th element for nth column function getNthColumn(n) { return getTableHeader().querySelectorAll('th')[n]; } // loads all columns function loadColumns() { var colNodes = getTableHeader().querySelectorAll('th'), colNode, cols = [], col, i; for (i = 0; i < colNodes.length; i += 1) { colNode = colNodes[i]; col = { key: colNode.getAttribute('data-col'), sortable: !colNode.getAttribute('data-nosort'), type: colNode.getAttribute('data-type') || 'string' }; cols.push(col); if (col.sortable) { col.defaultDescSort = col.type === 'number'; colNode.innerHTML = colNode.innerHTML + ''; } } return cols; } // attaches a data attribute to every tr element with an object // of data values keyed by column name function loadRowData(tableRow) { var tableCols = tableRow.querySelectorAll('td'), colNode, col, data = {}, i, val; for (i = 0; i < tableCols.length; i += 1) { colNode = tableCols[i]; col = cols[i]; val = colNode.getAttribute('data-value'); if (col.type === 'number') { val = Number(val); } data[col.key] = val; } return data; } // loads all row data function loadData() { var rows = getTableBody().querySelectorAll('tr'), i; for (i = 0; i < rows.length; i += 1) { rows[i].data = loadRowData(rows[i]); } } // sorts the table using the data for the ith column function sortByIndex(index, desc) { var key = cols[index].key, sorter = function(a, b) { a = a.data[key]; b = b.data[key]; return a < b ? -1 : a > b ? 1 : 0; }, finalSorter = sorter, tableBody = document.querySelector('.coverage-summary tbody'), rowNodes = tableBody.querySelectorAll('tr'), rows = [], i; if (desc) { finalSorter = function(a, b) { return -1 * sorter(a, b); }; } for (i = 0; i < rowNodes.length; i += 1) { rows.push(rowNodes[i]); tableBody.removeChild(rowNodes[i]); } rows.sort(finalSorter); for (i = 0; i < rows.length; i += 1) { tableBody.appendChild(rows[i]); } } // removes sort indicators for current column being sorted function removeSortIndicators() { var col = getNthColumn(currentSort.index), cls = col.className; cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); col.className = cls; } // adds sort indicators for current column being sorted function addSortIndicators() { getNthColumn(currentSort.index).className += currentSort.desc ? ' sorted-desc' : ' sorted'; } // adds event listeners for all sorter widgets function enableUI() { var i, el, ithSorter = function ithSorter(i) { var col = cols[i]; return function() { var desc = col.defaultDescSort; if (currentSort.index === i) { desc = !currentSort.desc; } sortByIndex(i, desc); removeSortIndicators(); currentSort.index = i; currentSort.desc = desc; addSortIndicators(); }; }; for (i = 0; i < cols.length; i += 1) { if (cols[i].sortable) { // add the click event handler on the th so users // dont have to click on those tiny arrows el = getNthColumn(i).querySelector('.sorter').parentElement; if (el.addEventListener) { el.addEventListener('click', ithSorter(i)); } else { el.attachEvent('onclick', ithSorter(i)); } } } } // adds sorting functionality to the UI return function() { if (!getTable()) { return; } cols = loadColumns(); loadData(); addSortIndicators(); enableUI(); }; })(); window.addEventListener('load', addSorting); ================================================ FILE: coverage/lcov-report/src/app/components/Product.vue.html ================================================ Code coverage report for src/app/components/Product.vue

All files / src/app/components Product.vue

50% Statements 3/6
100% Branches 0/0
33.33% Functions 1/3
50% Lines 3/6

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45                                    2x 2x                   3x                              
<template>
  <v-card hover outlined>
    <v-card-text>
 
      <v-img height="200"
             :src="product.thumbnailUrl"/>
 
      <p class="mt-6 mb-0 title success--text">{{ product.name }}</p>
 
      <p class="pink--text body-1">${{ product.price.toLocaleString() }}</p>
      <p>{{ product.description }}</p>
 
      <v-btn block color="success" @click="addToCart" :loading="loading">Add to cart</v-btn>
 
    </v-card-text>
  </v-card>
</template>
<script lang="ts">
  import Vue from 'vue'
  import {cartStore} from "@/app/store";
 
  export default Vue.extend({
    props: {
      product: {
        type: Object
      }
    },
 
    data() {
      return {
        loading: false,
      }
    },
 
    methods: {
      addToCart() {
        this.loading = true;
        cartStore.addProductToCart({product: this.product, quantity: 1})
          .finally(() => {
            this.loading = false
          })
      }
    }
  })
</script>
================================================ FILE: coverage/lcov-report/src/app/components/ProductList.vue.html ================================================ Code coverage report for src/app/components/ProductList.vue

All files / src/app/components ProductList.vue

100% Statements 7/7
100% Branches 0/0
100% Functions 3/3
100% Lines 7/7

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38                    1x 1x 1x 1x                   1x       1x         1x          
<template>
  <div>
    <v-row>
      <v-col :key="idx" md="4" sm="6" v-for="(item ,idx) in products" xs="12">
        <Product class="my-5" :key="idx" :product="item"></Product>
      </v-col>
    </v-row>
  </div>
</template>
<script lang="ts">
import Vue from "vue";
import ProductComponent from "@/app/components/Product.vue";
import { ProductStore } from "@/app/store/product";
import { getModule } from "vuex-module-decorators";
import { Product } from "@/domain/entity";
 
export default Vue.extend({
  components: {
    Product: ProductComponent
  },
 
  computed: {
    productStore(): ProductStore {
      return getModule(ProductStore, this.$store);
    },
 
    products(): Product[] {
      return this.productStore.items;
    }
  },
 
  data() {
    return {
      loading: false
    };
  }
});
</script>
================================================ FILE: coverage/lcov-report/src/app/components/index.html ================================================ Code coverage report for src/app/components

All files src/app/components

76.92% Statements 10/13
100% Branches 0/0
66.67% Functions 4/6
76.92% Lines 10/13

Press n or j to go to the next uncovered block, b, p or k for the previous block.

File Statements Branches Functions Lines
Product.vue
50% 3/6 100% 0/0 33.33% 1/3 50% 3/6
ProductList.vue
100% 7/7 100% 0/0 100% 3/3 100% 7/7
================================================ FILE: coverage/lcov-report/src/app/store/cart.ts.html ================================================ Code coverage report for src/app/store/cart.ts

All files / src/app/store cart.ts

78.95% Statements 15/19
100% Branches 2/2
50% Functions 4/8
81.25% Lines 13/16

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 533x 3x                                 3x 3x 3x                           3x 4x 4x 1x   3x         3x 4x 4x            
import { lazyInject } from "@/di";
import { Action, Module, Mutation, VuexModule } from "vuex-module-decorators";
import { Cart, Product } from "@/domain/entity";
import { AddItemToCart } from "@/usecases/interactor/addItemToCart";
 
export interface CartState {
  items: Cart[];
}
 
export interface AddProductToCartPayload {
  product: Product;
  quantity: number;
}
 
@Module({
  name: "cart",
  namespaced: true
})
export class CartStore extends VuexModule implements CartState {
  public items: Cart[] = [];
  @lazyInject("AddItemToCart") private addItemToCart!: AddItemToCart;
 
  get totalCartItem(): number {
    return this.items.reduce((acc, cart) => acc + cart.quantity, 0);
  }
 
  get totalAmount(): number {
    return this.items.reduce(
      (acc, item) => acc + item.quantity * item.product.price,
      0
    );
  }
 
  @Mutation
  addItem(cart: Cart) {
    const idx = this.items.findIndex(c => c.product.id === cart.product.id);
    if (idx >= 0) {
      this.items[idx].quantity += cart.quantity;
    } else {
      this.items.push(cart);
    }
  }
 
  @Action
  async addProductToCart({ product, quantity }: AddProductToCartPayload) {
    await this.addItemToCart.execute(product, quantity).toPromise();
    this.addItem({
      product: product,
      quantity: quantity
    } as Cart);
  }
}
 
================================================ FILE: coverage/lcov-report/src/app/store/index.html ================================================ Code coverage report for src/app/store

All files src/app/store

90% Statements 36/40
100% Branches 2/2
63.64% Functions 7/11
91.67% Lines 33/36

Press n or j to go to the next uncovered block, b, p or k for the previous block.

File Statements Branches Functions Lines
cart.ts
78.95% 15/19 100% 2/2 50% 4/8 81.25% 13/16
index.ts
100% 10/10 100% 0/0 100% 0/0 100% 10/10
product.ts
100% 11/11 100% 0/0 100% 3/3 100% 10/10
================================================ FILE: coverage/lcov-report/src/app/store/index.ts.html ================================================ Code coverage report for src/app/store/index.ts

All files / src/app/store index.ts

100% Statements 10/10
100% Branches 0/0
100% Functions 0/0
100% Lines 10/10

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 252x 2x 2x 2x 2x   2x             2x             2x 2x   2x  
import Vue from "vue";
import Vuex from "vuex";
import {ProductState, ProductStore} from "./product";
import {CartState, CartStore} from "./cart";
import {getModule} from "vuex-module-decorators";
 
Vue.use(Vuex);
 
export interface RootState {
  product: ProductState;
  cart: CartState;
}
 
const store = new Vuex.Store<RootState>({
  modules: {
    product: ProductStore,
    cart: CartStore
  }
});
 
const productStore = getModule(ProductStore, store)
const cartStore = getModule(CartStore, store)
export {productStore, cartStore}
export default store;
 
================================================ FILE: coverage/lcov-report/src/app/store/product.ts.html ================================================ Code coverage report for src/app/store/product.ts

All files / src/app/store product.ts

100% Statements 11/11
100% Branches 0/0
100% Functions 3/3
100% Lines 10/10

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 313x   3x                       3x 3x   3x     3x 4x       3x 2x 2x      
import { lazyInject } from "@/di";
 
import { Action, Module, Mutation, VuexModule } from "vuex-module-decorators";
import { Product } from "@/domain/entity";
import { GetAllProduct } from "@/usecases/interactor/getAllProduct";
 
export interface ProductState {
  items: Product[];
}
 
@Module({
  name: "product",
  namespaced: true
})
export class ProductStore extends VuexModule implements ProductState {
  public items: Product[] = [];
 
  @lazyInject("GetAllProduct") private getAllProduct!: GetAllProduct;
 
  @Mutation
  setItems(items: Product[]) {
    this.items = items;
  }
 
  @Action
  async fetchItems() {
    const list = await this.getAllProduct.execute().toPromise();
    this.setItems(list);
  }
}
 
================================================ FILE: coverage/lcov-report/src/data/inMemoryRepository/cartRepository.ts.html ================================================ Code coverage report for src/data/inMemoryRepository/cartRepository.ts

All files / src/data/inMemoryRepository cartRepository.ts

41.67% Statements 5/12
100% Branches 0/0
0% Functions 0/5
36.36% Lines 4/11

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 284x 4x 4x       8x                                          
import {Observable, of} from "rxjs";
import {delay, map} from "rxjs/operators";
import {injectable} from "inversify";
import {Cart, Product} from "@/domain/entity";
 
@injectable()
export default class CartRepositoryImpl {
  private _carts: Cart[] = [];
 
  public addItemToCart(product: Product, quantity: number): Observable<void> {
    this._carts.push({product, quantity});
    return of(1).pipe(
      // delay(1000),
      map(() => {
        return;
      })
    );
  }
 
  public getTotalCartItem(): Observable<number> {
    const val = this._carts.length;
    return of(null).pipe(
      // delay(1000),
      map(() => val)
    );
  }
}
 
================================================ FILE: coverage/lcov-report/src/data/inMemoryRepository/index.html ================================================ Code coverage report for src/data/inMemoryRepository

All files src/data/inMemoryRepository

38.46% Statements 10/26
100% Branches 0/0
0% Functions 0/8
34.78% Lines 8/23

Press n or j to go to the next uncovered block, b, p or k for the previous block.

File Statements Branches Functions Lines
cartRepository.ts
41.67% 5/12 100% 0/0 0% 0/5 36.36% 4/11
productRepository.ts
35.71% 5/14 100% 0/0 0% 0/3 33.33% 4/12
================================================ FILE: coverage/lcov-report/src/data/inMemoryRepository/productRepository.ts.html ================================================ Code coverage report for src/data/inMemoryRepository/productRepository.ts

All files / src/data/inMemoryRepository productRepository.ts

35.71% Statements 5/14
100% Branches 0/0
0% Functions 0/3
33.33% Lines 4/12

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 434x   4x   4x     8x                                                                      
import {Observable, of} from "rxjs";
 
import {injectable} from "inversify";
import {Product} from "@/domain/entity";
import {map} from "rxjs/operators";
 
@injectable()
export default class ProductRepositoryImpl {
  readonly _products: Product[];
 
  constructor() {
    const _images = [
      "https://www.dropbox.com/s/swg9bdr0ejcbtrl/img1.jpg?raw=1",
      "https://www.dropbox.com/s/swg9bdr0ejcbtrl/img2.jpg?raw=1",
      "https://www.dropbox.com/s/78fot6w894stu3n/img3.jpg?raw=1",
      "https://www.dropbox.com/s/swg9bdr0ejcbtrl/img4.jpg?raw=1",
      "https://www.dropbox.com/s/d45c3pap1h4cu0y/img5.jpg?raw=1",
      "https://www.dropbox.com/s/rjj1vtdx79xptu0/img6.jpeg?raw=1",
      "https://www.dropbox.com/s/miym588nx2lscqt/img7.jpg?raw=1",
      "https://www.dropbox.com/s/miym588nx2lscqt/img8.jpg?raw=1",
      "https://www.dropbox.com/s/swg9bdr0ejcbtrl/img9.jpg?raw=1",
    ]
    console.log('hti')
    const products = []
    for (let i = 0; i < 20; i++) {
      products.push({
        id: `${i + 1}`,
        name: `Product ${i + 1}`,
        price: 2200,
        thumbnailUrl: _images[Math.floor(Math.random() * (_images.length - 1))],
        description: "3.0GHz Dual-core Haswell Intel Core i5 Turbo Boost up to 3.2 GHz, 3MB L3 cache 8GB (two 4GB SO-DIMMs..."
      })
    }
    this._products = products
  }
 
  public getAll(): Observable<Product[]> {
    return of(null).pipe(
      map(() => this._products)
    )
  }
}
 
================================================ FILE: coverage/lcov-report/src/di.ts.html ================================================ Code coverage report for src/di.ts

All files / src di.ts

100% Statements 15/15
100% Branches 0/0
100% Functions 0/0
100% Lines 15/15

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 384x 4x 4x     4x 4x 4x 4x 4x   4x   4x       4x         4x       4x       4x         4x    
import "reflect-metadata";
import {Container} from "inversify";
import getDecorators from "inversify-inject-decorators";
import CartRepository from "@/usecases/repository/cartRepository";
import ProductRepository from "@/usecases/repository/productRepository";
import CartRepositoryImpl from "@/data/inMemoryRepository/cartRepository";
import ProductRepositoryImpl from "@/data/inMemoryRepository/productRepository";
import {AddItemToCart, AddItemToCartImpl} from "@/usecases/interactor/addItemToCart";
import {GetAllProduct, GetAllProductImpl} from "@/usecases/interactor/getAllProduct";
import {GetTotalCartItem, GetTotalCartItemImpl} from "@/usecases/interactor/getTotalCartItem";
 
const container = new Container();
 
container
  .bind<CartRepository>("CartRepository")
  .to(CartRepositoryImpl)
  .inSingletonScope();
container
  .bind<ProductRepository>("ProductRepository")
  .to(ProductRepositoryImpl)
  .inSingletonScope();
 
container
  .bind<AddItemToCart>("AddItemToCart")
  .to(AddItemToCartImpl)
  .inSingletonScope();
container
  .bind<GetAllProduct>("GetAllProduct")
  .to(GetAllProductImpl)
  .inSingletonScope();
container
  .bind<GetTotalCartItem>("GetTotalCartItem")
  .to(GetTotalCartItemImpl)
  .inSingletonScope();
 
const {lazyInject} = getDecorators(container);
export {lazyInject, container};
 
================================================ FILE: coverage/lcov-report/src/index.html ================================================ Code coverage report for src

All files src

100% Statements 15/15
100% Branches 0/0
100% Functions 0/0
100% Lines 15/15

Press n or j to go to the next uncovered block, b, p or k for the previous block.

File Statements Branches Functions Lines
di.ts
100% 15/15 100% 0/0 100% 0/0 100% 15/15
================================================ FILE: coverage/lcov-report/src/usecases/interactor/addItemToCart.ts.html ================================================ Code coverage report for src/usecases/interactor/addItemToCart.ts

All files / src/usecases/interactor addItemToCart.ts

60% Statements 3/5
100% Branches 0/0
0% Functions 0/2
50% Lines 2/4

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21  4x                 4x                    
import {Observable} from "rxjs";
import {inject, injectable} from "inversify";
import {Product} from "@/domain/entity";
import CartRepository from "@/usecases/repository/cartRepository";
 
export interface AddItemToCart {
  execute(product: Product, quantity: number): Observable<void>;
}
 
@injectable()
export class AddItemToCartImpl implements AddItemToCart {
  constructor(
    @inject("CartRepository") private cartRepository: CartRepository
  ) {
  }
 
  execute(product: Product, quantity: number): Observable<void> {
    return this.cartRepository.addItemToCart(product, quantity);
  }
}
 
================================================ FILE: coverage/lcov-report/src/usecases/interactor/getAllProduct.ts.html ================================================ Code coverage report for src/usecases/interactor/getAllProduct.ts

All files / src/usecases/interactor getAllProduct.ts

60% Statements 3/5
100% Branches 0/0
0% Functions 0/2
50% Lines 2/4

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20    4x               4x                  
import { Product } from "@/domain/entity";
import { Observable, of } from "rxjs";
import { inject, injectable } from "inversify";
import ProductRepository from "@/usecases/repository/productRepository";
 
export interface GetAllProduct {
  execute(): Observable<Product[]>;
}
 
@injectable()
export class GetAllProductImpl implements GetAllProduct {
  constructor(
    @inject("ProductRepository") public productRepo: ProductRepository
  ) {}
 
  execute(): Observable<Product[]> {
    return this.productRepo.getAll();
  }
}
 
================================================ FILE: coverage/lcov-report/src/usecases/interactor/getTotalCartItem.ts.html ================================================ Code coverage report for src/usecases/interactor/getTotalCartItem.ts

All files / src/usecases/interactor getTotalCartItem.ts

60% Statements 3/5
100% Branches 0/0
0% Functions 0/2
50% Lines 2/4

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20  4x               4x                    
import {Observable} from "rxjs";
import {inject, injectable} from "inversify";
import CartRepository from "@/usecases/repository/cartRepository";
 
export interface GetTotalCartItem {
  execute(): Observable<number>;
}
 
@injectable()
export class GetTotalCartItemImpl implements GetTotalCartItem {
  constructor(
    @inject("CartRepository") private cartRepository: CartRepository
  ) {
  }
 
  execute(): Observable<number> {
    return this.cartRepository.getTotalCartItem()
  }
}
 
================================================ FILE: coverage/lcov-report/src/usecases/interactor/index.html ================================================ Code coverage report for src/usecases/interactor

All files src/usecases/interactor

60% Statements 9/15
100% Branches 0/0
0% Functions 0/6
50% Lines 6/12

Press n or j to go to the next uncovered block, b, p or k for the previous block.

File Statements Branches Functions Lines
addItemToCart.ts
60% 3/5 100% 0/0 0% 0/2 50% 2/4
getAllProduct.ts
60% 3/5 100% 0/0 0% 0/2 50% 2/4
getTotalCartItem.ts
60% 3/5 100% 0/0 0% 0/2 50% 2/4
================================================ FILE: coverage/lcov.info ================================================ TN: SF:C:\Users\B7911\Desktop\vue-shopping-clean-architecture\src\di.ts FNF:0 FNH:0 DA:1,4 DA:2,4 DA:3,4 DA:6,4 DA:7,4 DA:8,4 DA:9,4 DA:10,4 DA:12,4 DA:14,4 DA:18,4 DA:23,4 DA:27,4 DA:31,4 DA:36,4 LF:15 LH:15 BRF:0 BRH:0 end_of_record TN: SF:C:\Users\B7911\Desktop\vue-shopping-clean-architecture\src\app\components\Product.vue FN:29,(anonymous_1) FN:36,(anonymous_2) FN:38,(anonymous_3) FNF:3 FNH:1 FNDA:3,(anonymous_1) FNDA:0,(anonymous_2) FNDA:0,(anonymous_3) DA:19,2 DA:20,2 DA:30,3 DA:37,0 DA:38,0 DA:40,0 LF:6 LH:3 BRF:0 BRH:0 end_of_record TN: SF:C:\Users\B7911\Desktop\vue-shopping-clean-architecture\src\app\components\ProductList.vue FN:23,(anonymous_1) FN:27,(anonymous_2) FN:32,(anonymous_3) FNF:3 FNH:3 FNDA:1,(anonymous_1) FNDA:1,(anonymous_2) FNDA:1,(anonymous_3) DA:11,1 DA:12,1 DA:13,1 DA:14,1 DA:24,1 DA:28,1 DA:33,1 LF:7 LH:7 BRF:0 BRH:0 end_of_record TN: SF:C:\Users\B7911\Desktop\vue-shopping-clean-architecture\src\app\store\cart.ts FN:19,(anonymous_0) FN:23,(anonymous_1) FN:24,(anonymous_2) FN:27,(anonymous_3) FN:29,(anonymous_4) FN:35,(anonymous_5) FN:36,(anonymous_6) FN:45,(anonymous_7) FNF:8 FNH:4 FNDA:3,(anonymous_0) FNDA:0,(anonymous_1) FNDA:0,(anonymous_2) FNDA:0,(anonymous_3) FNDA:0,(anonymous_4) FNDA:4,(anonymous_5) FNDA:2,(anonymous_6) FNDA:4,(anonymous_7) DA:1,3 DA:2,3 DA:19,3 DA:20,3 DA:21,3 DA:24,0 DA:28,0 DA:29,0 DA:35,3 DA:36,4 DA:37,4 DA:38,1 DA:40,3 DA:45,3 DA:46,4 DA:47,4 LF:16 LH:13 BRDA:37,0,0,1 BRDA:37,0,1,3 BRF:2 BRH:2 end_of_record TN: SF:C:\Users\B7911\Desktop\vue-shopping-clean-architecture\src\app\store\index.ts FNF:0 FNH:0 DA:1,2 DA:2,2 DA:3,2 DA:4,2 DA:5,2 DA:7,2 DA:14,2 DA:21,2 DA:22,2 DA:24,2 LF:10 LH:10 BRF:0 BRH:0 end_of_record TN: SF:C:\Users\B7911\Desktop\vue-shopping-clean-architecture\src\app\store\product.ts FN:15,(anonymous_0) FN:21,(anonymous_1) FN:26,(anonymous_2) FNF:3 FNH:3 FNDA:3,(anonymous_0) FNDA:4,(anonymous_1) FNDA:2,(anonymous_2) DA:1,3 DA:3,3 DA:15,3 DA:16,3 DA:18,3 DA:21,3 DA:22,4 DA:26,3 DA:27,2 DA:28,2 LF:10 LH:10 BRF:0 BRH:0 end_of_record TN: SF:C:\Users\B7911\Desktop\vue-shopping-clean-architecture\src\data\inMemoryRepository\cartRepository.ts FN:7,(anonymous_0) FN:10,(anonymous_1) FN:14,(anonymous_2) FN:20,(anonymous_3) FN:24,(anonymous_4) FNF:5 FNH:0 FNDA:0,(anonymous_0) FNDA:0,(anonymous_1) FNDA:0,(anonymous_2) FNDA:0,(anonymous_3) FNDA:0,(anonymous_4) DA:1,4 DA:2,4 DA:3,4 DA:7,8 DA:8,0 DA:11,0 DA:12,0 DA:15,0 DA:21,0 DA:22,0 DA:24,0 LF:11 LH:4 BRF:0 BRH:0 end_of_record TN: SF:C:\Users\B7911\Desktop\vue-shopping-clean-architecture\src\data\inMemoryRepository\productRepository.ts FN:11,(anonymous_0) FN:37,(anonymous_1) FN:39,(anonymous_2) FNF:3 FNH:0 FNDA:0,(anonymous_0) FNDA:0,(anonymous_1) FNDA:0,(anonymous_2) DA:1,4 DA:3,4 DA:5,4 DA:8,8 DA:12,0 DA:23,0 DA:24,0 DA:25,0 DA:26,0 DA:34,0 DA:38,0 DA:39,0 LF:12 LH:4 BRF:0 BRH:0 end_of_record TN: SF:C:\Users\B7911\Desktop\vue-shopping-clean-architecture\src\usecases\interactor\addItemToCart.ts FN:12,(anonymous_0) FN:17,(anonymous_1) FNF:2 FNH:0 FNDA:0,(anonymous_0) FNDA:0,(anonymous_1) DA:2,4 DA:11,4 DA:13,0 DA:18,0 LF:4 LH:2 BRF:0 BRH:0 end_of_record TN: SF:C:\Users\B7911\Desktop\vue-shopping-clean-architecture\src\usecases\interactor\getAllProduct.ts FN:12,(anonymous_0) FN:16,(anonymous_1) FNF:2 FNH:0 FNDA:0,(anonymous_0) FNDA:0,(anonymous_1) DA:3,4 DA:11,4 DA:13,0 DA:17,0 LF:4 LH:2 BRF:0 BRH:0 end_of_record TN: SF:C:\Users\B7911\Desktop\vue-shopping-clean-architecture\src\usecases\interactor\getTotalCartItem.ts FN:11,(anonymous_0) FN:16,(anonymous_1) FNF:2 FNH:0 FNDA:0,(anonymous_0) FNDA:0,(anonymous_1) DA:2,4 DA:10,4 DA:12,0 DA:17,0 LF:4 LH:2 BRF:0 BRH:0 end_of_record ================================================ FILE: cypress.json ================================================ { "pluginsFile": "tests/e2e/plugins/index.js" } ================================================ FILE: jest.config.js ================================================ module.exports = { preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel' } ================================================ FILE: package.json ================================================ { "name": "vue-shopping-clean-architecture", "version": "0.1.0", "private": true, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "test:unit": "vue-cli-service test:unit", "test:e2e": "vue-cli-service test:e2e", "lint": "vue-cli-service lint" }, "dependencies": { "@types/lodash": "^4.14.149", "@types/sinon": "^7.5.2", "core-js": "^3.6.4", "inversify": "^5.0.1", "inversify-inject-decorators": "^3.1.0", "lodash": "^4.17.15", "reflect-metadata": "^0.1.13", "register-service-worker": "^1.6.2", "sinon": "^9.0.0", "vue": "^2.6.11", "vue-property-decorator": "^8.4.0", "vue-router": "^3.1.5", "vuetify": "^2.2.11", "vuex": "^3.1.2", "vuex-module-decorators": "^0.16.1" }, "devDependencies": { "@types/jest": "^24.0.19", "@typescript-eslint/eslint-plugin": "^2.18.0", "@typescript-eslint/parser": "^2.18.0", "@vue/cli-plugin-babel": "~4.2.0", "@vue/cli-plugin-e2e-cypress": "~4.2.0", "@vue/cli-plugin-eslint": "~4.2.0", "@vue/cli-plugin-pwa": "~4.2.0", "@vue/cli-plugin-router": "~4.2.0", "@vue/cli-plugin-typescript": "~4.2.0", "@vue/cli-plugin-unit-jest": "~4.2.0", "@vue/cli-plugin-vuex": "~4.2.0", "@vue/cli-service": "~4.2.0", "@vue/eslint-config-typescript": "^5.0.1", "@vue/test-utils": "1.0.0-beta.31", "eslint": "^6.7.2", "eslint-plugin-vue": "^6.1.2", "sass": "^1.19.0", "sass-loader": "^8.0.0", "typescript": "~3.7.5", "vue-cli-plugin-vuetify": "~2.0.5", "vue-template-compiler": "^2.6.11", "vuetify-loader": "^1.3.0" } } ================================================ FILE: public/index.html ================================================ <%= htmlWebpackPlugin.options.title %>
================================================ FILE: public/robots.txt ================================================ User-agent: * Disallow: ================================================ FILE: src/app/App.vue ================================================ ================================================ FILE: src/app/assets/app.css ================================================ * { font-family: '-apple-system', 'HelveticaNeue', sans-serif; } ================================================ FILE: src/app/components/CartPreview.vue ================================================ ================================================ FILE: src/app/components/NavBar.vue ================================================ ================================================ FILE: src/app/components/Product.vue ================================================ ================================================ FILE: src/app/components/ProductList.vue ================================================ ================================================ FILE: src/app/main.ts ================================================ import Vue from 'vue' import App from './App.vue' import './registerServiceWorker' import router from './router' import store from './store' import vuetify from './plugins/vuetify'; import "./assets/app.css" Vue.config.productionTip = false new Vue({ router, store, // @ts-ignore vuetify, render: h => h(App) }).$mount('#app') ================================================ FILE: src/app/plugins/vuetify.ts ================================================ import Vue from 'vue'; import Vuetify from 'vuetify/lib'; import 'vuetify/dist/vuetify.min.css' Vue.use(Vuetify); export default new Vuetify({}); ================================================ FILE: src/app/registerServiceWorker.ts ================================================ /* eslint-disable no-console */ import {register} from 'register-service-worker' if (process.env.NODE_ENV === 'production') { register(`${process.env.BASE_URL}service-worker.js`, { ready() { console.log( 'App is being served from cache by a service worker.\n' + 'For more details, visit https://goo.gl/AFskqB' ) }, registered() { console.log('Service worker has been registered.') }, cached() { console.log('Content has been cached for offline use.') }, updatefound() { console.log('New content is downloading.') }, updated() { console.log('New content is available; please refresh.') }, offline() { console.log('No internet connection found. App is running in offline mode.') }, error(error) { console.error('Error during service worker registration:', error) } }) } ================================================ FILE: src/app/router/index.ts ================================================ import Vue from 'vue' import VueRouter from 'vue-router' import Home from '../views/Home.vue' import Checkout from "@/app/views/Checkout.vue"; Vue.use(VueRouter) const routes = [ { path: '/', name: 'Home', component: Home }, { path: '/checkout', name: 'Checkout', component: Checkout }, { path: '/about', name: 'About', // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import(/* webpackChunkName: "about" */ '../views/About.vue') } ] const router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes }) export default router ================================================ FILE: src/app/shims-tsx.d.ts ================================================ import Vue, {VNode} from 'vue' declare global { namespace JSX { // tslint:disable no-empty-interface interface Element extends VNode { } // tslint:disable no-empty-interface interface ElementClass extends Vue { } interface IntrinsicElements { [elem: string]: any; } } } ================================================ FILE: src/app/shims-vue.d.ts ================================================ declare module '*.vue' { import Vue from 'vue' export default Vue } declare module 'vuetify/lib' ================================================ FILE: src/app/store/cart.ts ================================================ import {lazyInject} from "@/di"; import {Action, Module, Mutation, VuexModule} from "vuex-module-decorators"; import {Cart, Product} from "@/domain/entity"; import {AddItemToCart} from "@/usecases/interactor/addItemToCart"; import {ProceedCheckout} from "@/usecases/interactor/proceedCheckout"; export interface CartState { items: Cart[]; } export interface AddProductToCartPayload { product: Product; quantity: number; } @Module({ name: "cart", namespaced: true }) export class CartStore extends VuexModule implements CartState { public items: Cart[] = []; @lazyInject("AddItemToCart") private addItemToCart!: AddItemToCart; @lazyInject("ProceedCheckout") private proceedCheckout!: ProceedCheckout; get totalCartItem(): number { return this.items.reduce((acc, cart) => acc + cart.quantity, 0); } get totalAmount(): number { return this.items.reduce( (acc, item) => acc + item.quantity * item.product.price, 0 ); } @Mutation clearCart() { this.items = [] } @Mutation addItem(cart: Cart) { const idx = this.items.findIndex(c => c.product.id === cart.product.id); if (idx >= 0) { this.items[idx].quantity += cart.quantity; } else { this.items.push(cart); } } @Action async addProductToCart({product, quantity}: AddProductToCartPayload) { await this.addItemToCart.execute(product, quantity).toPromise(); this.addItem({ product: product, quantity: quantity } as Cart); } @Action async checkout() { await this.proceedCheckout.execute().toPromise(); this.clearCart() } } ================================================ FILE: src/app/store/index.ts ================================================ import Vue from "vue"; import Vuex from "vuex"; import {ProductState, ProductStore} from "./product"; import {CartState, CartStore} from "./cart"; import {getModule} from "vuex-module-decorators"; Vue.use(Vuex); export interface RootState { product: ProductState; cart: CartState; } const store = new Vuex.Store({ modules: { product: ProductStore, cart: CartStore } }); export default store; ================================================ FILE: src/app/store/product.ts ================================================ import { lazyInject } from "@/di"; import { Action, Module, Mutation, VuexModule } from "vuex-module-decorators"; import { Product } from "@/domain/entity"; import { GetAllProduct } from "@/usecases/interactor/getAllProduct"; export interface ProductState { items: Product[]; } @Module({ name: "product", namespaced: true }) export class ProductStore extends VuexModule implements ProductState { public items: Product[] = []; @lazyInject("GetAllProduct") private getAllProduct!: GetAllProduct; @Mutation setItems(items: Product[]) { this.items = items; } @Action async fetchItems() { const list = await this.getAllProduct.execute().toPromise(); this.setItems(list); } } ================================================ FILE: src/app/views/About.vue ================================================ ================================================ FILE: src/app/views/Checkout.vue ================================================ ================================================ FILE: src/app/views/Home.vue ================================================ ================================================ FILE: src/data/inMemoryRepository/cartRepository.ts ================================================ import {Observable, of} from "rxjs"; import {tap, map} from "rxjs/operators"; import {injectable} from "inversify"; import {Cart, Product} from "@/domain/entity"; import CartRepository from "@/usecases/repository/cartRepository"; @injectable() export default class CartRepositoryImpl implements CartRepository { private _carts: Cart[] = []; public addItemToCart(product: Product, quantity: number): Observable { this._carts.push({product, quantity}); return of(1).pipe( // delay(1000), map(() => { return; }) ); } public getTotalCartItem(): Observable { const val = this._carts.length; return of(null).pipe( // delay(1000), map(() => val) ); } proceedCheckout(): Observable { return of(null).pipe( tap(() => { this._carts = [] }), map(() => { return }) ); } } ================================================ FILE: src/data/inMemoryRepository/productRepository.ts ================================================ import {Observable, of} from "rxjs"; import {injectable} from "inversify"; import {Product} from "@/domain/entity"; import {map} from "rxjs/operators"; @injectable() export default class ProductRepositoryImpl { readonly _products: Product[]; constructor() { const _images = [ "https://www.dropbox.com/s/swg9bdr0ejcbtrl/img1.jpg?raw=1", "https://www.dropbox.com/s/swg9bdr0ejcbtrl/img2.jpg?raw=1", "https://www.dropbox.com/s/78fot6w894stu3n/img3.jpg?raw=1", "https://www.dropbox.com/s/swg9bdr0ejcbtrl/img4.jpg?raw=1", "https://www.dropbox.com/s/d45c3pap1h4cu0y/img5.jpg?raw=1", "https://www.dropbox.com/s/rjj1vtdx79xptu0/img6.jpeg?raw=1", "https://www.dropbox.com/s/miym588nx2lscqt/img7.jpg?raw=1", "https://www.dropbox.com/s/miym588nx2lscqt/img8.jpg?raw=1", "https://www.dropbox.com/s/swg9bdr0ejcbtrl/img9.jpg?raw=1", ] const products = [] for (let i = 0; i < 20; i++) { products.push({ id: `${i + 1}`, name: `Product ${i + 1}`, price: 2200, thumbnailUrl: _images[Math.floor(Math.random() * (_images.length - 1))], description: "3.0GHz Dual-core Haswell Intel Core i5 Turbo Boost up to 3.2 GHz, 3MB L3 cache 8GB (two 4GB SO-DIMMs..." }) } this._products = products } public getAll(): Observable { return of(null).pipe( map(() => this._products) ) } } ================================================ FILE: src/di.ts ================================================ import "reflect-metadata"; import {Container} from "inversify"; import getDecorators from "inversify-inject-decorators"; import CartRepository from "@/usecases/repository/cartRepository"; import ProductRepository from "@/usecases/repository/productRepository"; import CartRepositoryImpl from "@/data/inMemoryRepository/cartRepository"; import ProductRepositoryImpl from "@/data/inMemoryRepository/productRepository"; import {AddItemToCart, AddItemToCartImpl} from "@/usecases/interactor/addItemToCart"; import {GetAllProduct, GetAllProductImpl} from "@/usecases/interactor/getAllProduct"; import {GetTotalCartItem, GetTotalCartItemImpl} from "@/usecases/interactor/getTotalCartItem"; import {ProceedCheckout, ProceedCheckoutImpl} from "@/usecases/interactor/proceedCheckout"; import {ProductStore} from "@/app/store/product"; import {getModule} from "vuex-module-decorators"; const container = new Container(); container .bind("CartRepository") .to(CartRepositoryImpl) .inSingletonScope(); container .bind("ProductRepository") .to(ProductRepositoryImpl) .inSingletonScope(); container .bind("AddItemToCart") .to(AddItemToCartImpl) .inSingletonScope(); container .bind("GetAllProduct") .to(GetAllProductImpl) .inSingletonScope(); container .bind("GetTotalCartItem") .to(GetTotalCartItemImpl) .inSingletonScope(); container .bind("ProceedCheckout") .to(ProceedCheckoutImpl) .inSingletonScope(); const {lazyInject} = getDecorators(container); export {lazyInject, container}; ================================================ FILE: src/domain/entity/index.ts ================================================ export interface Product { id?: string; name: string; price: number; description?: string; thumbnailUrl?: string; } export interface Cart { product: Product; quantity: number; } ================================================ FILE: src/main.ts ================================================ import "./app/main" ================================================ FILE: src/usecases/interactor/addItemToCart.ts ================================================ import {Observable} from "rxjs"; import {inject, injectable} from "inversify"; import {Product} from "@/domain/entity"; import CartRepository from "@/usecases/repository/cartRepository"; export interface AddItemToCart { execute(product: Product, quantity: number): Observable; } @injectable() export class AddItemToCartImpl implements AddItemToCart { constructor( @inject("CartRepository") private cartRepository: CartRepository ) { } execute(product: Product, quantity: number): Observable { return this.cartRepository.addItemToCart(product, quantity); } } ================================================ FILE: src/usecases/interactor/getAllProduct.ts ================================================ import { Product } from "@/domain/entity"; import { Observable, of } from "rxjs"; import { inject, injectable } from "inversify"; import ProductRepository from "@/usecases/repository/productRepository"; export interface GetAllProduct { execute(): Observable; } @injectable() export class GetAllProductImpl implements GetAllProduct { constructor( @inject("ProductRepository") public productRepo: ProductRepository ) {} execute(): Observable { return this.productRepo.getAll(); } } ================================================ FILE: src/usecases/interactor/getTotalCartItem.ts ================================================ import {Observable} from "rxjs"; import {inject, injectable} from "inversify"; import CartRepository from "@/usecases/repository/cartRepository"; export interface GetTotalCartItem { execute(): Observable; } @injectable() export class GetTotalCartItemImpl implements GetTotalCartItem { constructor( @inject("CartRepository") private cartRepository: CartRepository ) { } execute(): Observable { return this.cartRepository.getTotalCartItem() } } ================================================ FILE: src/usecases/interactor/proceedCheckout.ts ================================================ import {Observable} from "rxjs"; import {inject, injectable} from "inversify"; import CartRepository from "@/usecases/repository/cartRepository"; export interface ProceedCheckout { execute(): Observable; } @injectable() export class ProceedCheckoutImpl implements ProceedCheckout { constructor( @inject("CartRepository") private cartRepository: CartRepository ) { } execute(): Observable { return this.cartRepository.proceedCheckout(); } } ================================================ FILE: src/usecases/repository/cartRepository.ts ================================================ import {Observable} from "rxjs"; import {Product} from "@/domain/entity"; export default interface CartRepository { addItemToCart(product: Product, quantity: number): Observable; getTotalCartItem(): Observable; proceedCheckout(): Observable; } ================================================ FILE: src/usecases/repository/productRepository.ts ================================================ import {Product} from "@/domain/entity"; import {Observable} from "rxjs"; export default interface ProductRepository { getAll(): Observable; } ================================================ FILE: tests/e2e/.eslintrc.js ================================================ module.exports = { plugins: [ 'cypress' ], env: { mocha: true, 'cypress/globals': true }, rules: { strict: 'off' } } ================================================ FILE: tests/e2e/plugins/index.js ================================================ /* eslint-disable arrow-body-style */ // https://docs.cypress.io/guides/guides/plugins-guide.html // if you need a custom webpack configuration you can uncomment the following import // and then use the `file:preprocessor` event // as explained in the cypress docs // https://docs.cypress.io/api/plugins/preprocessors-api.html#Examples // /* eslint-disable import/no-extraneous-dependencies, global-require */ // const webpack = require('@cypress/webpack-preprocessor') module.exports = (on, config) => { // on('file:preprocessor', webpack({ // webpackOptions: require('@vue/cli-service/webpack.config'), // watchOptions: {} // })) return Object.assign({}, config, { fixturesFolder: 'tests/e2e/fixtures', integrationFolder: 'tests/e2e/specs', screenshotsFolder: 'tests/e2e/screenshots', videosFolder: 'tests/e2e/videos', supportFile: 'tests/e2e/support/index.js' }) } ================================================ FILE: tests/e2e/specs/test.js ================================================ // https://docs.cypress.io/api/introduction/api.html describe('My First Test', () => { it('Visits the app root url', () => { cy.visit('/') cy.contains('h1', 'Welcome to Your Vue.js + TypeScript App') }) }) ================================================ FILE: tests/e2e/support/commands.js ================================================ // *********************************************** // This example commands.js shows you how to // create various custom commands and overwrite // existing commands. // // For more comprehensive examples of custom // commands please read more here: // https://on.cypress.io/custom-commands // *********************************************** // // // -- This is a parent command -- // Cypress.Commands.add("login", (email, password) => { ... }) // // // -- This is a child command -- // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) // // // -- This is a dual command -- // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) // // // -- This is will overwrite an existing command -- // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) ================================================ FILE: tests/e2e/support/index.js ================================================ // *********************************************************** // This example support/index.js is processed and // loaded automatically before your test files. // // This is a great place to put global configuration and // behavior that modifies Cypress. // // You can change the location of this file or turn off // automatically serving support files with the // 'supportFile' configuration option. // // You can read more here: // https://on.cypress.io/configuration // *********************************************************** // Import commands.js using ES2015 syntax: import './commands' // Alternatively you can use CommonJS syntax: // require('./commands') ================================================ FILE: tests/unit/components/Product.spec.ts ================================================ import ProductComponent from "@/app/components/Product.vue"; import Vue from 'vue' import { shallowMount, mount, createLocalVue, Wrapper } from "@vue/test-utils"; import { Product } from "@/domain/entity"; import vuetify from "vuetify"; const product: Product = { id: "1", name: "test 1", price: 1000, thumbnailUrl: "" } as Product; describe("Product.vue", () => { beforeEach(() => {}); it("should contains product info", () => { Vue.use(vuetify); const wrapper = mount(ProductComponent, { propsData: { product } }); expect(wrapper.text()).toContain(product.name); }); }); ================================================ FILE: tests/unit/components/ProductList.spec.ts ================================================ import ProductComponent from '@/app/components/Product.vue'; import ProductList from "@/app/components/ProductList.vue"; import Vue from "vue"; import Vuex from "vuex"; import * as _ from "lodash"; import { shallowMount, mount, createLocalVue, Wrapper } from "@vue/test-utils"; import { Product } from "@/domain/entity"; import vuetify from "vuetify"; import { CartStore } from "@/app/store/cart"; import { getModule } from "vuex-module-decorators"; import { ProductStore } from "@/app/store/product"; const products: Product[] = [ { id: "1", name: "test 1", price: 1000, thumbnailUrl: "" }, { id: "1", name: "test 1", price: 1000, thumbnailUrl: "" } ]; describe("ProductList.vue", () => { let productStore: ProductStore; beforeEach(() => {}); it("should render Product.vue", async () => { Vue.use(vuetify); Vue.use(Vuex); const store = new Vuex.Store({ modules: { product: _.cloneDeep(ProductStore) } }); productStore = getModule(ProductStore, store); productStore.setItems(products); // execute const wrapper = mount(ProductList, { store }); await Vue.nextTick(); // assert console.log(wrapper.findAll('Product')) expect(wrapper.findAll(ProductComponent).length).toEqual(products.length); }); }); ================================================ FILE: tests/unit/store/cart.spec.ts ================================================ import Vuex from "vuex"; import { createLocalVue } from "@vue/test-utils"; import { getModule } from "vuex-module-decorators"; import { container } from "@/di"; import { from, of } from "rxjs"; import sinon from "sinon"; import { injectable } from "inversify"; import { Product } from "@/domain/entity"; import { CartStore } from "@/app/store/cart"; import { AddItemToCart } from "@/usecases/interactor/addItemToCart"; import * as _ from "lodash"; container.unbindAll(); @injectable() class MockAddItemToCart implements AddItemToCart { execute(product: Product, quantity: number): import("rxjs").Observable { throw new Error("Method not implemented."); } } container .bind("AddItemToCart") .to(MockAddItemToCart) .inSingletonScope(); const mockUseCase = container.get("AddItemToCart"); const sandbox = sinon.createSandbox(); describe("CartStore", () => { describe("[Actions]", () => { describe("addProductToCart()", () => { let module: CartStore; beforeEach(() => { const localVue = createLocalVue(); localVue.use(Vuex); const store = new Vuex.Store({ modules: { cart: _.cloneDeep(CartStore) } }); module = getModule(CartStore, store); }); afterEach(() => { sandbox.restore(); }); it("should execute usecase: AddItemToCart", async () => { // setup const spy = sandbox.stub(mockUseCase, "execute"); spy.callsFake(() => of()); // execute const product: Product = { name: "test", price: 999 }; const params = { product: product, quantity: 1 }; await module.addProductToCart(params); // assert expect(spy.calledOnce).toBe(true); }); it("should update state", async () => { const spy = sandbox.stub(mockUseCase, "execute"); spy.callsFake(() => of()); // execute const product1: Product = { id: "1", name: "test 1", price: 999 }; const product2: Product = { id: "2", name: "test 2", price: 999 }; await module.addProductToCart({ product: product1, quantity: 1 }); // assert expect(module.items.length).toEqual(1); // add the same product to cart, the quantity is increased but the number of item is the same await module.addProductToCart({ product: product1, quantity: 1 }); expect(module.items.length).toEqual(1); // add another product, the item should be increased await module.addProductToCart({ product: product2, quantity: 1 }); expect(module.items.length).toEqual(2); }); }); }); }); ================================================ FILE: tests/unit/store/product.spec.ts ================================================ import { ProductStore } from "./../../../src/app/store/product"; import Vuex from "vuex"; import { createLocalVue } from "@vue/test-utils"; import { getModule } from "vuex-module-decorators"; import { container } from "@/di"; import sinon from "sinon"; import { GetAllProduct } from "@/usecases/interactor/getAllProduct"; import { injectable } from "inversify"; import { of } from "rxjs"; import { Product } from "@/domain/entity"; container.unbindAll(); @injectable() class MockGetAllProduct implements GetAllProduct { execute(): import("rxjs").Observable< import("../../../src/domain/entity").Product[] > { throw new Error("Method not implemented."); } } container .bind("GetAllProduct") .to(MockGetAllProduct) .inSingletonScope(); const mockUseCase = container.get("GetAllProduct"); const sandbox = sinon.createSandbox(); describe("ProductStore", () => { describe("[Actions]", () => { describe("fetchAll()", () => { beforeEach(() => {}); afterEach(() => { sandbox.restore(); }); it("should execute usecase: GetAllProduct", async () => { // setup const localVue = createLocalVue(); localVue.use(Vuex); const store = new Vuex.Store({ modules: { product: ProductStore } }); const module = getModule(ProductStore, store); const spy = sandbox.stub(mockUseCase, "execute"); spy.callsFake(() => of([])); // execute await module.fetchItems(); // assert expect(spy.calledOnce).toBe(true); }); it("should call mutation: setItems", async () => { // setup const localVue = createLocalVue(); localVue.use(Vuex); const store = new Vuex.Store({ modules: { product: ProductStore } }); const module = getModule(ProductStore, store); const spy = sandbox.stub(mockUseCase, "execute"); spy.callsFake(() => of([])); const mutation = sandbox.spy(module, "setItems"); // execute await module.fetchItems(); // assert expect(mutation.calledOnce).toBe(true); }); }); }); describe("[Mutations]", () => { describe("setItems()", () => { beforeEach(() => {}); afterEach(() => { sandbox.restore(); }); it("should update list item state", () => { // setup const localVue = createLocalVue(); localVue.use(Vuex); const store = new Vuex.Store({ modules: { product: ProductStore } }); const module = getModule(ProductStore, store); // execute const products: Product[] = []; module.setItems(products); // assert expect(module.items).toEqual(products); }); }); }); }); ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "experimentalDecorators": true, "target": "esnext", "module": "esnext", "strict": true, "jsx": "preserve", "noUnusedLocals": false, "noUnusedParameters": false, "importHelpers": true, "moduleResolution": "node", "esModuleInterop": true, "allowSyntheticDefaultImports": true, "sourceMap": true, "baseUrl": ".", "types": [ "webpack-env", "jest" ], "paths": { "@/*": [ "src/*" ] }, "lib": [ "esnext", "dom", "dom.iterable", "scripthost" ] }, "include": [ "src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "tests/**/*.ts", "tests/**/*.tsx" ], "exclude": [ "node_modules" ] } ================================================ FILE: vue.config.js ================================================ module.exports = { "publicPath": process.env.NODE_ENV === "production" ? "/vue-shopping" : "/", "transpileDependencies": [ "vuetify" ] }