intial ts conversion

This commit is contained in:
Dan Elbert 2021-11-28 23:19:56 -06:00
parent d82fce08d9
commit df2ccf86b2
47 changed files with 11127 additions and 7 deletions

5
.editorconfig Normal file
View File

@ -0,0 +1,5 @@
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true

1
.foreman Normal file
View File

@ -0,0 +1 @@
port: 3000

1
.gitignore vendored
View File

@ -20,6 +20,7 @@
.byebug_history .byebug_history
/public/assets /public/assets
/dist
.DS_Store .DS_Store
/public/packs /public/packs

View File

@ -19,6 +19,7 @@ gem 'tzinfo-data'
group :development, :test do group :development, :test do
gem 'sqlite3', '~> 1.4.2' gem 'sqlite3', '~> 1.4.2'
gem 'listen', '~> 3.3'
gem 'rspec-rails', '~> 5.0.2' gem 'rspec-rails', '~> 5.0.2'
gem 'rails-controller-testing' gem 'rails-controller-testing'
gem 'factory_bot_rails', '~> 6.2.0' gem 'factory_bot_rails', '~> 6.2.0'

View File

@ -80,6 +80,7 @@ GEM
factory_bot_rails (6.2.0) factory_bot_rails (6.2.0)
factory_bot (~> 6.2.0) factory_bot (~> 6.2.0)
railties (>= 5.0.0) railties (>= 5.0.0)
ffi (1.15.4)
globalid (1.0.0) globalid (1.0.0)
activesupport (>= 5.0) activesupport (>= 5.0)
i18n (1.8.11) i18n (1.8.11)
@ -97,6 +98,9 @@ GEM
kaminari-core (= 1.2.1) kaminari-core (= 1.2.1)
kaminari-core (1.2.1) kaminari-core (1.2.1)
liner (0.2.4) liner (0.2.4)
listen (3.7.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
loofah (2.12.0) loofah (2.12.0)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
@ -154,6 +158,9 @@ GEM
rake (>= 0.13) rake (>= 0.13)
thor (~> 1.0) thor (~> 1.0)
rake (13.0.6) rake (13.0.6)
rb-fsevent (0.11.0)
rb-inotify (0.10.1)
ffi (~> 1.0)
redcarpet (3.5.1) redcarpet (3.5.1)
rspec-core (3.10.1) rspec-core (3.10.1)
rspec-support (~> 3.10.0) rspec-support (~> 3.10.0)
@ -207,6 +214,7 @@ DEPENDENCIES
database_cleaner (~> 2.0.1) database_cleaner (~> 2.0.1)
factory_bot_rails (~> 6.2.0) factory_bot_rails (~> 6.2.0)
kaminari (~> 1.2.1) kaminari (~> 1.2.1)
listen (~> 3.3)
oj (~> 3.13.9) oj (~> 3.13.9)
pg (~> 1.2.3) pg (~> 1.2.3)
puma (~> 5.5) puma (~> 5.5)

View File

@ -1,2 +1,2 @@
frontend: yarn serve
rails: bundle exec rails s -b 0.0.0.0 rails: bundle exec rails s -b 0.0.0.0
webpacker: bin/webpack-dev-server

View File

@ -5,7 +5,7 @@ A self hosted cookbook
Parsley is released under the MIT License. Parsley is released under the MIT License.
Copyright (C) 2020 Dan Elbert Copyright (C) 2021 Dan Elbert
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -5,7 +5,7 @@ class ApplicationSerializer
def initialize(items, serializer, opts = {}) def initialize(items, serializer, opts = {})
super(items, opts) super(items, opts)
@collection_name = opts[:collection_name] @collection_name = 'list'
@serializer = serializer @serializer = serializer
end end
@ -14,10 +14,10 @@ class ApplicationSerializer
if @collection_name && item.respond_to?(:total_pages) if @collection_name && item.respond_to?(:total_pages)
{ {
total_count: item.total_count, totalCount: item.total_count,
total_pages: item.total_pages, totalPages: item.total_pages,
current_page: item.current_page, currentPage: item.current_page,
page_size: item.limit_value, pageSize: item.limit_value,
@collection_name.to_sym => list @collection_name.to_sym => list
} }
else else

5
babel.config.js Normal file
View File

@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
};

90
package.json Normal file
View File

