├── .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 |
2 |
21 |
22 |
23 |
38 |
39 |
94 |
--------------------------------------------------------------------------------
/components/item-list-nav.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
< prev
4 |
< prev
5 |
{{ page }}/{{ maxPage }}
6 |
more >
7 |
more >
8 |
9 |
10 |
11 |
30 |
31 |
51 |
--------------------------------------------------------------------------------
/components/item.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ item.points }}
4 |
5 |
6 | {{ item.title }}
7 | ({{ item.url | host }})
8 |
9 |
10 | {{ item.title }}
11 |
12 |
13 |
14 |
15 | by
16 | {{ item.user }}
17 |
18 | {{ item.time | timeAgo }} ago
19 |
22 |
23 | {{ item.type }}
24 |
25 |
26 |
27 |
43 |
44 |
79 |
--------------------------------------------------------------------------------
/components/lazy-wrapper.vue:
--------------------------------------------------------------------------------
1 |
2 |
22 |
--------------------------------------------------------------------------------
/components/spinner.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
20 |
21 |
22 |
23 |
24 |
37 |
38 |
94 |
--------------------------------------------------------------------------------
/layouts/default.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
19 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
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 |
2 |
3 |
14 |
28 |
29 |
30 |
31 |
61 |
62 |
116 |
--------------------------------------------------------------------------------
/pages/user/_id.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | User : {{ user.id }}
5 |
6 |
17 |
18 |
19 | submissions
20 | |
21 | comments
22 |
23 |
24 |
25 | User not found.
26 |
27 |
28 |
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 |
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 |
--------------------------------------------------------------------------------
17 | {{ 18 | item.comments 19 | ? item.comments.length + " comments" 20 | : "No comments yet." 21 | }} 22 |
23 |24 |
25 |
26 |