diff --git a/.eslintrc.js b/.eslintrc.js index 53f17a9422..1021c7404e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -17,6 +17,30 @@ module.exports = { '@vue/standard', '@vue/typescript/recommended', '@nextcloud', + 'plugin:@typescript-eslint/recommended', ], ignorePatterns: ['*.d.ts'], + rules: { + 'no-console': 'warn', + '@typescript-eslint/no-var-requires': 'off', + + // TODO: Trouble importing .ts files into .vue files for some reason? + 'import/extensions': 'off', + 'n/no-missing-import': 'off', + }, + settings: { + 'import/resolver': { + node: { + extensions: ['.ts'], + }, + }, + }, + overrides: [ + { + files: ['*spec.ts', 'tests/javascript/unit/setup.ts'], + rules: { + '@typescript-eslint/no-explicit-any': 'off', + }, + }, + ], } diff --git a/package-lock.json b/package-lock.json index 48ae5985ee..9279f9e1dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,7 +46,7 @@ "eslint": "^8.6.0", "eslint-config-standard": "^17.0.0", "eslint-import-resolver-webpack": "^0.12.2", - "eslint-plugin-import": "^2.22.0", + "eslint-plugin-import": "^2.26.0", "eslint-plugin-jsdoc": "^39.2.1", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^6.0.0", @@ -82,13 +82,14 @@ "vue-color": "^2.8.1", "vue-eslint-parser": "^9.0.2", "vue-jest": "^3.0.7", - "vue-loader": "^15.9.8", + "vue-loader": "^15.10.1", "vue-material-design-icons": "^5.1.2", "vue-multiselect": "^2.1.6", "vue-template-compiler": "^2.6.14", "vue2-datepicker": "^3.11.0", "webpack": "^5.72.1", - "webpack-cli": "^4.9.2" + "webpack-cli": "^4.9.2", + "webpack-merge": "^5.8.0" }, "engines": { "node": "^16.0.0", @@ -14885,6 +14886,15 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/terser": { "version": "5.15.1", "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.1.tgz", @@ -15161,14 +15171,6 @@ "node": ">=10.13.0" } }, - "node_modules/ts-loader/node_modules/tapable": { - "version": "2.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/tsconfig": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", @@ -15659,9 +15661,9 @@ } }, "node_modules/vue-loader": { - "version": "15.10.0", - "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.10.0.tgz", - "integrity": "sha512-VU6tuO8eKajrFeBzMssFUP9SvakEeeSi1BxdTH5o3+1yUyrldp8IERkSdXlMI2t4kxF2sqYUDsQY+WJBxzBmZg==", + "version": "15.10.1", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.10.1.tgz", + "integrity": "sha512-SaPHK1A01VrNthlix6h1hq4uJu7S/z0kdLUb6klubo738NeQoLbS6V9/d8Pv19tU0XdQKju3D1HSKuI8wJ5wMA==", "dev": true, "dependencies": { "@vue/component-compiler-utils": "^3.1.0", @@ -16240,15 +16242,6 @@ "node": ">=6.11.5" } }, - "node_modules/webpack/node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/webpack/node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -27425,6 +27418,12 @@ } } }, + "tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true + }, "terser": { "version": "5.15.1", "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.1.tgz", @@ -27607,10 +27606,6 @@ "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } - }, - "tapable": { - "version": "2.2.1", - "dev": true } } }, @@ -28003,9 +27998,9 @@ } }, "vue-loader": { - "version": "15.10.0", - "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.10.0.tgz", - "integrity": "sha512-VU6tuO8eKajrFeBzMssFUP9SvakEeeSi1BxdTH5o3+1yUyrldp8IERkSdXlMI2t4kxF2sqYUDsQY+WJBxzBmZg==", + "version": "15.10.1", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.10.1.tgz", + "integrity": "sha512-SaPHK1A01VrNthlix6h1hq4uJu7S/z0kdLUb6klubo738NeQoLbS6V9/d8Pv19tU0XdQKju3D1HSKuI8wJ5wMA==", "dev": true, "requires": { "@vue/component-compiler-utils": "^3.1.0", @@ -28232,12 +28227,6 @@ "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", "dev": true }, - "tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true - }, "watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", diff --git a/package.json b/package.json index 079dead08e..a181d2614b 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ "build": "NODE_ENV=production webpack --progress --config webpack.js", "dev": "NODE_ENV=development webpack --progress --config webpack.js", "watch": "NODE_ENV=development webpack --progress --watch --config webpack.js", - "lint": "eslint --ext .js,.vue src", - "lint:fix": "eslint --ext .js,.vue src --fix", + "lint": "eslint --ext .js,.vue,.ts src", + "lint:fix": "eslint --ext .js,.vue,.ts src --fix", "stylelint": "stylelint **/*.css **/*.scss **/*.vue", "stylelint:fix": "stylelint **/*.css **/*.scss **/*.vue --fix", "test": "jest --verbose", @@ -82,7 +82,7 @@ "eslint": "^8.6.0", "eslint-config-standard": "^17.0.0", "eslint-import-resolver-webpack": "^0.12.2", - "eslint-plugin-import": "^2.22.0", + "eslint-plugin-import": "^2.26.0", "eslint-plugin-jsdoc": "^39.2.1", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^6.0.0", @@ -118,13 +118,14 @@ "vue-color": "^2.8.1", "vue-eslint-parser": "^9.0.2", "vue-jest": "^3.0.7", - "vue-loader": "^15.9.8", + "vue-loader": "^15.10.1", "vue-material-design-icons": "^5.1.2", "vue-multiselect": "^2.1.6", "vue-template-compiler": "^2.6.14", "vue2-datepicker": "^3.11.0", "webpack": "^5.72.1", - "webpack-cli": "^4.9.2" + "webpack-cli": "^4.9.2", + "webpack-merge": "^5.8.0" }, "jest": { "preset": "ts-jest", diff --git a/src/App.vue b/src/App.vue index ff6bf6c432..076fed19cb 100644 --- a/src/App.vue +++ b/src/App.vue @@ -13,6 +13,7 @@ import Vue from 'vue' import NcContent from '@nextcloud/vue/dist/Components/NcContent.js' import NcAppContent from '@nextcloud/vue/dist/Components/NcAppContent.js' import Sidebar from './components/Sidebar.vue' +import { ACTIONS } from './store' export default Vue.extend({ components: { @@ -20,8 +21,9 @@ export default Vue.extend({ Sidebar, NcAppContent, }, - created() { - this.$store.dispatch('loadFolder') + async created() { + await this.$store.dispatch(ACTIONS.FETCH_FOLDERS) + await this.$store.dispatch(ACTIONS.FETCH_FEEDS) }, }) diff --git a/src/components/AddFeed.vue b/src/components/AddFeed.vue index 5d50b7b4e1..af5d759374 100644 --- a/src/components/AddFeed.vue +++ b/src/components/AddFeed.vue @@ -107,12 +107,15 @@ diff --git a/src/types/Feed.ts b/src/types/Feed.ts new file mode 100644 index 0000000000..9e06aca3cf --- /dev/null +++ b/src/types/Feed.ts @@ -0,0 +1,8 @@ +export type Feed = { + folderId?: number; + unreadCount: number; + url: string; + title?: string; + autoDiscover?: boolean; + faviconLink?: string; +} diff --git a/src/types/Feed.vue b/src/types/Feed.vue deleted file mode 100644 index eb2e8a0983..0000000000 --- a/src/types/Feed.vue +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/src/types/Folder.ts b/src/types/Folder.ts new file mode 100644 index 0000000000..533cc93b7f --- /dev/null +++ b/src/types/Folder.ts @@ -0,0 +1,8 @@ +import { Feed } from './Feed' + +export type Folder = { + feeds: Feed[]; + feedCount: number; + name: string; + id: number; +} diff --git a/src/types/Folder.vue b/src/types/Folder.vue deleted file mode 100644 index dd9b5caf38..0000000000 --- a/src/types/Folder.vue +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/tests/javascript/unit/components/AddFeed.spec.ts b/tests/javascript/unit/components/AddFeed.spec.ts index a549ffb305..66105e3f8a 100644 --- a/tests/javascript/unit/components/AddFeed.spec.ts +++ b/tests/javascript/unit/components/AddFeed.spec.ts @@ -1,14 +1,23 @@ -import { shallowMount } from '@vue/test-utils' -import { store, localVue } from '../setupStore' +import { shallowMount, createLocalVue } from '@vue/test-utils' -import AddFeed from 'Components/AddFeed.vue' +import AddFeed from '../../../../src/components/AddFeed.vue' describe('AddFeed.vue', () => { 'use strict' it('should initialize without showing createNewFolder', () => { - const wrapper = shallowMount(AddFeed, { localVue, store }) + const localVue = createLocalVue() + const wrapper = shallowMount(AddFeed, { + localVue, + mocks: { + $store: { + state: { + folders: [], + }, + }, + }, + }) - expect(wrapper.vm.$data.createNewFolder).toBeFalsy - }); -}); \ No newline at end of file + expect(wrapper.vm.$data.createNewFolder).toBeFalsy() + }) +}) diff --git a/tests/javascript/unit/components/AdminSettings.spec.ts b/tests/javascript/unit/components/AdminSettings.spec.ts index 91f03e62ed..06e88a6ccf 100644 --- a/tests/javascript/unit/components/AdminSettings.spec.ts +++ b/tests/javascript/unit/components/AdminSettings.spec.ts @@ -1,65 +1,64 @@ -import axios from '@nextcloud/axios'; -import { shallowMount, Wrapper } from '@vue/test-utils'; -import { store, localVue } from '../setupStore'; -import { showError, showSuccess } from '@nextcloud/dialogs'; -import { loadState } from '@nextcloud/initial-state'; +import axios from '@nextcloud/axios' +import { createLocalVue, shallowMount, Wrapper } from '@vue/test-utils' +import { showError, showSuccess } from '@nextcloud/dialogs' +import { loadState } from '@nextcloud/initial-state' import 'regenerator-runtime/runtime' // NOTE: Required for testing password-confirmation? -import AdminSettings from 'Components/AdminSettings.vue'; +import AdminSettings from '../../../../src/components/AdminSettings.vue' - -jest.mock('@nextcloud/axios'); -jest.mock('@nextcloud/initial-state'); -jest.mock('@nextcloud/router'); -jest.mock('@nextcloud/dialogs'); +jest.mock('@nextcloud/axios') +jest.mock('@nextcloud/initial-state') +jest.mock('@nextcloud/router') +jest.mock('@nextcloud/dialogs') describe('AdminSettings.vue', () => { - 'use strict'; + 'use strict' - let wrapper: Wrapper; + let wrapper: Wrapper beforeAll(() => { - jest.useFakeTimers(); - (loadState as any).mockReturnValue(''); - wrapper = shallowMount(AdminSettings, { localVue, store}); - }); + jest.useFakeTimers() + const localVue = createLocalVue(); + (loadState as any).mockReturnValue('') + wrapper = shallowMount(AdminSettings, { localVue }) + }) it('should initialize and fetch settings from state', () => { - expect(loadState).toBeCalledTimes(7); - }); + expect(loadState).toBeCalledTimes(7) + }) it('should send post with updated settings', async () => { jest.spyOn(axios, 'post').mockResolvedValue({ data: {} }); (wrapper.vm as any).handleResponse = jest.fn() - await wrapper.vm.$options?.methods?.update.call(wrapper.vm, 'key', 'val'); + await wrapper.vm.$options?.methods?.update.call(wrapper.vm, 'key', 'val') - expect(axios.post).toBeCalledTimes(1); - }); + expect(axios.post).toBeCalledTimes(1) + }) it('should handle bad response', () => { - (showError as any).mockClear(); - console.error = jest.fn(); + (showError as any).mockClear() + console.error = jest.fn() wrapper.vm.$options?.methods?.handleResponse.call(wrapper.vm, { error: true, errorMessage: 'FAIL', - }); + }) - expect(showError).toBeCalledTimes(1); - }); + expect(showError).toBeCalledTimes(1) + }) it('should handle success response', () => { wrapper.vm.$options?.methods?.handleResponse.call(wrapper.vm, { status: 'ok', }); - (global as any).t = jest.fn(); - jest.runAllTimers(); + (global as any).t = jest.fn() + jest.runAllTimers() - expect(showSuccess).toBeCalledTimes(1); - }); + expect(showSuccess).toBeCalledTimes(1) + }) afterAll(() => { - jest.clearAllMocks(); - jest.useRealTimers(); - }); -}); + jest.clearAllMocks() + jest.useRealTimers() + }) +}) diff --git a/tests/javascript/unit/components/Explore.spec.ts b/tests/javascript/unit/components/Explore.spec.ts index fe547792e8..b2ab052d55 100644 --- a/tests/javascript/unit/components/Explore.spec.ts +++ b/tests/javascript/unit/components/Explore.spec.ts @@ -1,22 +1,32 @@ -import axios from '@nextcloud/axios'; -import { shallowMount } from '@vue/test-utils'; -import { store, localVue } from '../setupStore'; +import axios from '@nextcloud/axios' +import { shallowMount, createLocalVue } from '@vue/test-utils' -import * as router from '@nextcloud/router'; +import * as router from '@nextcloud/router' -import Explore from 'Components/Explore.vue'; +import Explore from '../../../../src/components/Explore.vue' -jest.mock('@nextcloud/axios'); +jest.mock('@nextcloud/axios') describe('Explore.vue', () => { - 'use strict'; + 'use strict' + const localVue = createLocalVue() it('should initialize without showing AddFeed Component', () => { - (axios as any).get.mockResolvedValue({ data: {} }); - (router as any).generateUrl = jest.fn().mockReturnValue(''); + (axios as any).get.mockResolvedValue({ data: { } }); + (router as any).generateUrl = jest.fn().mockReturnValue('') - const wrapper = shallowMount(Explore, { localVue, store }); + const wrapper = shallowMount(Explore, { + localVue, + mocks: { + $store: { + state: { + feeds: [], + folders: [], + }, + }, + }, + }) - expect(wrapper.vm.$data.showAddFeed).toBeFalsy; - }); -}); + expect(wrapper.vm.$data.showAddFeed).toBeFalsy() + }) +}) diff --git a/tests/javascript/unit/components/Sidebar.spec.ts b/tests/javascript/unit/components/Sidebar.spec.ts index 63b490f0ee..1eee5486a2 100644 --- a/tests/javascript/unit/components/Sidebar.spec.ts +++ b/tests/javascript/unit/components/Sidebar.spec.ts @@ -1,46 +1,136 @@ -import { Wrapper, shallowMount } from '@vue/test-utils' -import { store, localVue } from '../setupStore' +import { ACTIONS } from '../../../../src/store' +import { Wrapper, shallowMount, createLocalVue } from '@vue/test-utils' -import AppSidebar from 'Components/Sidebar.vue' +import AppSidebar from '../../../../src/components/Sidebar.vue' describe('Sidebar.vue', () => { 'use strict' - let wrapper: Wrapper; + let wrapper: Wrapper beforeAll(() => { - wrapper = shallowMount(AppSidebar, { localVue, store }) - wrapper.vm.$store.dispatch = jest.fn(); + const localVue = createLocalVue() + wrapper = shallowMount(AppSidebar, { + localVue, + mocks: { + $store: { + state: { + feeds: [], + folders: [], + }, + dispatch: jest.fn(), + }, + }, + }) }) it('should initialize without showing AddFeed Component', () => { - expect(wrapper.vm.$data.showAddFeed).toBeFalsy - }); + expect((wrapper.vm as any).$data.showAddFeed).toBeFalsy() + }) - it('should dispatch message to store with folder name to create new folder', () => { - (wrapper.vm as any).newFolder('abc') - - expect(wrapper.vm.$store.dispatch).toHaveBeenCalledWith('addFolder', { folder: { name: 'abc'} }) - }); + describe('User Actions', () => { + it('should dispatch message to store with folder name to create new folder', () => { + (wrapper.vm as any).newFolder('abc') - it('should dispatch message to store with folder object on delete folder', () => { - const folder = {}; - (wrapper.vm as any).deleteFolder(folder) + expect((wrapper.vm as any).$store.dispatch).toHaveBeenCalledWith(ACTIONS.ADD_FOLDERS, { folder: { name: 'abc' } }) + }) - expect(wrapper.vm.$store.dispatch).toHaveBeenCalledWith('deleteFolder', { folder }) - }) + it('should dispatch message to store with folder object on delete folder', () => { + const folder = {}; + (wrapper.vm as any).deleteFolder(folder) + + expect((wrapper.vm as any).$store.dispatch).toHaveBeenCalledWith(ACTIONS.DELETE_FOLDER, { folder }) + }) + + it('should set showAddFeed to true', () => { + (wrapper.vm as any).showShowAddFeed() + expect(wrapper.vm.$data.showAddFeed).toBeTruthy() + }) - it('should set showAddFeed to true', () => { - (wrapper.vm as any).showShowAddFeed() - expect(wrapper.vm.$data.showAddFeed).toBeTruthy + it('should set showAddFeed to false', () => { + (wrapper.vm as any).closeShowAddFeed() + expect(wrapper.vm.$data.showAddFeed).toBeFalsy() + }) }) - it('should set showAddFeed to false', () => { - (wrapper.vm as any).closeShowAddFeed() - expect(wrapper.vm.$data.showAddFeed).toBeFalsy + describe('SideBarState', () => { + it('should return no top level nav when no folders or feeds', () => { + const topLevelNav = (wrapper.vm.$options.computed?.topLevelNav as any).call({ + $store: { + getters: { + feeds: [], + folders: [], + }, + }, + }) + + expect(topLevelNav).toEqual([]) + }) + + it('should return top level nav with 1 feed', () => { + const feeds: any[] = [{ name: 'feed1', id: 1 }] + const folders: any[] = [] + const topLevelNav = (wrapper.vm.$options.computed?.topLevelNav as any).call({ + $store: { + getters: { + feeds, + folders, + }, + }, + }) + + expect(topLevelNav).toEqual([feeds[0]]) + }) + + it('should return top level nav with 1 folder (with feeds)', () => { + const feeds: any[] = [{ name: 'feed2', id: 2, folderId: 123 }] + const folders: any[] = [{ name: 'abc', id: 123 }] + const topLevelNav = (wrapper.vm.$options.computed?.topLevelNav as any).call({ + $store: { + getters: { + feeds, + folders, + }, + }, + }) + + expect(topLevelNav).toEqual(folders) + }) + + it('should return top level nav with 1 folder (without feed)', () => { + const feeds: any[] = [{ name: 'feed1', id: 1 }] + const folders: any[] = [{ name: 'abc', id: 123 }] + const topLevelNav = (wrapper.vm.$options.computed?.topLevelNav as any).call({ + $store: { + getters: { + feeds, + folders, + }, + }, + }) + + expect(topLevelNav).toEqual([feeds[0], ...folders]) + }) + + it('should return top level nav with feeds and folders', () => { + const feeds: any[] = [{ name: 'feed1', id: 1 }, { name: 'feed2', id: 2, folderId: 123 }] + const folders: any[] = [{ name: 'abc', id: 123 }, { name: 'xyz', id: 234 }] + const topLevelNav = (wrapper.vm.$options.computed?.topLevelNav as any).call({ + $store: { + getters: { + feeds, + folders, + }, + }, + }) + + expect(topLevelNav).toEqual([feeds[0], ...folders]) + }) }) + // TODO: More Template Testing with https://test-utils.vuejs.org/guide/essentials/a-crash-course.html#adding-a-new-todo + afterEach(() => { - jest.clearAllMocks(); - }); + jest.clearAllMocks() + }) }) diff --git a/tests/javascript/unit/setup.ts b/tests/javascript/unit/setup.ts index f3b7349536..510e53baad 100644 --- a/tests/javascript/unit/setup.ts +++ b/tests/javascript/unit/setup.ts @@ -10,11 +10,11 @@ config.mocks.$t = function(_app: any, string: any) { } config.mocks.t = config.mocks.$t -config.mocks.$n = function(app: any, singular: any, plural: any, count: any) { +config.mocks.$n = function(app: any, singular: any) { return singular } config.mocks.n = config.mocks.$n afterAll(() => { - + // TODO: afterAll tests? }) diff --git a/tests/javascript/unit/setupStore.ts b/tests/javascript/unit/setupStore.ts deleted file mode 100644 index 074d591efa..0000000000 --- a/tests/javascript/unit/setupStore.ts +++ /dev/null @@ -1,12 +0,0 @@ -// NOTE: This was copied from nextcloud/tasks repo -import { createLocalVue } from '@vue/test-utils' -import Vuex from 'vuex' - -const localVue = createLocalVue() -localVue.use(Vuex) - -const store = new Vuex.Store({ - modules: { - }, -}) -export { store, localVue } diff --git a/tests/javascript/unit/store/feed.spec.ts b/tests/javascript/unit/store/feed.spec.ts new file mode 100644 index 0000000000..29df4b1054 --- /dev/null +++ b/tests/javascript/unit/store/feed.spec.ts @@ -0,0 +1,55 @@ +import axios from '@nextcloud/axios' +import { Feed } from '../../../../src/types/Feed' +import { AppState } from '../../../../src/store' +import { FEED_ACTION_TYPES, FEED_MUTATION_TYPES, mutations, actions } from '../../../../src/store/feed' + +jest.mock('@nextcloud/axios') + +describe('feed.ts', () => { + 'use strict' + + describe('actions', () => { + it('ADD_FEED should call POST and commit feed to state', async () => { + (axios as any).post.mockResolvedValue() + const commit = jest.fn() + await actions[FEED_ACTION_TYPES.ADD_FEED]({ commit }, { feedReq: { url: '' } }) + expect(axios.post).toBeCalled() + expect(commit).toBeCalled() + }) + + it('FETCH_FEEDS should call GET and commit returned feeds to state', async () => { + (axios as any).get.mockResolvedValue({ data: { feeds: [] } }) + const commit = jest.fn() + await (actions[FEED_ACTION_TYPES.FETCH_FEEDS] as any)({ commit }) + expect(axios.get).toBeCalled() + expect(commit).toBeCalled() + }) + }) + + describe('mutations', () => { + it('SET_FEEDS should add feeds to state', () => { + const state = { feeds: [] as Feed[], folders: [] as any[] } as AppState + let feeds = [] as Feed[] + + mutations[FEED_MUTATION_TYPES.SET_FEEDS](state, feeds) + expect(state.feeds.length).toEqual(0) + + feeds = [{ title: 'test' }] as Feed[] + + mutations[FEED_MUTATION_TYPES.SET_FEEDS](state, feeds) + expect(state.feeds.length).toEqual(1) + expect(state.feeds[0]).toEqual(feeds[0]) + }) + + it('SET_FEEDS should add feeds and unreadCount to folder if exists and folder set', () => { + const state = { feeds: [] as Feed[], folders: [{ id: 1, feedCount: 3, feeds: [] as Feed[] }] } as AppState + const feeds = [{ title: 'test', folderId: 1, unreadCount: 2 }] as Feed[] + + mutations[FEED_MUTATION_TYPES.SET_FEEDS](state, feeds) + expect(state.feeds.length).toEqual(1) + expect(state.feeds[0]).toEqual(feeds[0]) + expect(state.folders[0].feeds[0]).toEqual(feeds[0]) + expect(state.folders[0].feedCount).toEqual(5) + }) + }) +}) diff --git a/tests/javascript/unit/store/folder.spec.ts b/tests/javascript/unit/store/folder.spec.ts new file mode 100644 index 0000000000..68cc04da7a --- /dev/null +++ b/tests/javascript/unit/store/folder.spec.ts @@ -0,0 +1,71 @@ +import axios from '@nextcloud/axios' +import { generateUrl } from '@nextcloud/router' +import { Folder } from '../../../../src/types/Folder' +import { AppState } from '../../../../src/store' +import { FOLDER_ACTION_TYPES, FOLDER_MUTATION_TYPES, mutations, actions } from '../../../../src/store/folder' + +jest.mock('@nextcloud/axios') +jest.mock('@nextcloud/router') + +describe('folder.ts', () => { + 'use strict' + + describe('actions', () => { + it('FETCH_FOLDERS should send GET and then commit folders returned to state', async () => { + (generateUrl as any).mockReturnValue(''); + (axios.get as any).mockResolvedValue({ data: { folders: [] } }) + + const commit = jest.fn() + + await (actions[FOLDER_ACTION_TYPES.FETCH_FOLDERS] as any)({ commit }) + expect(axios.get).toBeCalled() + expect(commit).toBeCalled() + }) + + it('ADD_FOLDERS should send POST and then commit the folders returned to state', async () => { + (axios.post as any).mockResolvedValue({ data: { folders: [] } }) + + const folder = {} as Folder + const commit = jest.fn() + + await actions[FOLDER_ACTION_TYPES.ADD_FOLDERS]({ commit }, { folder }) + expect(axios.post).toBeCalled() + expect(commit).toBeCalled() + }) + + it('DELETE_FOLDER should send DELETE and then commit deleted folder to state', async () => { + (axios.delete as any).mockResolvedValue() + + const folder = {} as Folder + const commit = jest.fn() + + await actions[FOLDER_ACTION_TYPES.DELETE_FOLDER]({ commit }, { folder }) + expect(axios.delete).toBeCalled() + expect(commit).toBeCalled() + }) + }) + + describe('mutations', () => { + it('SET_FOLDERS should add the passed in folders to the state', () => { + const state = { folders: [] as Folder[] } as AppState + let folders = [] as Folder[] + + (mutations[FOLDER_MUTATION_TYPES.SET_FOLDERS] as any)(state, folders) + expect(state.folders.length).toEqual(0) + + folders = [{ name: 'test' }] as Folder[] + + (mutations[FOLDER_MUTATION_TYPES.SET_FOLDERS] as any)(state, folders) + expect(state.folders.length).toEqual(1) + expect(state.folders[0]).toEqual(folders[0]) + }) + + it('DELETE_FOLDER should remove the passed in folder from the state', () => { + const state = { folders: [{ name: 'test' }] as Folder[] } as AppState + const folders = [state.folders[0]] as Folder[] + + (mutations[FOLDER_MUTATION_TYPES.DELETE_FOLDER] as any)(state, folders) + expect(state.folders.length).toEqual(0) + }) + }) +}) diff --git a/webpack.js b/webpack.js index 9b6a70a0bc..25c295596f 100644 --- a/webpack.js +++ b/webpack.js @@ -1,4 +1,5 @@ -const webpackConfig = require('@nextcloud/webpack-vue-config') +const { merge } = require('webpack-merge') +let webpackConfig = require('@nextcloud/webpack-vue-config') const path = require('path') webpackConfig.entry['admin-settings'] = path.join( @@ -7,6 +8,12 @@ webpackConfig.entry['admin-settings'] = path.join( 'main-admin.js', ) +webpackConfig = merge(webpackConfig, { + resolve: { + extensions: ['.ts'], + }, +}) + // Add TS Loader for processing typescript in vue templates webpackConfig.module.rules.push({ test: /.ts$/,