diff options
author | Devlin Junker <devlin.junker@gmail.com> | 2023-08-24 14:52:54 -0700 |
---|---|---|
committer | Benjamin Brahmer <info@b-brahmer.de> | 2023-08-26 07:48:18 +0200 |
commit | 8183510385bad408ea3f0136bd4a5487ba7df526 (patch) | |
tree | f5cb61b97eb62f1cabb5ad506e47e8d982fc3d63 | |
parent | 3f34e3da33e4e20995f33f69ad8e6fed1a7bad72 (diff) |
More cleanup and started on unit tests
Signed-off-by: Devlin Junker <devlin.junker@gmail.com>
-rw-r--r-- | package-lock.json | 14 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | src/App.vue | 1 | ||||
-rw-r--r-- | src/components/FeedItemDisplay.vue | 14 | ||||
-rw-r--r-- | src/components/FeedItemDisplayList.vue | 2 | ||||
-rw-r--r-- | src/components/FeedItemRow.vue | 25 | ||||
-rw-r--r-- | src/components/Starred.vue | 4 | ||||
-rw-r--r-- | src/components/Unread.vue | 12 | ||||
-rw-r--r-- | src/dataservices/item.service.ts | 80 | ||||
-rw-r--r-- | src/store/item.ts | 107 | ||||
-rw-r--r-- | src/types/MutationTypes.ts | 8 | ||||
-rw-r--r-- | tests/javascript/unit/components/AddFeed.spec.ts | 6 | ||||
-rw-r--r-- | tests/javascript/unit/components/FeedItemRow.spec.ts | 3 | ||||
-rw-r--r-- | tests/javascript/unit/components/Starred.spec.ts | 43 | ||||
-rw-r--r-- | tests/javascript/unit/components/Unread.spec.ts | 65 | ||||
-rw-r--r-- | tests/javascript/unit/services/item.service.spec.ts | 66 | ||||
-rw-r--r-- | tests/javascript/unit/store/feed.spec.ts | 20 | ||||
-rw-r--r-- | tests/javascript/unit/store/item.spec.ts | 127 |
18 files changed, 449 insertions, 150 deletions
diff --git a/package-lock.json b/package-lock.json index f8a0bf696..50742389e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@nextcloud/password-confirmation": "^1.0.1", "@nextcloud/router": "^2.0.0", "@nextcloud/vue": "^7.0.1", + "lodash": "^4.17.21", "vue": "^2.6.14", "vue-router": "^3.5.3", "vuex": "^3.6.2" @@ -33,6 +34,7 @@ "@nextcloud/stylelint-config": "^2.1.2", "@nextcloud/webpack-vue-config": "^5.1.0", "@types/jest": "^29.1.1", + "@types/lodash": "^4.14.197", "@types/webpack-env": "^1.17.0", "@typescript-eslint/eslint-plugin": "^5.27.1", "@typescript-eslint/parser": "^5.27.1", @@ -3861,6 +3863,12 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/lodash": { + "version": "4.14.197", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.197.tgz", + "integrity": "sha512-BMVOiWs0uNxHVlHBgzTIqJYmj+PgCo4euloGF+5m4okL3rEYzM2EEv78mw8zWSMM57dM7kVIgJ2QDvwHSoCI5g==", + "dev": true + }, "node_modules/@types/mdast": { "version": "3.0.12", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.12.tgz", @@ -23464,6 +23472,12 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "@types/lodash": { + "version": "4.14.197", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.197.tgz", + "integrity": "sha512-BMVOiWs0uNxHVlHBgzTIqJYmj+PgCo4euloGF+5m4okL3rEYzM2EEv78mw8zWSMM57dM7kVIgJ2QDvwHSoCI5g==", + "dev": true + }, "@types/mdast": { "version": "3.0.12", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.12.tgz", diff --git a/package.json b/package.json index 3d58fb272..ad25bc7d3 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "@nextcloud/password-confirmation": "^1.0.1", "@nextcloud/router": "^2.0.0", "@nextcloud/vue": "^7.0.1", + "lodash": "^4.17.21", "vue": "^2.6.14", "vue-router": "^3.5.3", "vuex": "^3.6.2" @@ -69,6 +70,7 @@ "@nextcloud/stylelint-config": "^2.1.2", "@nextcloud/webpack-vue-config": "^5.1.0", "@types/jest": "^29.1.1", + "@types/lodash": "^4.14.197", "@types/webpack-env": "^1.17.0", "@typescript-eslint/eslint-plugin": "^5.27.1", "@typescript-eslint/parser": "^5.27.1", diff --git a/src/App.vue b/src/App.vue index dafe1a33c..98c3e393c 100644 --- a/src/App.vue +++ b/src/App.vue @@ -25,7 +25,6 @@ export default Vue.extend({ await this.$store.dispatch(ACTIONS.FETCH_FOLDERS) await this.$store.dispatch(ACTIONS.FETCH_FEEDS) await this.$store.dispatch(ACTIONS.FETCH_STARRED) - await this.$store.dispatch(ACTIONS.FETCH_UNREAD) }, }) </script> diff --git a/src/components/FeedItemDisplay.vue b/src/components/FeedItemDisplay.vue index df6fa9199..edb576b5f 100644 --- a/src/components/FeedItemDisplay.vue +++ b/src/components/FeedItemDisplay.vue @@ -15,7 +15,6 @@ </NcActionButton> </NcActions> <StarIcon :class="{'starred': item.starred }" @click="toggleStarred(item)" /> - <Eye @click="markUnread(item)" /> <CloseIcon @click="clearSelected()" /> </div> <div class="article"> @@ -98,20 +97,18 @@ import { mapState } from 'vuex' import ShareVariant from 'vue-material-design-icons/ShareVariant.vue' import StarIcon from 'vue-material-design-icons/Star.vue' import CloseIcon from 'vue-material-design-icons/Close.vue' -import Eye from 'vue-material-design-icons/Eye.vue' import NcActions from '@nextcloud/vue/dist/Components/NcActions.js' import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js' import { Feed } from '../types/Feed' import { FeedItem } from '../types/FeedItem' -import { ACTIONS } from '../store' +import { ACTIONS, MUTATIONS } from '../store' export default Vue.extend({ name: 'FeedItemDisplay', components: { CloseIcon, - Eye, StarIcon, ShareVariant, NcActions, @@ -125,19 +122,15 @@ export default Vue.extend({ }, data: () => { return { - expanded: false, keepUnread: false, } }, computed: { - isExpanded() { - return this.expanded - }, ...mapState(['feeds']), }, methods: { clearSelected() { - this.$store.dispatch(ACTIONS.SET_SELECTED_ITEM, { id: undefined }) + this.$store.commit(MUTATIONS.SET_SELECTED_ITEM, { id: undefined }) }, formatDate(epoch: number) { return new Date(epoch).toLocaleString() @@ -158,9 +151,6 @@ export default Vue.extend({ toggleStarred(item: FeedItem): void { this.$store.dispatch(item.starred ? ACTIONS.UNSTAR_ITEM : ACTIONS.STAR_ITEM, { item }) }, - markUnread(item: FeedItem): void { - this.$store.dispatch(ACTIONS.MARK_UNREAD, { item }) - }, }, }) diff --git a/src/components/FeedItemDisplayList.vue b/src/components/FeedItemDisplayList.vue index 8d3649c62..e86aa93c1 100644 --- a/src/components/FeedItemDisplayList.vue +++ b/src/components/FeedItemDisplayList.vue @@ -51,7 +51,7 @@ export default Vue.extend({ return this.$store.getters.selected }, reachedEnd(): boolean { - return this.mounted && this.$store.state.items.allItemsLoaded[this.fetchKey] !== undefined && this.$store.state.items.allItemsLoaded[this.fetchKey] + return this.mounted && this.$store.state.items.allItemsLoaded[this.fetchKey] === true }, }, mounted() { diff --git a/src/components/FeedItemRow.vue b/src/components/FeedItemRow.vue index 04d4a9343..c7fcfd6ec 100644 --- a/src/components/FeedItemRow.vue +++ b/src/components/FeedItemRow.vue @@ -13,9 +13,9 @@ <span v-if="getFeed(item.feedId).faviconLink" style="width: 24px; background-size: contain;" :style="{ 'backgroundImage': 'url(' + getFeed(item.feedId).faviconLink + ')' }" /> </div> <div class="title-container" :class="{ 'unread': item.unread }"> - <span :style="{ 'white-space': !isExpanded ? 'nowrap' : 'normal' }" :dir="item.rtl && 'rtl'"> + <span style="white-space: nowrap" :dir="item.rtl && 'rtl'"> {{ item.title }} - <span v-if="!isExpanded" class="intro" v-html="item.intro" /> + <span class="intro" v-html="item.intro" /> </span> </div> <div class="date-container"> @@ -25,7 +25,9 @@ </div> <div class="button-container" @click="$event.stopPropagation()"> <StarIcon :class="{'starred': item.starred }" @click="toggleStarred(item)" /> - <Eye :class="{ 'keep-unread': keepUnread }" @click="toggleKeepUnread(item)" /> + <EyeIcon v-if="item.unread && !keepUnread" @click="toggleKeepUnread(item)" /> + <EyeCheckIcon v-if="!item.unread && !keepUnread" @click="toggleKeepUnread(item)" /> + <EyeLockIcon v-if="keepUnread" class="keep-unread" @click="toggleKeepUnread(item)" /> <NcActions :force-menu="true"> <template #icon> <ShareVariant /> @@ -49,7 +51,9 @@ import { mapState } from 'vuex' import EarthIcon from 'vue-material-design-icons/Earth.vue' import StarIcon from 'vue-material-design-icons/Star.vue' -import Eye from 'vue-material-design-icons/Eye.vue' +import EyeIcon from 'vue-material-design-icons/Eye.vue' +import EyeCheckIcon from 'vue-material-design-icons/EyeCheck.vue' +import EyeLockIcon from 'vue-material-design-icons/EyeLock.vue' import RssIcon from 'vue-material-design-icons/Rss.vue' import ShareVariant from 'vue-material-design-icons/ShareVariant.vue' @@ -58,14 +62,16 @@ import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js' import { Feed } from '../types/Feed' import { FeedItem } from '../types/FeedItem' -import { ACTIONS } from '../store' +import { ACTIONS, MUTATIONS } from '../store' export default Vue.extend({ name: 'FeedItemRow', components: { EarthIcon, StarIcon, - Eye, + EyeIcon, + EyeCheckIcon, + EyeLockIcon, ShareVariant, RssIcon, NcActions, @@ -87,8 +93,7 @@ export default Vue.extend({ }, methods: { select(): void { - this.$store.dispatch(ACTIONS.SET_SELECTED_ITEM, { id: this.item.id }) - // this.expanded = !this.expanded + this.$store.commit(MUTATIONS.SET_SELECTED_ITEM, { id: this.item.id }) this.markRead(this.item) }, formatDate(epoch: number): string { @@ -228,4 +233,8 @@ export default Vue.extend({ .material-design-icon.starred:hover { color: #555555; } + + .eye-check-icon { + color: var(--color-primary-light); + } </style> diff --git a/src/components/Starred.vue b/src/components/Starred.vue index d1aba4921..29e7d3bf3 100644 --- a/src/components/Starred.vue +++ b/src/components/Starred.vue @@ -20,7 +20,7 @@ import NcCounterBubble from '@nextcloud/vue/dist/Components/NcCounterBubble.js' import FeedItemDisplayList from './FeedItemDisplayList.vue' import { FeedItem } from '../types/FeedItem' -import { ACTIONS } from '../store' +import { ACTIONS, MUTATIONS } from '../store' export default Vue.extend({ components: { @@ -35,7 +35,7 @@ export default Vue.extend({ }, }, created() { - this.$store.dispatch(ACTIONS.SET_SELECTED_ITEM, { id: undefined }) + this.$store.commit(MUTATIONS.SET_SELECTED_ITEM, { id: undefined }) }, methods: { async fetchMore() { diff --git a/src/components/Unread.vue b/src/components/Unread.vue index 324b20d32..195623245 100644 --- a/src/components/Unread.vue +++ b/src/components/Unread.vue @@ -7,7 +7,10 @@ </NcCounterBubble> </div> - <FeedItemDisplayList :items="unread()" :fetch-key="'starred'" @load-more="fetchMore()" /> + <FeedItemDisplayList v-if="unread()" + :items="unread()" + :fetch-key="'unread'" + @load-more="fetchMore()" /> </div> </template> @@ -20,7 +23,7 @@ import NcCounterBubble from '@nextcloud/vue/dist/Components/NcCounterBubble.js' import FeedItemDisplayList from './FeedItemDisplayList.vue' import { FeedItem } from '../types/FeedItem' -import { ACTIONS } from '../store' +import { ACTIONS, MUTATIONS } from '../store' type UnreadItemState = { unreadCache?: FeedItem[] @@ -40,7 +43,10 @@ export default Vue.extend({ ...mapState(['items']), }, created() { - this.$store.dispatch(ACTIONS.SET_SELECTED_ITEM, { id: undefined }) + this.$store.commit(MUTATIONS.SET_SELECTED_ITEM, { id: undefined }) + if (this.unread() === undefined) { + this.$store.dispatch(ACTIONS.FETCH_UNREAD) + } }, methods: { unread() { diff --git a/src/dataservices/item.service.ts b/src/dataservices/item.service.ts new file mode 100644 index 000000000..cddd1e978 --- /dev/null +++ b/src/dataservices/item.service.ts @@ -0,0 +1,80 @@ +import _ from 'lodash' +import { AxiosResponse } from 'axios' +import axios from '@nextcloud/axios' + +import { API_ROUTES } from '../types/ApiRoutes' +import { FeedItem } from '../types/FeedItem' + +export const ITEM_TYPES = { + STARRED: 2, + UNREAD: 6, +} + +export class ItemService { + + static debounceFetchStarred = _.debounce(ItemService.fetchStarred, 400, { leading: true }) + static debounceFetchUnread = _.debounce(ItemService.fetchUnread, 400, { leading: true }) + + /** + * Makes backend call to retrieve starred items + * + * @param start (id of last starred item loaded) + * @return {AxiosResponse} response object containing backend request response + */ + static async fetchStarred(start: number): Promise<AxiosResponse> { + return await axios.get(API_ROUTES.ITEMS, { + params: { + limit: 40, + oldestFirst: false, + search: '', + showAll: false, + type: ITEM_TYPES.STARRED, + offset: start, + }, + }) + } + + /** + * Makes backend call to retrieve unread items + * + * @param start (id of last unread item loaded) + * @return {AxiosResponse} response object containing backend request response + */ + static async fetchUnread(start: number): Promise<AxiosResponse> { + return await axios.get(API_ROUTES.ITEMS, { + params: { + limit: 40, + oldestFirst: false, + search: '', + showAll: false, + type: ITEM_TYPES.UNREAD, + offset: start, + }, + }) + } + + /** + * Makes backend call to mark item as read/unread in DB + * + * @param {FeedItem} item FeedItem (containing id) that wil be marked as read/unread + * @param {boolean} read if read or not + */ + static async markRead(item: FeedItem, read: boolean): Promise<void> { + axios.post(API_ROUTES.ITEMS + `/${item.id}/read`, { + isRead: read, + }) + } + + /** + * Makes backend call to mark item as starred/unstarred in DB + * + * @param {FeedItem} item FeedItem (containing id) that wil be marked as starred/unstarred + * @param {boolean} read if starred or not + */ + static async markStarred(item: FeedItem, read: boolean): Promise<void> { + axios.post(API_ROUTES.ITEMS + `/${item.feedId}/${item.guidHash}/star`, { + isStarred: read, + }) + } + +} diff --git a/src/store/item.ts b/src/store/item.ts index de30f6778..982e75979 100644 --- a/src/store/item.ts +++ b/src/store/item.ts @@ -1,9 +1,8 @@ -import axios from '@nextcloud/axios' - import { ActionParams } from '../store' import { FEED_ITEM_MUTATION_TYPES } from '../types/MutationTypes' -import { API_ROUTES } from '../types/ApiRoutes' + import { FeedItem } from '../types/FeedItem' +import { ItemService } from '../dataservices/item.service' export const FEED_ITEM_ACTION_TYPES = { FETCH_STARRED: 'FETCH_STARRED', @@ -12,7 +11,6 @@ export const FEED_ITEM_ACTION_TYPES = { MARK_UNREAD: 'MARK_UNREAD', STAR_ITEM: 'STAR_ITEM', UNSTAR_ITEM: 'UNSTAR_ITEM', - SET_SELECTED_ITEM: 'SET_SELECTED_ITEM', } export type ItemState = { @@ -53,56 +51,35 @@ const getters = { } export const actions = { - async [FEED_ITEM_ACTION_TYPES.SET_SELECTED_ITEM]({ commit }: ActionParams, { id }: { id: string }) { - state.selectedId = id - }, async [FEED_ITEM_ACTION_TYPES.FETCH_UNREAD]({ commit }: ActionParams, { start }: { start: number } = { start: 0 }) { - if (state.allItems.filter((item) => item.unread).length === 0) { - state.fetchingItems.unread = true - } - const response = await axios.get(API_ROUTES.ITEMS, { - params: { - limit: 40, - oldestFirst: false, - search: '', - showAll: false, - type: 6, - offset: start, - }, - }) - - commit(FEED_ITEM_MUTATION_TYPES.SET_ITEMS, response.data.items) - - if (response.data.items.length < 40) { - state.allItemsLoaded.unread = true + commit(FEED_ITEM_MUTATION_TYPES.SET_FETCHING, { key: 'unread', fetching: true }) + + const response = await ItemService.debounceFetchUnread(start) + + commit(FEED_ITEM_MUTATION_TYPES.SET_ITEMS, response?.data.items) + + if (response?.data.items.length < 40) { + commit(FEED_ITEM_MUTATION_TYPES.SET_ALL_LOADED, { key: 'unread', loaded: true }) } - state.fetchingItems.unread = false + commit(FEED_ITEM_MUTATION_TYPES.SET_FETCHING, { key: 'unread', fetching: false }) }, async [FEED_ITEM_ACTION_TYPES.FETCH_STARRED]({ commit }: ActionParams, { start }: { start: number } = { start: 0 }) { - state.fetchingItems.starred = true - const response = await axios.get(API_ROUTES.ITEMS, { - params: { - limit: 40, - oldestFirst: false, - search: '', - showAll: false, - type: 2, - offset: start, - }, - }) - - commit(FEED_ITEM_MUTATION_TYPES.SET_ITEMS, response.data.items) - commit(FEED_ITEM_MUTATION_TYPES.SET_STARRED_COUNT, response.data.starred) - - if (response.data.items.length < 40) { - state.starredLoaded = true + commit(FEED_ITEM_MUTATION_TYPES.SET_FETCHING, { key: 'starred', fetching: true }) + const response = await ItemService.debounceFetchStarred(start) + + commit(FEED_ITEM_MUTATION_TYPES.SET_ITEMS, response?.data.items) + if (response?.data.starred) { + commit(FEED_ITEM_MUTATION_TYPES.SET_STARRED_COUNT, response?.data.starred) + } + + if (response?.data.items.length < 40) { + commit(FEED_ITEM_MUTATION_TYPES.SET_ALL_LOADED, { key: 'starred', loaded: true }) } - state.fetchingItems.starred = false + commit(FEED_ITEM_MUTATION_TYPES.SET_FETCHING, { key: 'starred', fetching: false }) }, [FEED_ITEM_ACTION_TYPES.MARK_READ]({ commit }: ActionParams, { item }: { item: FeedItem}) { - axios.post(API_ROUTES.ITEMS + `/${item.id}/read`, { - isRead: true, - }) + ItemService.markRead(item, true) + if (item.unread) { commit(FEED_ITEM_MUTATION_TYPES.SET_UNREAD_COUNT, state.unreadCount - 1) } @@ -110,9 +87,8 @@ export const actions = { commit(FEED_ITEM_MUTATION_TYPES.UPDATE_ITEM, { item }) }, [FEED_ITEM_ACTION_TYPES.MARK_UNREAD]({ commit }: ActionParams, { item }: { item: FeedItem}) { - axios.post(API_ROUTES.ITEMS + `/${item.id}/read`, { - isRead: false, - }) + ItemService.markRead(item, false) + if (!item.unread) { commit(FEED_ITEM_MUTATION_TYPES.SET_UNREAD_COUNT, state.unreadCount + 1) } @@ -120,17 +96,15 @@ export const actions = { commit(FEED_ITEM_MUTATION_TYPES.UPDATE_ITEM, { item }) }, [FEED_ITEM_ACTION_TYPES.STAR_ITEM]({ commit }: ActionParams, { item }: { item: FeedItem}) { - axios.post(API_ROUTES.ITEMS + `/${item.feedId}/${item.guidHash}/star`, { - isStarred: true, - }) + ItemService.markStarred(item, true) + item.starred = true commit(FEED_ITEM_MUTATION_TYPES.UPDATE_ITEM, { item }) commit(FEED_ITEM_MUTATION_TYPES.SET_STARRED_COUNT, state.starredCount + 1) }, [FEED_ITEM_ACTION_TYPES.UNSTAR_ITEM]({ commit }: ActionParams, { item }: { item: FeedItem}) { - axios.post(API_ROUTES.ITEMS + `/${item.feedId}/${item.guidHash}/star`, { - isStarred: false, - }) + ItemService.markStarred(item, false) + item.starred = false commit(FEED_ITEM_MUTATION_TYPES.UPDATE_ITEM, { item }) commit(FEED_ITEM_MUTATION_TYPES.SET_STARRED_COUNT, state.starredCount - 1) @@ -138,12 +112,17 @@ export const actions = { } export const mutations = { + [FEED_ITEM_MUTATION_TYPES.SET_SELECTED_ITEM](state: ItemState, { id }: { id: string }) { + state.selectedId = id + }, [FEED_ITEM_MUTATION_TYPES.SET_ITEMS](state: ItemState, items: FeedItem[]) { - items.forEach(it => { - if (state.allItems.find((existing: FeedItem) => existing.id === it.id) === undefined) { - state.allItems.push(it) - } - }) + if (items) { + items.forEach(it => { + if (state.allItems.find((existing: FeedItem) => existing.id === it.id) === undefined) { + state.allItems.push(it) + } + }) + } }, [FEED_ITEM_MUTATION_TYPES.SET_STARRED_COUNT](state: ItemState, count: number) { state.starredCount = count @@ -155,6 +134,12 @@ export const mutations = { const idx = state.allItems.findIndex((it) => it.id === item.id) state.allItems.splice(idx, 1, item) }, + [FEED_ITEM_MUTATION_TYPES.SET_FETCHING](state: ItemState, { fetching, key }: { fetching: boolean; key: string; }) { + state.fetchingItems[key] = fetching + }, + [FEED_ITEM_MUTATION_TYPES.SET_ALL_LOADED](state: ItemState, { loaded, key }: { loaded: boolean; key: string; }) { + state.allItemsLoaded[key] = loaded + }, } export default { diff --git a/src/types/MutationTypes.ts b/src/types/MutationTypes.ts index 7176366e5..9d725c0c3 100644 --- a/src/types/MutationTypes.ts +++ b/src/types/MutationTypes.ts @@ -10,8 +10,12 @@ export const FOLDER_MUTATION_TYPES = { export const FEED_ITEM_MUTATION_TYPES = { SET_ITEMS: 'SET_ITEMS', + UPDATE_ITEM: 'UPDATE_ITEM', + SET_SELECTED_ITEM: 'SET_SELECTED_ITEM', + SET_STARRED_COUNT: 'SET_STARRED_COUNT', SET_UNREAD_COUNT: 'SET_UNREAD_COUNT', - UPDATE_ITEM: 'UPDATE_ITEM', - SET_FETCHING: 'SET_FETCHING' + + SET_FETCHING: 'SET_FETCHING', + SET_ALL_LOADED: 'SET_ALL_LOADED', } diff --git a/tests/javascript/unit/components/AddFeed.spec.ts b/tests/javascript/unit/components/AddFeed.spec.ts index daf50329e..169d9b5a2 100644 --- a/tests/javascript/unit/components/AddFeed.spec.ts +++ b/tests/javascript/unit/components/AddFeed.spec.ts @@ -47,13 +47,13 @@ describe('AddFeed.vue', () => { expect(response).toBeFalsy() - wrapper.vm.$data.feedUrl = 'http://test.com' + wrapper.vm.$data.feedUrl = 'http://example.com' response = wrapper.vm.feedUrlExists() expect(response).toBeFalsy() - wrapper.vm.$data.feedUrl = 'http://test.com' - wrapper.vm.$store.state.feeds.feeds = [{ url: 'http://test.com' }] + wrapper.vm.$data.feedUrl = 'http://example.com' + wrapper.vm.$store.state.feeds.feeds = [{ url: 'http://example.com' }] response = wrapper.vm.feedUrlExists() expect(response).toBeTruthy() diff --git a/tests/javascript/unit/components/FeedItemRow.spec.ts b/tests/javascript/unit/components/FeedItemRow.spec.ts index 9abe384d6..14c2d6cca 100644 --- a/tests/javascript/unit/components/FeedItemRow.spec.ts +++ b/tests/javascript/unit/components/FeedItemRow.spec.ts @@ -34,6 +34,7 @@ describe('FeedItemRow.vue', () => { folders: [], }, dispatch: dispatchStub, + commit: jest.fn(), }, }, }) @@ -50,7 +51,7 @@ describe('FeedItemRow.vue', () => { it('should expand when clicked', async () => { await wrapper.find('.feed-item-row').trigger('click') - expect(wrapper.vm.$data.expanded).toBe(true) + // expect(wrapper.vm.$data.expanded).toBe(true) }) it('should format date correctly', () => { diff --git a/tests/javascript/unit/components/Starred.spec.ts b/tests/javascript/unit/components/Starred.spec.ts index 36d4e53a1..4f09f4e3c 100644 --- a/tests/javascript/unit/components/Starred.spec.ts +++ b/tests/javascript/unit/components/Starred.spec.ts @@ -2,12 +2,11 @@ import Vuex, { Store } from 'vuex' import { shallowMount, createLocalVue, Wrapper } from '@vue/test-utils' import Starred from '../../../../src/components/Starred.vue' -import VirtualScroll from '../../../../src/components/VirtualScroll.vue' -import FeedItemRow from '../../../../src/components/FeedItemRow.vue' +import FeedItemDisplayList from '../../../../src/components/FeedItemDisplayList.vue' jest.mock('@nextcloud/axios') -describe('Explore.vue', () => { +describe('Starred.vue', () => { 'use strict' const localVue = createLocalVue() localVue.use(Vuex) @@ -24,7 +23,9 @@ describe('Explore.vue', () => { store = new Vuex.Store({ state: { items: { - starredLoaded: false, + fetchingItems: { + starred: false, + }, }, }, actions: { @@ -33,6 +34,10 @@ describe('Explore.vue', () => { starred: () => [mockItem], }, }) + + store.dispatch = jest.fn() + store.commit = jest.fn() + wrapper = shallowMount(Starred, { propsData: { item: mockItem, @@ -42,34 +47,12 @@ describe('Explore.vue', () => { }) }) - it('should initialize with mounted flag set', () => { - expect(wrapper.vm.$data.mounted).toBeTruthy() - }) - it('should get starred items from state', () => { - expect((wrapper.findAllComponents(FeedItemRow).length)).toEqual(1) + expect((wrapper.findComponent(FeedItemDisplayList)).props().items.length).toEqual(1) }) - it('should check starredLoaded and mounted to determine if the virtual scroll has reached end ', () => { - wrapper.vm.$store.state.items.starredLoaded = false - expect((wrapper.findComponent(VirtualScroll)).props().reachedEnd).toEqual(false) - - wrapper.vm.$store.state.items.starredLoaded = true - store.state.items.starredLoaded = true - - wrapper = shallowMount(Starred, { - propsData: { - item: mockItem, - }, - data: () => { - return { - mounted: true, - } - }, - localVue, - store, - }) - - expect((wrapper.findComponent(VirtualScroll)).props().reachedEnd).toEqual(true) + it('should dispatch FETCH_STARRED action if not fetchingItems.starred', () => { + (wrapper.vm as any).fetchMore() + expect(store.dispatch).toBeCalled() }) }) diff --git a/tests/javascript/unit/components/Unread.spec.ts b/tests/javascript/unit/components/Unread.spec.ts new file mode 100644 index 000000000..6de4d8565 --- /dev/null +++ b/tests/javascript/unit/components/Unread.spec.ts @@ -0,0 +1,65 @@ +import Vuex, { Store } from 'vuex' +import { shallowMount, createLocalVue, Wrapper } from '@vue/test-utils' + +import Unread from '../../../../src/components/Unread.vue' +import FeedItemDisplayList from '../../../../src/components/FeedItemDisplayList.vue' + +jest.mock('@nextcloud/axios') + +describe('Unread.vue', () => { + 'use strict' + const localVue = createLocalVue() + localVue.use(Vuex) + let wrapper: Wrapper<Unread> + + const mockItem = { + feedId: 1, + title: 'feed item', + pubDate: Date.now() / 1000, + } + + let store: Store<any> + beforeAll(() => { + store = new Vuex.Store({ + state: { + items: { + fetchingItems: { + unread: false, + }, + }, + }, + actions: { + }, + getters: { + unread: () => [mockItem, mockItem], + }, + }) + + store.dispatch = jest.fn() + store.commit = jest.fn() + + wrapper = shallowMount(Unread, { + propsData: { + item: mockItem, + }, + localVue, + store, + }) + }) + + it('should get unread items from state', () => { + expect((wrapper.findComponent(FeedItemDisplayList)).props().items.length).toEqual(2) + }) + + it('should dispatch FETCH_UNREAD action if not fetchingItems.unread', () => { + (wrapper.vm as any).$store.state.items.fetchingItems.unread = true; + + (wrapper.vm as any).fetchMore() + expect(store.dispatch).not.toBeCalled(); + + (wrapper.vm as any).$store.state.items.fetchingItems.unread = false; + + (wrapper.vm as any).fetchMore() + expect(store.dispatch).toBeCalled() + }) +}) diff --git a/tests/javascript/unit/services/item.service.spec.ts b/tests/javascript/unit/services/item.service.spec.ts new file mode 100644 index 000000000..29de8f1a3 --- /dev/null +++ b/tests/javascript/unit/services/item.service.spec.ts @@ -0,0 +1,66 @@ +import { ITEM_TYPES, ItemService } from '../../../../src/dataservices/item.service' +import axios from '@nextcloud/axios' + +jest.mock('@nextcloud/axios') + +describe('item.service.ts', () => { + 'use strict' + + beforeEach(() => { + (axios.get as any).mockReset(); + (axios.post as any).mockReset() + }) + + describe('fetchStarred', () => { + it('should call GET with offset set to start param', async () => { + (axios as any).get.mockResolvedValue({ data: { feeds: [] } }) + + await ItemService.fetchStarred(0) + + expect(axios.get).toBeCalled() + const queryParams = (axios.get as any).mock.calls[0][1].params + + expect(queryParams.offset).toEqual(0) + expect(queryParams.type).toEqual(ITEM_TYPES.STARRED) + }) + }) + + describe('fetchUnread', () => { + it('should call GET with offset set to start param', async () => { + (axios as any).get.mockResolvedValue({ data: { feeds: [] } }) + + await ItemService.fetchUnread(2) + + expect(axios.get).toBeCalled() + const queryParams = (axios.get as any).mock.calls[0][1].params + + expect(queryParams.offset).toEqual(2) + expect(queryParams.type).toEqual(ITEM_TYPES.UNREAD) + }) + }) + + describe('markRead', () => { + it('should call POST with item id in URL and read param', async () => { + await ItemService.markRead({ id: 123 } as any, true) + + expect(axios.post).toBeCalled() + const args = (axios.post as any).mock.calls[0] + + expect(args[0]).toContain('123') + expect(args[1].isRead).toEqual(true) + }) + }) + + describe('markStarred', () => { + it('should call POST with item feedId and guidHash in URL and read param', async () => { + await ItemService.markStarred({ feedId: 1, guidHash: 'abc' } as any, false) + + expect(axios.post).toBeCalled() + const args = (axios.post as any).mock.calls[0] + + expect(args[0]).toContain('1') + expect(args[0]).toContain('abc') + expect(args[1].isStarred).toEqual(false) + }) + }) +}) diff --git a/tests/javascript/unit/store/feed.spec.ts b/tests/javascript/unit/store/feed.spec.ts index 922c79f3f..3a7521f89 100644 --- a/tests/javascript/unit/store/feed.spec.ts +++ b/ |