diff --git a/package.json b/package.json index 9bd151930..34dd0ca49 100644 --- a/package.json +++ b/package.json @@ -40,11 +40,16 @@ "@fortawesome/free-regular-svg-icons": "5.15.3", "@fortawesome/free-solid-svg-icons": "5.15.3", "@fortawesome/vue-fontawesome": "2.0.2", + "@types/jest": "^26.0.24", + "@typescript-eslint/eslint-plugin": "^4.18.0", + "@typescript-eslint/parser": "^4.18.0", "@vue/cli": "4.5.13", "@vue/cli-plugin-babel": "4.5.13", "@vue/cli-plugin-eslint": "4.5.13", "@vue/cli-plugin-pwa": "4.5.13", + "@vue/cli-plugin-typescript": "~4.5.0", "@vue/cli-service": "4.5.13", + "@vue/eslint-config-typescript": "^7.0.0", "axios": "0.21.1", "babel-eslint": "10.1.0", "cypress": "7.7.0", @@ -54,6 +59,8 @@ "faker": "5.5.3", "jest": "27.0.6", "sass-loader": "10.2.0", + "ts-jest": "^27.0.3", + "typescript": "^4.3.5", "vue-flatpickr-component": "8.1.7", "vue-notification": "1.3.20", "vue-router": "3.5.2", @@ -67,7 +74,8 @@ }, "extends": [ "plugin:vue/essential", - "eslint:recommended" + "eslint:recommended", + "@vue/typescript" ], "rules": { "vue/html-quotes": [ @@ -88,7 +96,7 @@ ] }, "parserOptions": { - "parser": "babel-eslint" + "parser": "@typescript-eslint/parser" }, "ignorePatterns": [ "*.test.js", @@ -105,11 +113,23 @@ "last 2 versions", "not ie < 11" ], - "license": "AGPL-3.0-or-later", "jest": { "testPathIgnorePatterns": [ "cypress" ], - "testEnvironment": "jsdom" - } -} \ No newline at end of file + "testEnvironment": "jsdom", + "preset": "ts-jest", + "roots": [ + "/src" + ], + "transform": { + "^.+\\.(js|tsx?)$": "ts-jest" + }, + "moduleFileExtensions": [ + "ts", + "js", + "json" + ] + }, + "license": "AGPL-3.0-or-later" +} diff --git a/src/components/quick-actions/quick-actions.vue b/src/components/quick-actions/quick-actions.vue index bd7e80213..2459329cc 100644 --- a/src/components/quick-actions/quick-actions.vue +++ b/src/components/quick-actions/quick-actions.vue @@ -63,7 +63,7 @@ import {CURRENT_LIST, LOADING, LOADING_MODULE, QUICK_ACTIONS_ACTIVE} from '@/sto import ListModel from '@/models/list' import createTask from '@/components/tasks/mixins/createTask' import QuickAddMagic from '@/components/tasks/partials/quick-add-magic' -import {getHistory} from '@/modules/listHistory' +import {getHistory} from '../../modules/listHistory' const TYPE_LIST = 'list' const TYPE_TASK = 'task' diff --git a/src/main.js b/src/main.ts similarity index 88% rename from src/main.js rename to src/main.ts index bc1c16467..83deede77 100644 --- a/src/main.js +++ b/src/main.ts @@ -2,10 +2,18 @@ import Vue from 'vue' import App from './App.vue' import router from './router' +declare global { + interface Window { + API_URL: string; + } +} + import {formatDate, formatDateSince} from '@/helpers/time/formatDate' +// @ts-ignore import {VERSION} from './version.json' // Register the modal +// @ts-ignore import Modal from './components/modal/modal' // Add CSS import './styles/vikunja.scss' @@ -80,6 +88,7 @@ import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome' import './registerServiceWorker' // Shortcuts +// @ts-ignore - no types available import vueShortkey from 'vue-shortkey' // Mixins import message from './message' @@ -90,6 +99,7 @@ import {getListTitle} from './helpers/getListTitle' // Vuex import {store} from './store' // i18n +import VueI18n from 'vue-i18n' // types import {i18n} from './i18n/setup' console.info(`Vikunja frontend version ${VERSION}`) @@ -180,12 +190,15 @@ Vue.directive('focus', focus) import tooltip from '@/directives/tooltip' +// @ts-ignore Vue.directive('tooltip', tooltip) +// @ts-ignore import Button from '@/components/input/button' Vue.component('x-button', Button) +// @ts-ignore import Card from '@/components/misc/card' Vue.component('card', Card) @@ -193,7 +206,7 @@ Vue.component('card', Card) Vue.mixin({ methods: { formatDateSince(date) { - return formatDateSince(date, (p, params) => this.$t(p, params)) + return formatDateSince(date, (p: VueI18n.Path, params?: VueI18n.Values) => this.$t(p, params)) }, formatDate(date) { return formatDate(date, 'PPPPpppp', this.$t('date.locale')) @@ -202,16 +215,16 @@ Vue.mixin({ return formatDate(date, 'PPpp', this.$t('date.locale')) }, getNamespaceTitle(n) { - return getNamespaceTitle(n, p => this.$t(p)) + return getNamespaceTitle(n, (p: VueI18n.Path) => this.$t(p)) }, getListTitle(l) { - return getListTitle(l, p => this.$t(p)) + return getListTitle(l, (p: VueI18n.Path) => this.$t(p)) }, error(e, actions = []) { - return message.error(e, this, p => this.$t(p), actions) + return message.error(e, this, (p: VueI18n.Path) => this.$t(p), actions) }, success(s, actions = []) { - return message.success(s, this, p => this.$t(p), actions) + return message.success(s, this, (p: VueI18n.Path) => this.$t(p), actions) }, colorIsDark: colorIsDark, setTitle: setTitle, diff --git a/src/modules/listHistory.test.js b/src/modules/listHistory.test.ts similarity index 74% rename from src/modules/listHistory.test.js rename to src/modules/listHistory.test.ts index dce26d7ce..9b62da69c 100644 --- a/src/modules/listHistory.test.js +++ b/src/modules/listHistory.test.ts @@ -26,9 +26,9 @@ test('store list in history', () => { }) test('store only the last 5 lists in history', () => { - let saved = null + let saved: string | null = null Storage.prototype.getItem = jest.fn(() => saved) - Storage.prototype.setItem = jest.fn((key, lists) => { + Storage.prototype.setItem = jest.fn((key: string, lists: string) => { saved = lists }) @@ -42,9 +42,9 @@ test('store only the last 5 lists in history', () => { }) test('don\'t store the same list twice', () => { - let saved = null + let saved: string | null = null Storage.prototype.getItem = jest.fn(() => saved) - Storage.prototype.setItem = jest.fn((key, lists) => { + Storage.prototype.setItem = jest.fn((key: string, lists: string) => { saved = lists }) @@ -53,22 +53,10 @@ test('don\'t store the same list twice', () => { expect(saved).toBe('[{"id":1}]') }) -test('don\'t store the same list twice with different id types', () => { - let saved = null - Storage.prototype.getItem = jest.fn(() => saved) - Storage.prototype.setItem = jest.fn((key, lists) => { - saved = lists - }) - - saveListToHistory({id: 1}) - saveListToHistory({id: '1'}) - expect(saved).toBe('[{"id":1}]') -}) - test('move a list to the beginning when storing it multiple times', () => { - let saved = null + let saved: string | null = null Storage.prototype.getItem = jest.fn(() => saved) - Storage.prototype.setItem = jest.fn((key, lists) => { + Storage.prototype.setItem = jest.fn((key: string, lists: string) => { saved = lists }) diff --git a/src/modules/listHistory.js b/src/modules/listHistory.ts similarity index 68% rename from src/modules/listHistory.js rename to src/modules/listHistory.ts index 0a5c0bb73..aaa61726a 100644 --- a/src/modules/listHistory.js +++ b/src/modules/listHistory.ts @@ -1,4 +1,8 @@ -export const getHistory = () => { +interface ListHistory { + id: number; +} + +export function getHistory(): ListHistory[] { const savedHistory = localStorage.getItem('listHistory') if (savedHistory === null) { return [] @@ -7,17 +11,17 @@ export const getHistory = () => { return JSON.parse(savedHistory) } -export function saveListToHistory(list) { +export function saveListToHistory(list: ListHistory) { const history = getHistory() - list.id = parseInt(list.id) + // list.id = parseInt(list.id) // Remove the element if it already exists in history, preventing duplicates and essentially moving it to the beginning - for (const i in history) { - if (history[i].id === list.id) { + history.forEach((l, i) => { + if (l.id === list.id) { history.splice(i, 1) } - } + }) // Add the new list to the beginning of the list history.unshift(list) diff --git a/src/types/shims-tsx.d.ts b/src/types/shims-tsx.d.ts new file mode 100644 index 000000000..c656c68b8 --- /dev/null +++ b/src/types/shims-tsx.d.ts @@ -0,0 +1,13 @@ +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 + } + } +} diff --git a/src/types/shims-vue.d.ts b/src/types/shims-vue.d.ts new file mode 100644 index 000000000..d9f24faa4 --- /dev/null +++ b/src/types/shims-vue.d.ts @@ -0,0 +1,4 @@ +declare module '*.vue' { + import Vue from 'vue' + export default Vue +} diff --git a/src/types/window.d.ts b/src/types/window.d.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/views/Home.vue b/src/views/Home.vue index ea8973a67..cd434dc09 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -46,7 +46,7 @@