@ -0,0 +1,90 @@
{
"name": "parsley",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test:unit": "vue-cli-service test:unit",
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.6.5",
"register-service-worker": "^1.7.1",
"vue": "^3.0.0",
"vue-class-component": "^8.0.0-0",
"vue-router": "^4.0.0-0",
"vuex": "^4.0.0-0"
},
"devDependencies": {
"@types/chai": "^4.2.11",
"@types/mocha": "^5.2.4",
"@typescript-eslint/eslint-plugin": "^4.18.0",
"@typescript-eslint/parser": "^4.18.0",
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-pwa": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-typescript": "~4.5.0",
"@vue/cli-plugin-unit-mocha": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/compiler-sfc": "^3.0.0",
"@vue/eslint-config-standard": "^5.1.2",
"@vue/eslint-config-typescript": "^7.0.0",
"@vue/test-utils": "^2.0.0-0",
"chai": "^4.1.2",
"eslint": "^6.7.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^7.0.0",
"sass": "^1.26.5",
"sass-loader": "^8.0.2",
"typescript": "~4.1.5"
},
"resolutions": {
"workbox-webpack-plugin": "~6.4.1"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/vue3-essential",
"@vue/standard",
"@vue/typescript/recommended"
],
"parserOptions": {
"ecmaVersion": 2020
},
"rules": {
"space-before-function-paren": [
"error",
"never"
],
"semi": [
"error",
"always"
]
},
"overrides": [
{
"files": [
"**/__tests__/*.{j,t}s?(x)",
"**/tests/unit/**/*.spec.{j,t}s?(x)"
],
"env": {
"mocha": true
}
}
]
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 799 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.00251 14.9297L0 1.07422H6.14651L8.00251 4.27503L9.84583 1.07422H16L8.00251 14.9297Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 215 B

17
public/index.html Normal file
View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

30
src/App.vue Normal file
View File

