summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--package-lock.json14
-rw-r--r--package.json2
-rw-r--r--src/App.vue1
-rw-r--r--src/components/FeedItemDisplay.vue14
-rw-r--r--src/components/FeedItemDisplayList.vue2
-rw-r--r--src/components/FeedItemRow.vue25
-rw-r--r--src/components/Starred.vue4
-rw-r--r--src/components/Unread.vue12
-rw-r--r--src/dataservices/item.service.ts80
-rw-r--r--src/store/item.ts107
-rw-r--r--src/types/MutationTypes.ts8
-rw-r--r--tests/javascript/unit/components/AddFeed.spec.ts6
-rw-r--r--tests/javascript/unit/components/FeedItemRow.spec.ts3
-rw-r--r--tests/javascript/unit/components/Starred.spec.ts43
-rw-r--r--tests/javascript/unit/components/Unread.spec.ts65
-rw-r--r--tests/javascript/unit/services/item.service.spec.ts66
-rw-r--r--tests/javascript/unit/store/feed.spec.ts20
-rw-r--r--tests/javascript/unit/store/item.spec.ts127
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/tests/javascript/unit/store/feed.spec.ts
@@ -3,7 +3,7 @@ import { Feed } from '../../../../src/types/Feed'
import { AppState } from '../../../../src/store'
import { FEED_ACTION_TYPES, mutations, actions } from '../../../../src/store/feed'
-import { FEED_MUTATION_TYPES } from '../../../../src/types/MutationTypes'
+import { FEED_ITEM_MUTATION_TYPES, FEED_MUTATION_TYPES } from '../../../../src/types/MutationTypes'
jest.mock('@nextcloud/axios')
@@ -11,6 +11,17 @@ describe('feed.ts', () => {
'use strict'
describe('actions', () => {
+ describe('FETCH_FEEDS', () => {
+ it('should call GET and commit returned feeds to state', async () => {
+ (axios as any).get.mockResolvedValue({ data: { feeds: [] } })
+ const commit = jest.fn()