├── .editorconfig ├── .gitignore ├── .prettierrc.js ├── LICENSE ├── README.md ├── assets └── logo.png ├── common ├── api.ts ├── cache.ts └── utils.ts ├── components ├── comment.vue ├── item-list-nav.vue ├── item.vue ├── lazy-wrapper.vue └── spinner.vue ├── layouts └── default.vue ├── nuxt.config.ts ├── package.json ├── pages ├── _feed │ └── _page.vue ├── index.tsx ├── item │ └── _id.vue └── user │ └── _id.vue ├── plugins └── filters.ts ├── static ├── favicon.ico └── icon.png ├── store └── index.ts ├── tests ├── __snapshots__ │ ├── comment.spec.ts.snap │ └── spinner.spec.ts.snap ├── comment.spec.ts ├── spinner.spec.ts ├── sum.spec.ts ├── sum.ts └── vue-shims.d.ts ├── tsconfig.json ├── tslint.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_size = 2 6 | indent_style = space 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | yarn-error.log 6 | .idea 7 | *.iml 8 | .nuxt 9 | .vscode 10 | 11 | static/manifest*.json 12 | static/sw.js 13 | static/workbox-sw*.js* 14 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "semi": false 3 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-present, Yuxi (Evan) You 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nuxt Hacker News TS 2 | 3 | HackerNews clone built with Nuxt.js and TypeScript showcasing best practices of developing real life modern isomorphic Web Apps with [Nuxt](https://github.com/nuxt/nuxt.js). It features integrations with [TsLint](https://palantir.github.io/tslint/) (linting), [Prettier](https://prettier.io/) (code formatting), [Jest](https://jestjs.io/) (testing), [Axios](https://github.com/nuxt-community/axios-module) (http calls on steroids), [Storybook](https://storybook.js.org/)* (component playground). 4 | 5 |

6 | 7 |
9 | Live Demo 10 |
11 |

12 | 13 | ## Performance 14 | 15 | Coming soon 16 | 17 | ## Features 18 | 19 | - Server Side Rendering & Caching 20 | - Code Splitting 21 | - Single-file Vue Components 22 | - Real-time List Updates with FLIP Animation 23 | - Prefetch/Preload JS + DNS + Data 24 | - Critical Path CSS 25 | - PWA experience using [PWA Module](https://github.com/nuxt-community/pwa-module) with almost _zero config_ 26 | - PRPL 27 | - Hot reloading dev environment 28 | - [TSLint](https://palantir.github.io/tslint/) and [Prettier](https://prettier.io/) integration 29 | - Typescript 3 30 | - Storybook Integration (Coming Soon) 31 | - Snapshot and Unit Tests with Jest and [Vue-Test-Utils](https://vue-test-utils.vuejs.org/) (Coming Soon) 32 | 33 | ## Build Setup 34 | 35 | **Requires Node.js 6+** 36 | 37 | ```bash 38 | # install dependencies 39 | npm install # or yarn 40 | 41 | # serve in dev mode, with hot reload at localhost:3000 42 | npm run dev 43 | 44 | # build for production 45 | npm run build 46 | 47 | # serve in production mode 48 | npm start 49 | 50 | # run unit tests 51 | npm run test 52 | 53 | # validate code with TSLint (with Prettier) 54 | npm run lint 55 | 56 | # validate and fix with TSLint (with Prettier) 57 | npm run lintfix 58 | 59 | ``` 60 | 61 | ## Links 62 | For Nuxt JS version go [here](https://github.com/nuxt/hackernews) 63 | 64 | 65 | This repository is originally ported from [vue-hackernews-2.0](https://github.com/vuejs/vue-hackernews-2.0) and [HackerNews Nuxt](https://github.com/nuxt/hackernews) 66 | 67 | ## License 68 | 69 | MIT 70 | 71 | -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuxt-community/hackernews-nuxt-ts/b409a53289372152b8e2becb048596c77d12b49f/assets/logo.png -------------------------------------------------------------------------------- /common/api.ts: -------------------------------------------------------------------------------- 1 | export const feeds = { 2 | news: { title: "News", pages: 10 }, 3 | newest: { title: "Newest", pages: 12 }, 4 | ask: { title: "Ask", pages: 2 }, 5 | show: { title: "Show", pages: 2 }, 6 | jobs: { title: "Jobs", pages: 1 } 7 | } 8 | 9 | export const validFeeds = Object.keys(feeds) 10 | -------------------------------------------------------------------------------- /common/cache.ts: -------------------------------------------------------------------------------- 1 | import express from "express" 2 | import apicache from "apicache" 3 | 4 | const app = express() 5 | 6 | // https://github.com/kwhitley/apicache 7 | app.use(apicache.middleware("15 minutes")) 8 | 9 | // apicache.options({ debug: true }) 10 | 11 | export default { 12 | path: "/api/", 13 | handler: app 14 | } 15 | -------------------------------------------------------------------------------- /common/utils.ts: -------------------------------------------------------------------------------- 1 | export const lazy = (commit, task, optimistic, enabled = false) => { 2 | // By default, do lazy operations only in client 3 | if (enabled === undefined) { 4 | // hack `(process as any)` to wait till this is fixed in Nuxt 5 | enabled = (process as any).client 6 | } 7 | 8 | // Non lazy mode 9 | if (!enabled) { 10 | return task().then(commit) 11 | } 12 | 13 | // Do real task in background 14 | Promise.resolve(task(optimistic)) 15 | .then(commit) 16 | .catch(console.error) 17 | 18 | // Commit optimistic value and resolve 19 | return Promise.resolve(commit(optimistic)) 20 | } 21 | -------------------------------------------------------------------------------- /components/comment.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 38 | 39 | 94 | -------------------------------------------------------------------------------- /components/item-list-nav.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 30 | 31 | 51 | -------------------------------------------------------------------------------- /components/item.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 43 | 44 | 79 | -------------------------------------------------------------------------------- /components/lazy-wrapper.vue: -------------------------------------------------------------------------------- 1 | 2 | 22 | -------------------------------------------------------------------------------- /components/spinner.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 37 | 38 | 94 | -------------------------------------------------------------------------------- /layouts/default.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 47 | 48 | 155 | -------------------------------------------------------------------------------- /nuxt.config.ts: -------------------------------------------------------------------------------- 1 | const isDev = process.env.NODE_ENV !== "production" 2 | 3 | export default { 4 | mode: "spa", 5 | modern: !isDev, 6 | head: { 7 | titleTemplate: "Nuxt HN TS | %s", 8 | meta: [ 9 | { 10 | property: "og:image", 11 | content: 12 | "https://user-images.githubusercontent.com/904724/26879447-689b56a8-4b91-11e7-968f-5eea1d6c71b4.png" 13 | }, 14 | { 15 | property: "twitter:card", 16 | content: "summary_large_image" 17 | }, 18 | { 19 | property: "twitter:site", 20 | content: "@nuxt_js" 21 | } 22 | ], 23 | link: [ 24 | { 25 | rel: "icon", 26 | type: "image/x-icon", 27 | href: "/favicon.ico" 28 | } 29 | ] 30 | }, 31 | loading: { 32 | color: "#59cc93" 33 | }, 34 | manifest: { 35 | name: "Nuxt Hacker News Typescript", 36 | short_name: "Nuxt HN TS", 37 | description: "HackerNews clone built with Nuxt.js & Typescript", 38 | theme_color: "#188269" 39 | }, 40 | modules: ["@nuxtjs/pwa", "@nuxtjs/component-cache", "@nuxtjs/axios"], 41 | axios: { 42 | debug: isDev, 43 | proxy: true 44 | }, 45 | proxy: { 46 | "/api": { 47 | target: "https://api.hnpwa.com/v0/", 48 | pathRewrite: { 49 | "^/api/": "" 50 | } 51 | } 52 | }, 53 | plugins: ["~/plugins/filters"], 54 | serverMiddleware: ["~/common/cache"], 55 | render: { 56 | http2: { 57 | push: true 58 | }, 59 | static: { 60 | maxAge: "1y", 61 | setHeaders(res, path) { 62 | if (path.includes("sw.js")) { 63 | res.setHeader("Cache-Control", `public, max-age=${15 * 60}`) 64 | } 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-ts-hn", 3 | "description": "Nuxt Typescript Hacker News", 4 | "version": "1.0.0", 5 | "author": "Evan You ", 6 | "contributors": [ 7 | { 8 | "name": "Sebastien Chopin (@Atinux)" 9 | }, 10 | { 11 | "name": "Alexandre Chopin (@alexchopin)" 12 | }, 13 | { 14 | "name": "Pooya Parsa (@pi0)" 15 | }, 16 | { 17 | "name": "HG(@husayt)" 18 | }, 19 | { 20 | "name": "Kevin Marrec (@kevinmarrec)" 21 | } 22 | ], 23 | "private": true, 24 | "scripts": { 25 | "dev": "nuxt dev", 26 | "build": "nuxt build", 27 | "start": "nuxt start", 28 | "dev-spa": "nuxt dev --spa", 29 | "build-spa": "nuxt build --spa", 30 | "start-spa": "nuxt start --spa", 31 | "tsc": "tsc --showConfig", 32 | "test": "jest", 33 | "test:watch": "jest --watchAll", 34 | "testc": "tsc -p tsconfig.json", 35 | "lint": "tslint --project .", 36 | "lintfix": "tslint --fix --project .", 37 | "post-update": "yarn upgrade --latest" 38 | }, 39 | "now": { 40 | "alias": "hn.nuxtjs.org" 41 | }, 42 | "engines": { 43 | "node": ">=8.0" 44 | }, 45 | "dependencies": { 46 | "@nuxtjs/axios": "^5.3.6", 47 | "@nuxtjs/component-cache": "1.1.4", 48 | "@nuxtjs/pwa": "2.6.0", 49 | "apicache": "1.2.3", 50 | "express": "4.16.4", 51 | "nuxt": "^2.6.3", 52 | "ts-node": "^8.1.0", 53 | "vue-property-decorator": "^7.3.0" 54 | }, 55 | "devDependencies": { 56 | "@nuxt/typescript": "^2.6.3", 57 | "@types/jest": "^23.3.13", 58 | "@vue/test-utils": "^1.0.0-beta.28", 59 | "jest": "^24.0.0", 60 | "jest-serializer-vue": "^2.0.2", 61 | "prettier": "^1.16.1", 62 | "stylus": "^0.54.5", 63 | "stylus-loader": "^3.0.2", 64 | "ts-jest": "^23.10.5", 65 | "tslint-config-prettier": "^1.17.0", 66 | "tslint-plugin-prettier": "^2.0.1", 67 | "vue-jest": "^3.0.2" 68 | }, 69 | "jest": { 70 | "moduleFileExtensions": [ 71 | "js", 72 | "ts", 73 | "json", 74 | "vue" 75 | ], 76 | "moduleNameMapper": { 77 | "^@/(.*)$": "/$1" 78 | }, 79 | "transform": { 80 | ".*\\.(vue)$": "vue-jest", 81 | "^.+\\.tsx?$": "ts-jest" 82 | }, 83 | "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.ts$", 84 | "snapshotSerializers": [ 85 | "jest-serializer-vue" 86 | ] 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /pages/_feed/_page.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 110 | 111 | 160 | -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { Vue, Component } from "vue-property-decorator" 2 | import { validFeeds } from "~/common/api" 3 | 4 | @Component({ 5 | fetch({ redirect }) { 6 | redirect("/" + validFeeds[0]) 7 | } 8 | }) 9 | export default class Index extends Vue {} 10 | -------------------------------------------------------------------------------- /pages/item/_id.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 61 | 62 | 116 | -------------------------------------------------------------------------------- /pages/user/_id.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 56 | 57 | 87 | -------------------------------------------------------------------------------- /plugins/filters.ts: -------------------------------------------------------------------------------- 1 | import Vue from "vue" 2 | 3 | export function host(url: string) { 4 | const hostName = url.replace(/^https?:\/\//, "").replace(/\/.*$/, "") 5 | const parts = hostName.split(".").slice(-3) 6 | if (parts[0] === "www") parts.shift() 7 | return parts.join(".") 8 | } 9 | 10 | export function timeAgo(time: number | Date | undefined) { 11 | const between = Date.now() / 1000 - Number(time) 12 | if (between < 3600) { 13 | return pluralize(~~(between / 60), " minute") 14 | } else if (between < 86400) { 15 | return pluralize(~~(between / 3600), " hour") 16 | } else { 17 | return pluralize(~~(between / 86400), " day") 18 | } 19 | } 20 | 21 | function pluralize(time: number, label: string) { 22 | if (time === 1) { 23 | return time + label 24 | } 25 | return time + label + "s" 26 | } 27 | 28 | const filters = { 29 | host, 30 | timeAgo 31 | } 32 | export default filters 33 | 34 | Object.keys(filters).forEach(key => { 35 | Vue.filter(key, filters[key]) 36 | }) 37 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuxt-community/hackernews-nuxt-ts/b409a53289372152b8e2becb048596c77d12b49f/static/favicon.ico -------------------------------------------------------------------------------- /static/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuxt-community/hackernews-nuxt-ts/b409a53289372152b8e2becb048596c77d12b49f/static/icon.png -------------------------------------------------------------------------------- /store/index.ts: -------------------------------------------------------------------------------- 1 | import { MutationTree, ActionTree } from "vuex" 2 | 3 | import Vue from "vue" 4 | 5 | import { validFeeds } from "~/common/api" 6 | import { lazy } from "~/common/utils" 7 | // import { CancelToken } from "axios" 8 | const baseUrl = "https://api.hnpwa.com/v0" // "/api" 9 | 10 | interface Dictionary { 11 | [key: string]: T 12 | } 13 | 14 | // export interface Users { 15 | // [key: string]: User 16 | // } 17 | export interface User { 18 | created: string 19 | created_time: number 20 | id: string 21 | karma: number 22 | } 23 | 24 | export interface Items { 25 | [key: number]: Item 26 | } 27 | export interface Item { 28 | comments: any[] 29 | comments_count: number 30 | content: string 31 | domain: string 32 | id: number 33 | points: number 34 | time: number | Date | undefined 35 | time_ago: string 36 | title: string 37 | type: string 38 | url: string 39 | user: string 40 | } 41 | 42 | // export interface Feeds { 43 | // [key: string]: Feed 44 | // } 45 | export interface Feed { 46 | comments_count: number 47 | domain: string 48 | id: number 49 | points: number 50 | time: number | Date | undefined 51 | time_ago: string 52 | title: string 53 | type: string 54 | url: string 55 | user: string 56 | } 57 | 58 | export interface IRootState { 59 | items: Items 60 | users: Dictionary 61 | feeds: Dictionary 62 | } 63 | // ================================================= 64 | // State 65 | // ================================================= 66 | const s = (): IRootState => { 67 | const state = { 68 | items: { 69 | /* [id: number]: Item */ 70 | }, 71 | users: { 72 | /* [id: string]: User */ 73 | }, 74 | feeds: { 75 | /* [page: number] : [ [id: number] ] */ 76 | } 77 | } 78 | 79 | validFeeds.forEach((feed: string) => { 80 | state.feeds[feed] = {} 81 | }) 82 | 83 | return state 84 | } 85 | 86 | // ================================================= 87 | // Mutations 88 | // ================================================= 89 | const mutations: MutationTree = { 90 | SET_FEED: (state, { feed, ids, page }) => { 91 | Vue.set(state.feeds[feed], page, ids) 92 | }, 93 | SET_ITEM: (state, { item }: { item: Item }) => { 94 | if (item) { 95 | Vue.set(state.items as Item[], item.id, item) 96 | } 97 | }, 98 | SET_ITEMS: (state, { items }: { items: Item[] }) => { 99 | items.forEach(item => { 100 | if (item) { 101 | Vue.set(state.items as Item[], item.id, item) 102 | } 103 | }) 104 | }, 105 | SET_USER: (state, { id, user }: { id: string; user: User }) => { 106 | Vue.set(state.users, id, user || false) /* false means user not found */ 107 | } 108 | } 109 | 110 | // ================================================= 111 | // Actions 112 | // ================================================= 113 | const actions: ActionTree = { 114 | FETCH_FEED({ commit, state }, { feed, page, prefetch }) { 115 | // Don't priorotize already fetched feeds 116 | if (state.feeds[feed][page] && state.feeds[feed][page].length) { 117 | prefetch = true 118 | } 119 | if (!prefetch) { 120 | if ((this as any).feedCancelSource) { 121 | ;(this as any).feedCancelSource.cancel( 122 | "prioritize feed: " + feed + " page: " + page 123 | ) 124 | } 125 | 126 | // ;(this as any).feedCancelSource = new CancelToken.source() 127 | } 128 | 129 | return lazy( 130 | items => { 131 | const ids = items.map(item => item.id) 132 | commit("SET_FEED", { feed, ids, page }) 133 | commit("SET_ITEMS", { items }) 134 | }, 135 | () => 136 | (this as any).$axios.$get(`${baseUrl}/${feed}/${page}.json`, { 137 | cancelToken: 138 | (this as any).feedCancelSource && 139 | (this as any).feedCancelSource.token 140 | }), 141 | (state.feeds[feed][page] || []).map(id => state.items[id]) 142 | ) 143 | }, 144 | 145 | FETCH_ITEM({ commit, state }, { id }) { 146 | return lazy( 147 | item => commit("SET_ITEM", { item }), 148 | () => (this as any).$axios.$get(`${baseUrl}/item/${id}.json`), 149 | Object.assign({ id, loading: true, comments: [] }, state.items[id]) 150 | ) 151 | }, 152 | 153 | FETCH_USER({ state, commit }, { id }) { 154 | return lazy( 155 | user => commit("SET_USER", { id, user }), 156 | () => (this as any).$axios.$get(`${baseUrl}/user/${id}.json`), 157 | Object.assign({ id, loading: true }, state.users[id]) 158 | ) 159 | } 160 | } 161 | 162 | export { s as state, actions, mutations } 163 | -------------------------------------------------------------------------------- /tests/__snapshots__/comment.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Comment snapshot 1`] = ` 4 |
  • 5 |
    thecosas 6 | 1545943367 ago 7 |
    8 |
    9 |

    Test

    10 |
    11 | 12 |
      13 |
    • 14 | `; 15 | -------------------------------------------------------------------------------- /tests/__snapshots__/spinner.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Spinner snapshot 1`] = ` 4 | 5 | 6 | 7 | `; 8 | -------------------------------------------------------------------------------- /tests/comment.spec.ts: -------------------------------------------------------------------------------- 1 | let jsData = { 2 | id: 18772478, 3 | user: "thecosas", 4 | time: 1545943367, 5 | time_ago: "19 hours ago", 6 | type: "comment", 7 | content: "

      Test

      ", 8 | comments: [], 9 | comments_count: 0, 10 | level: 0, 11 | url: "item?id=18772478" 12 | } 13 | 14 | import { shallowMount, RouterLinkStub } from "@vue/test-utils" 15 | import Comment from "@/components/comment.vue" 16 | // import { timeAgo } from "../plugins/filters" 17 | 18 | describe("Comment", () => { 19 | let wrapper 20 | 21 | beforeEach(() => { 22 | wrapper = shallowMount(Comment, { 23 | propsData: { 24 | comment: jsData 25 | }, 26 | stubs: { 27 | RouterLink: RouterLinkStub 28 | }, 29 | filters: { 30 | timeAgo(value) { 31 | return value 32 | } 33 | } 34 | }) 35 | }) 36 | 37 | test("snapshot", () => { 38 | expect(wrapper.html()).toMatchSnapshot() 39 | }) 40 | 41 | // it's also easy to check for the existence of elements 42 | 43 | it("renders Comment", () => { 44 | //console.log(wrapper.html()) 45 | expect(wrapper.find("div.text *").html()).toBe(jsData.content) 46 | }) 47 | }) 48 | -------------------------------------------------------------------------------- /tests/spinner.spec.ts: -------------------------------------------------------------------------------- 1 | import { shallowMount } from "@vue/test-utils" 2 | import Spinner from "@/components/spinner.vue" 3 | 4 | describe("Spinner", () => { 5 | // let wrapper = null 6 | 7 | beforeEach(() => { 8 | //wrapper = shallowMount(Spinner) 9 | }) 10 | 11 | test("snapshot", () => { 12 | expect( 13 | shallowMount(Spinner, { 14 | propsData: { 15 | show: true 16 | } 17 | }).html() 18 | ).toMatchSnapshot() 19 | }) 20 | 21 | // it's also easy to check for the existence of elements 22 | 23 | it("renders SVG if show", () => { 24 | const wrapper = shallowMount(Spinner, { 25 | propsData: { 26 | show: true 27 | } 28 | }) 29 | 30 | expect(wrapper.find("svg").isVisible()).toBe(true) 31 | }) 32 | 33 | it("doesn't render svg if show false", () => { 34 | const wrapper = shallowMount(Spinner, { 35 | propsData: { 36 | show: false 37 | } 38 | }) 39 | 40 | expect(wrapper.find("svg").isVisible()).toBe(false) 41 | }) 42 | 43 | afterEach(() => { 44 | // wrapper = null 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /tests/sum.spec.ts: -------------------------------------------------------------------------------- 1 | import sum from "./sum" 2 | 3 | describe("sum", () => { 4 | it("create sum of 2 numbers", () => { 5 | expect(sum(15, 8)).toBe(23) 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /tests/sum.ts: -------------------------------------------------------------------------------- 1 | export default function sum(a: number, b: number): number { 2 | return a + b 3 | } 4 | -------------------------------------------------------------------------------- /tests/vue-shims.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.vue" { 2 | import Vue from "vue" 3 | export default Vue 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "lib": [ 7 | "esnext", 8 | "esnext.asynciterable", 9 | "dom" 10 | ], 11 | "esModuleInterop": true, 12 | "experimentalDecorators": true, 13 | "allowJs": true, 14 | "sourceMap": true, 15 | "strict": true, 16 | "noImplicitAny": false, 17 | "noEmit": true, 18 | "baseUrl": ".", 19 | "paths": { 20 | "~/*": [ 21 | "./*" 22 | ], 23 | "@/*": [ 24 | "./*" 25 | ] 26 | }, 27 | "types": [ 28 | "@types/jest", 29 | "@types/node", 30 | "@nuxt/vue-app" 31 | ] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "warning", 3 | "extends": [ 4 | "tslint:recommended", 5 | "tslint-config-prettier" 6 | ], 7 | "rulesDirectory": ["tslint-plugin-prettier"], 8 | "jsRules": { 9 | "object-literal-sort-keys": false, 10 | "ordered-imports": false, 11 | "no-console": false, 12 | "no-shadowed-variable": false, 13 | "no-empty": [true, "allow-empty-functions"], 14 | "curly": [true, "ignore-same-line"] 15 | }, 16 | "rules": { 17 | "prettier": true, 18 | "member-access": [true, "no-public"], 19 | "variable-name": [true, "allow-leading-underscore"], 20 | "interface-name": false, 21 | "object-literal-sort-keys": false, 22 | "ordered-imports": false, 23 | "no-console": false, 24 | "no-empty": [true, "allow-empty-functions"], 25 | "curly": [true, "ignore-same-line"], 26 | "no-bitwise": false 27 | }, 28 | "linterOptions": { 29 | "exclude": [ 30 | "node_modules/**", 31 | "dist/**", 32 | "dist.js/**", 33 | ".nuxt/**", 34 | "tests/**" 35 | ] 36 | } 37 | } 38 | --------------------------------------------------------------------------------