@ -0,0 +1,30 @@
<template>
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<router-view/>
</template>
<style lang="scss">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
#nav {
padding: 30px;
a {
font-weight: bold;
color: #2c3e50;
&.router-link-exact-active {
color: #42b983;
}
}
}
</style>

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -0,0 +1,67 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa" target="_blank" rel="noopener">pwa</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router" target="_blank" rel="noopener">router</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-unit-mocha" target="_blank" rel="noopener">unit-mocha</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript" target="_blank" rel="noopener">typescript</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script lang="ts">
import { Options, Vue } from 'vue-class-component';
@Options({
props: {
msg: String
}
})
export default class HelloWorld extends Vue {
msg!: string
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

147
src/lib/api.ts Normal file
View File

@ -0,0 +1,147 @@
import * as Errors from './errors';
import { PagedCollection, Recipe } from '@/lib/models';
type UpdateHandler = {
url: string,
component: unknown,
handler: (res: Response) => void
}
class Api {
cacheFirstUpdateHandlers: UpdateHandler[] = [];
constructor() {
navigator.serviceWorker.addEventListener('message', async(event) => {
if (event.data.type === 'CACHE_UPDATED' && event.data.meta === 'workbox-broadcast-update') {
const { cacheName, updatedURL } = event.data.payload;
const interestedHandlers = this.cacheFirstUpdateHandlers.filter(h => h.url === updatedURL);
if (interestedHandlers.length > 0) {
const cache = await caches.open(cacheName);
const updatedResponse = await cache.match(updatedURL);
if (updatedResponse !== undefined) {
interestedHandlers.forEach(h => h.handler(updatedResponse));
}
}
}
});
}
async checkStatus(response: Response): Promise<Response> {
if (response.ok) {
return response;
} else if (response.status === 404) {
throw new Errors.ApiNotFoundError(response.statusText, response);
} else if (response.status === 422) {
let json = null;
try {
json = await response.json();
} catch (e) {
// no-op
}
throw new Errors.ApiValidationError('Validation Error', response, json);
} else {
throw new Errors.ApiServerError(response.statusText || 'Unknown Server Error', response);
}
}
performRequest<TParams, TReturns>(url: string, method: string, params: TParams, headers: {[index: string]: string} = {}): Promise<TReturns> {
const hasBody = (params !== null && params !== undefined) && Object.keys(params).length !== 0;
let body: string | null = null;
const reqHeaders = new Headers();
reqHeaders.append('Accept', 'application/json');
reqHeaders.append('Content-Type', 'application/json');
for (const key in headers) {
reqHeaders.append(key, headers[key]);
}
if (hasBody) {
body = JSON.stringify(params);
}
return fetch(url, {
headers: reqHeaders,
method: method,
credentials: 'same-origin',
body: body
}).then(this.checkStatus)
.then(res => res.json());
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
objectToUrlParams(obj: any, queryParams: string[] = [], prefixes: string[] = []): string[] {
for (const key in obj) {
const val = obj[key];
const paramName = prefixes.join('[') + '['.repeat(Math.min(prefixes.length, 1)) + encodeURIComponent(key) + ']'.repeat(prefixes.length);
if (Array.isArray(val)) {
for (const x of val) {
queryParams.push(paramName + '[]=' + (x === null ? '' : encodeURIComponent(x.toString())));
}
} else if (val !== null && typeof (val) === 'object') {
this.objectToUrlParams(val, queryParams, prefixes.concat([key]));
} else {
queryParams.push(paramName + '=' + (val === null ? '' : encodeURIComponent(val)));
}
}
return queryParams;
}
buildGetUrl<TParams>(url: string, params: TParams): string {
const queryParams = this.objectToUrlParams(params);
if (queryParams.length) {
url = url + '?' + queryParams.join('&');
}
return url;
}
get<TParams, TReturn>(url: string, params: TParams): Promise<TReturn> {
return this.performRequest<null, TReturn>(this.buildGetUrl(url, params), 'GET', null);
}
setupCacheFirstGet<TParams, TReturn>(url: string, params: TParams, component: unknown, dataHandler: (data: TReturn) => void): () => void {
const fullUrl = this.buildGetUrl(url, params);
this.cacheFirstUpdateHandlers.push({
url: fullUrl,
component: component,
handler: res => res.json().then(data => dataHandler(data))
});
this.performRequest<null, TReturn>(fullUrl, 'GET', null, { 'Stale-While-Revalidate': 'true' })
.then((data: TReturn) => dataHandler(data));
return () => {
this.disableCacheFirstGet(url, params, component);
};
}
disableCacheFirstGet<TParams>(url: string | null, params: TParams, component: unknown) {
let fullUrl: string | null = null;
if (url !== null) {
fullUrl = this.buildGetUrl(url, params);
}
this.cacheFirstUpdateHandlers = this.cacheFirstUpdateHandlers.filter(h => !(h.component === component && (fullUrl === null || fullUrl === h.url)));
}
getRecipeList(component: unknown, page: number, per: number, sortColumn: string | null, sortDirection: string | null, name: string | null, tags: string[] | null, dataHandler: (json: PagedCollection<Recipe>) => void): () => void {
const params = {
criteria: {
page: page,
per: per,
sort_column: sortColumn,
sort_direction: sortDirection,
name: name,
tags: tags
}
};
return this.setupCacheFirstGet('/recipes', params, component, dataHandler);
}
}
export default new Api();

57
src/lib/errors.ts Normal file
View File

@ -0,0 +1,57 @@
type ValidationErrors = {
[key: string]: string[]
}
export class ApiError extends Error {
public response: Response;
constructor(message: string, response: Response) {
super(message);
this.name = 'ApiError';
this.response = response;
}
public get responseCode(): number {
if (this.response) {
return this.response.status;
} else {
return 0;
}
}
}
export class ApiServerError extends ApiError {
constructor(message: string, response: Response) {
super(message, response);
this.name = 'ApiServerError';
}
}
export class ApiNotFoundError extends ApiError {
constructor(message: string, response: Response) {
super(message, response);
this.name = 'ApiNotFoundError';
}
}
export class ApiValidationError extends ApiError {
json?: { [index: string]: string[] };
constructor(message: string, response: Response, json?: { [index: string]: string[] }) {
super(message, response);
this.name = 'ApiValidationError';
this.json = json;
}
validationErrors(): ValidationErrors {
const errors = {} as ValidationErrors;
if (this.json !== null) {
for (const key in this.json) {
errors[key] = this.json[key] as string[];
}
} else {
errors.base = ['unknown error'];
}
return errors;
}
}

12
src/lib/models.ts Normal file
View File

@ -0,0 +1,12 @@
export interface PagedCollection<T> {
totalCount: number
totalPages: number
currentPage: number
pageSize: number
list: T[]
}
export interface Recipe {
id: number
name: string
}

8
src/main.ts Normal file
View File

@ -0,0 +1,8 @@
import { createApp } from 'vue';
import App from './App.vue';
import { registerServiceWorker } from './registerServiceWorker';
import router from './router';
import store from './store';
registerServiceWorker(store);
createApp(App).use(store).use(router).mount('#app');

View File

@ -0,0 +1,38 @@
/* eslint-disable no-console */
import { register } from 'register-service-worker';
import { Store } from 'vuex';
import { State } from '@/store/state';
export function registerServiceWorker(store: Store<State>): void {
if (process.env.NODE_ENV === 'production') {
register(`${process.env.BASE_URL}service-worker.js`, {
ready() {
store.commit('setUpdateAvailable', false);
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() {
store.commit('setUpdateAvailable', true);
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);
}
});
}
}

25
src/router/index.ts Normal file
View File

@ -0,0 +1,25 @@
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
import Recipes from '../views/Recipes.vue';
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'Recipes',
component: Recipes
},
{
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 = createRouter({
history: createWebHashHistory(),
routes
});
export default router;

19
src/service-worker.js Normal file
View File

@ -0,0 +1,19 @@
import { NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies';
import { BroadcastUpdatePlugin } from 'workbox-broadcast-update/BroadcastUpdatePlugin';
import { cleanupOutdatedCaches, precacheAndRoute } from 'workbox-precaching';
import { defaultHandler, registerRoute } from 'workbox-routing';
cleanupOutdatedCaches();
precacheAndRoute(self.__WB_MANIFEST);
const isStaleWhileRevalidate = ({ request }) => {
return request.headers.get('Cache-Then-Network') === 'true';
};
registerRoute(isStaleWhileRevalidate, new StaleWhileRevalidate({
plugins: [
new BroadcastUpdatePlugin()
]
}));
defaultHandler(new NetworkFirst());

6
src/shims-vue.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
/* eslint-disable */
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

17
src/store/index.ts Normal file
View File

@ -0,0 +1,17 @@
import { createStore } from 'vuex';
import { State } from './state';
export default createStore<State>({
state: {
updateAvailable: false
},
mutations: {
setUpdateAvailable(state, payload) {
state.updateAvailable = payload;
}
},
actions: {
},
modules: {
}
});

3
src/store/state.ts Normal file
View File

@ -0,0 +1,3 @@
export interface State {
updateAvailable: boolean
}

5
src/views/About.vue Normal file
View File

@ -0,0 +1,5 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>

18
src/views/Home.vue Normal file
View File

@ -0,0 +1,18 @@
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
</div>
</template>
<script lang="ts">
import { Options, Vue } from 'vue-class-component';
import HelloWorld from '@/components/HelloWorld.vue'; // @ is an alias to /src
@Options({
components: {
HelloWorld
}
})
export default class Home extends Vue {}
</script>

41
src/views/Recipes.vue Normal file
View File

@ -0,0 +1,41 @@
<template>
<div class="recipes">
<ul v-if="recipes !== null">
<li v-for="r in recipes.list" :key="r.id">
{{ r.name }}
</li>
</ul>
</div>
</template>
<script lang="ts">
import api from '@/lib/api';
import { PagedCollection, Recipe } from '@/lib/models';
import { Options, Vue } from 'vue-class-component';
@Options({
components: {
}
})
export default class Recipes extends Vue {
recipes: (PagedCollection<Recipe> | null) = null
cancelCacheUpdate: (() => void) | null = null
updateRecipes(): void {
if (this.cancelCacheUpdate !== null) {
this.cancelCacheUpdate();
}
this.cancelCacheUpdate = api.getRecipeList(this, 1, 20, null, null, null, null, data => { this.recipes = data; });
}
mounted(): void {
this.updateRecipes();
}
unmounted(): void {
if (this.cancelCacheUpdate !== null) {
this.cancelCacheUpdate();
}
}
}
</script>

9
src/vuex.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
import { Store } from 'vuex';
import { State } from '@/store/state';
declare module '@vue/runtime-core' {
// provide typings for `this.$store`
interface ComponentCustomProperties {
$store: Store<State>
}
}

View File

@ -0,0 +1,13 @@
import { expect } from 'chai';
import { shallowMount } from '@vue/test-utils';
import HelloWorld from '@/components/HelloWorld.vue';
describe('HelloWorld.vue', () => {
it('renders props.msg when passed', () => {
const msg = 'new message';
const wrapper = shallowMount(HelloWorld, {
props: { msg }
});
expect(wrapper.text()).to.include(msg);
});
});

42
tsconfig.json Normal file
View File

@ -0,0 +1,42 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env",
"mocha",
"chai"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}

19
vue.config.js Normal file
View File

@ -0,0 +1,19 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const path = require('path');
// In development, the Rails app will run on a port 100 higher than this one (default way foreman assigns ports)
const devRailsPort = (parseInt(process.env.PORT || '3000') + 100).toString();
module.exports = {
pwa: {
workboxPluginMode: 'InjectManifest',
workboxOptions: {
swSrc: path.join(__dirname, 'src/service-worker.js'),
compileSrc: true
}
},
devServer: {
proxy: `http://localhost:${devRailsPort}`
}
};

10413
yarn.lock Normal file

File diff suppressed because it is too large Load Diff