summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/Unit/Service/FolderServiceTest.php13
-rw-r--r--tests/javascript/helpers/CSSStub.js0
-rw-r--r--tests/javascript/unit/OC.ts38
-rw-r--r--tests/javascript/unit/components/AddFeed.spec.ts91
-rw-r--r--tests/javascript/unit/components/AdminSettings.spec.ts64
-rw-r--r--tests/javascript/unit/components/App.spec.ts58
-rw-r--r--tests/javascript/unit/components/ShareItem.spec.ts75
-rw-r--r--tests/javascript/unit/components/Sidebar.spec.ts188
-rw-r--r--tests/javascript/unit/components/SidebarFeedLinkActions.spec.ts95
-rw-r--r--tests/javascript/unit/components/feed-display/FeedItemDisplay.spec.ts99
-rw-r--r--tests/javascript/unit/components/feed-display/FeedItemDisplayList.spec.ts66
-rw-r--r--tests/javascript/unit/components/feed-display/FeedItemRow.spec.ts127
-rw-r--r--tests/javascript/unit/components/routes/All.spec.ts65
-rw-r--r--tests/javascript/unit/components/routes/Explore.spec.ts32
-rw-r--r--tests/javascript/unit/components/routes/Feed.spec.ts75
-rw-r--r--tests/javascript/unit/components/routes/Folder.spec.ts89
-rw-r--r--tests/javascript/unit/components/routes/Starred.spec.ts58
-rw-r--r--tests/javascript/unit/components/routes/Unread.spec.ts65
-rw-r--r--tests/javascript/unit/services/feed.service.spec.ts69
-rw-r--r--tests/javascript/unit/services/folder.service.spec.ts58
-rw-r--r--tests/javascript/unit/services/item.service.spec.ts110
-rw-r--r--tests/javascript/unit/services/share.service.spec.ts46
-rw-r--r--tests/javascript/unit/setup.ts21
-rw-r--r--tests/javascript/unit/store/app.spec.ts26
-rw-r--r--tests/javascript/unit/store/feed.spec.ts221
-rw-r--r--tests/javascript/unit/store/folder.spec.ts106
-rw-r--r--tests/javascript/unit/store/item.spec.ts243
m---------tests/test_helper/bats-assert0
m---------tests/test_helper/php-feed-generator0
29 files changed, 2196 insertions, 2 deletions
diff --git a/tests/Unit/Service/FolderServiceTest.php b/tests/Unit/Service/FolderServiceTest.php
index 3e7e98041..3e47c6136 100644
--- a/tests/Unit/Service/FolderServiceTest.php
+++ b/tests/Unit/Service/FolderServiceTest.php
@@ -65,13 +65,22 @@ class FolderServiceTest extends TestCase
protected function setUp(): void
{
$this->time = 222;
+
$timeFactory = $this->getMockBuilder(TimeFactory::class)
->disableOriginalConstructor()
->getMock();
- $timeFactory->expects($this->any())
- ->method('getTime')
+
+ $mockDateTime = $this->getMockBuilder(\DateTimeImmutable::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $mockDateTime->expects($this->any())
+ ->method('getTimestamp')
->will($this->returnValue($this->time));
+ $timeFactory->expects($this->any())
+ ->method('now')
+ ->will($this->returnValue($mockDateTime));
+
$this->feedService = $this->getMockBuilder(FeedServiceV2::class)
->disableOriginalConstructor()
->getMock();
diff --git a/tests/javascript/helpers/CSSStub.js b/tests/javascript/helpers/CSSStub.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/javascript/helpers/CSSStub.js
diff --git a/tests/javascript/unit/OC.ts b/tests/javascript/unit/OC.ts
new file mode 100644
index 000000000..cbc7c642e
--- /dev/null
+++ b/tests/javascript/unit/OC.ts
@@ -0,0 +1,38 @@
+export class OC {
+
+ generateUrl(url: any) {
+ return ''
+ }
+
+ imagePath(app: any, img: any) {
+ return ''
+ }
+
+ linkToRemote(app: any) {
+ return ''
+ }
+
+ getLanguage() {
+ return 'en-GB'
+ }
+
+ getLocale() {
+ return 'en_GB'
+ }
+
+ isUserAdmin() {
+ return false
+ }
+
+ L10N = {
+ translate(app: any, text: any) {
+ return text
+ },
+
+ translatePlural(app: any, text: any) {
+ return text
+ },
+ }
+
+ config = {}
+}
diff --git a/tests/javascript/unit/components/AddFeed.spec.ts b/tests/javascript/unit/components/AddFeed.spec.ts
new file mode 100644
index 000000000..48be08a79
--- /dev/null
+++ b/tests/javascript/unit/components/AddFeed.spec.ts
@@ -0,0 +1,91 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils'
+import AddFeed from '../../../../src/components/AddFeed.vue'
+import { FEED_ACTION_TYPES } from '../../../../src/store/feed'
+
+describe('AddFeed.vue', () => {
+ 'use strict'
+
+ const mockDispatch = jest.fn()
+ const mockStore = {
+ state: {
+ folders: { folders: [] },
+ feeds: { feeds: [] },
+ },
+ dispatch: mockDispatch,
+ }
+
+ let wrapper: any
+ beforeEach(() => {
+ const localVue = createLocalVue()
+ wrapper = shallowMount(AddFeed, {
+ localVue,
+ mocks: {
+ $route: {
+ query: {
+ subscribe_to: undefined,
+ },
+ },
+ $store: mockStore,
+ },
+ })
+ })
+
+ it('should initialize with default values', () => {
+ expect(wrapper.vm.$data.createNewFolder).toBeFalsy()
+ expect(wrapper.vm.$data.autoDiscover).toBeTruthy()
+ expect(wrapper.vm.$data.withBasicAuth).toBeFalsy()
+ })
+
+ it('should dispatch ADD_FEED action to store', async () => {
+ wrapper.vm.$emit = jest.fn()
+
+ await wrapper.vm.addFeed()
+
+ expect(wrapper.vm.$emit).toBeCalled()
+ expect(mockDispatch).toBeCalled()
+ expect(mockDispatch.mock.calls[0][0]).toEqual(FEED_ACTION_TYPES.ADD_FEED)
+ })
+
+ it('should check if feed url exists and return true', () => {
+ wrapper.vm.$data.feedUrl = ''
+ let response = wrapper.vm.feedUrlExists()
+
+ expect(response).toBeFalsy()
+
+ wrapper.vm.$data.feedUrl = 'http://example.com'
+ response = wrapper.vm.feedUrlExists()
+
+ expect(response).toBeFalsy()
+
+ wrapper.vm.$data.feedUrl = 'http://example.com'
+ wrapper.vm.$store.state.feeds.feeds = [{ url: 'http://example.com' }]
+ response = wrapper.vm.feedUrlExists()
+
+ expect(response).toBeTruthy()
+ })
+
+ it('should check if folder name exists when creating folder and return true', () => {
+ wrapper.vm.$data.newFolderName = ''
+ let response = wrapper.vm.folderNameExists()
+
+ expect(response).toBeFalsy()
+
+ wrapper.vm.$data.newFolderName = 'test'
+ response = wrapper.vm.folderNameExists()
+
+ expect(response).toBeFalsy()
+
+ wrapper.vm.$data.newFolderName = 'test'
+ wrapper.vm.$store.state.folders.folders = [{ name: 'test' }]
+ response = wrapper.vm.folderNameExists()
+
+ expect(response).toBeFalsy()
+
+ wrapper.vm.$data.newFolderName = 'test'
+ wrapper.vm.$data.createNewFolder = 'test'
+ wrapper.vm.$store.state.folders.folders = [{ name: 'test' }]
+ response = wrapper.vm.folderNameExists()
+
+ expect(response).toBeTruthy()
+ })
+})
diff --git a/tests/javascript/unit/components/AdminSettings.spec.ts b/tests/javascript/unit/components/AdminSettings.spec.ts
new file mode 100644
index 000000000..865b30416
--- /dev/null
+++ b/tests/javascript/unit/components/AdminSettings.spec.ts
@@ -0,0 +1,64 @@
+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 '../../../../src/components/AdminSettings.vue'
+
+jest.mock('@nextcloud/axios')
+jest.mock('@nextcloud/initial-state')
+jest.mock('@nextcloud/router')
+jest.mock('@nextcloud/dialogs')
+
+describe('AdminSettings.vue', () => {
+ 'use strict'
+
+ let wrapper: Wrapper<AdminSettings>
+
+ beforeAll(() => {
+ jest.useFakeTimers()
+ const localVue = createLocalVue();
+ (loadState as any).mockReturnValue('')
+ wrapper = shallowMount(AdminSettings, { localVue })
+ })
+
+ it('should initialize and fetch settings from state', () => {
+ expect(loadState).toBeCalledTimes(8)
+ })
+
+ 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')
+
+ expect(axios.post).toBeCalledTimes(1)
+ })
+
+ it('should handle bad response', () => {
+ (showError as any).mockClear()
+ console.error = jest.fn()
+ wrapper.vm.$options?.methods?.handleResponse.call(wrapper.vm, {
+ error: true,
+ errorMessage: 'FAIL',
+ })
+
+ 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()
+
+ expect(showSuccess).toBeCalledTimes(1)
+ })
+
+ afterAll(() => {
+ jest.clearAllMocks()
+ jest.useRealTimers()
+ })
+})
diff --git a/tests/javascript/unit/components/App.spec.ts b/tests/javascript/unit/components/App.spec.ts
new file mode 100644
index 000000000..f1c4096b9
--- /dev/null
+++ b/tests/javascript/unit/components/App.spec.ts
@@ -0,0 +1,58 @@
+import { shallowMount, createLocalVue, Wrapper } from '@vue/test-utils'
+
+import App from '../../../../src/App.vue'
+import { MUTATIONS } from '../../../../src/store'
+
+describe('FeedItemDisplay.vue', () => {
+ 'use strict'
+ const localVue = createLocalVue()
+ let wrapper: Wrapper<App>
+
+ const dispatchStub = jest.fn()
+ const commitStub = jest.fn()
+ beforeAll(() => {
+ wrapper = shallowMount(App, {
+ localVue,
+ mocks: {
+ $store: {
+ state: {
+ items: {
+ playingItem: undefined,
+ },
+ app: {
+ error: undefined,
+ },
+ },
+ dispatch: dispatchStub,
+ commit: commitStub,
+ },
+ },
+ })
+ })
+
+ beforeEach(() => {
+ dispatchStub.mockReset()
+ commitStub.mockReset()
+ })
+
+ it('should send SET_PLAYING_ITEM with undefined to stop playback', () => {
+ (wrapper.vm as any).stopPlaying()
+
+ expect(commitStub).toBeCalledWith(MUTATIONS.SET_PLAYING_ITEM, undefined)
+ })
+
+ it('should stop all video elements in page when playing video', () => {
+ const pauseStub = jest.fn()
+ document.getElementsByTagName = jest.fn().mockReturnValue([{ pause: pauseStub }]);
+
+ (wrapper.vm as any).stopVideo()
+
+ expect(pauseStub).toBeCalled()
+ })
+
+ it('should remove app state error with commit and undefined', () => {
+ (wrapper.vm as any).removeError()
+
+ expect(commitStub).toBeCalledWith(MUTATIONS.SET_ERROR, undefined)
+ })
+})
diff --git a/tests/javascript/unit/components/ShareItem.spec.ts b/tests/javascript/unit/components/ShareItem.spec.ts
new file mode 100644
index 000000000..f61c9e82b
--- /dev/null
+++ b/tests/javascript/unit/components/ShareItem.spec.ts
@@ -0,0 +1,75 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils'
+import ShareItem from '../../../../src/components/ShareItem.vue'
+import { ShareService } from '../../../../src/dataservices/share.service'
+
+describe('AddFeed.vue', () => {
+ 'use strict'
+
+ let wrapper: any
+ beforeEach(() => {
+ const localVue = createLocalVue()
+ wrapper = shallowMount(ShareItem, {
+ localVue,
+ propsData: {
+ itemId: 123,
+ },
+ })
+ })
+
+ describe('clickUser()', () => {
+ it('should add to selected if user not selected before', () => {
+ wrapper.vm.selected = []
+
+ wrapper.vm.clickUser({ displayName: 'display', shareName: 'share' })
+
+ expect(wrapper.vm.selected.length).toEqual(1)
+ })
+
+ it('should remove from selected if user is selected before', () => {
+ wrapper.vm.selected = [{ displayName: 'display', shareName: 'share' }]
+
+ wrapper.vm.clickUser({ displayName: 'display', shareName: 'share' })
+
+ expect(wrapper.vm.selected.length).toEqual(0)
+ })
+ })
+
+ describe('searchUsers()', () => {
+ it('should call ShareService to fetch users to add to user (display) list', async () => {
+ ShareService.fetchUsers = jest.fn().mockReturnValue({
+ data: {
+ ocs: {
+ data: {
+ users: [],
+ },
+ },
+ },
+ })
+ wrapper.vm.userName = 'search'
+
+ await wrapper.vm.searchUsers()
+
+ expect(ShareService.fetchUsers).toBeCalled()
+ })
+ })
+
+ describe('share()', () => {
+ it('should call ShareService to share article id with backend', async () => {
+ ShareService.share = jest.fn()
+ wrapper.vm.selected = [{ displayName: 'display', shareName: 'share' }]
+
+ await wrapper.vm.share()
+
+ expect(ShareService.share).toBeCalled()
+
+ wrapper.vm.selected = [{ displayName: 'display', shareName: 'share' }, { displayName: 'display2', shareName: 'share2' }]
+
+ await wrapper.vm.share()
+
+ let args = (ShareService.share as any).mock.calls[0]
+ expect(args[1]).toEqual(['share'])
+ args = (ShareService.share as any).mock.calls[1]
+ expect(args[1]).toEqual(['share', 'share2'])
+ })
+ })
+})
diff --git a/tests/javascript/unit/components/Sidebar.spec.ts b/tests/javascript/unit/components/Sidebar.spec.ts
new file mode 100644
index 000000000..404dac30a
--- /dev/null
+++ b/tests/javascript/unit/components/Sidebar.spec.ts
@@ -0,0 +1,188 @@
+import { ACTIONS } from '../../../../src/store'
+import { Wrapper, shallowMount, createLocalVue } from '@vue/test-utils'
+
+import AppSidebar from '../../../../src/components/Sidebar.vue'
+
+describe('Sidebar.vue', () => {
+ 'use strict'
+
+ let wrapper: Wrapper<AppSidebar>
+
+ const feeds = [{
+ id: 1, title: 'first',
+ }, {
+ id: 2, title: 'second', folderId: 123,
+ }]
+
+ beforeAll(() => {
+ const localVue = createLocalVue()
+ wrapper = shallowMount(AppSidebar, {
+ localVue,
+ mocks: {
+ $route: {
+ query: {
+ subscribe_to: undefined,
+ },
+ },
+ $store: {
+ state: {
+ feeds,
+ folders: [],
+ },
+ getters: {
+ feeds,
+ },
+ dispatch: jest.fn(),
+ },
+ },
+ })
+ })
+
+ beforeEach(() => {
+ (wrapper.vm as any).$store.dispatch.mockReset()
+ })
+
+ it('should initialize without showing AddFeed Component', () => {
+ expect((wrapper.vm as any).$data.showAddFeed).toBeFalsy()
+ })
+
+ describe('User Actions', () => {
+ it('should dispatch message to store with folder name to create new folder', () => {
+ (wrapper.vm as any).newFolder('abc')
+
+ expect((wrapper.vm as any).$store.dispatch).toHaveBeenCalledWith(ACTIONS.ADD_FOLDERS, { folder: { name: '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.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 false', () => {
+ (wrapper.vm as any).closeShowAddFeed()
+ expect(wrapper.vm.$data.showAddFeed).toBeFalsy()
+ })
+
+ it('should call mark feed read for all feeds in state', () => {
+ window.confirm = jest.fn().mockReturnValue(true);
+ (wrapper.vm as any).markAllRead()
+ expect((wrapper.vm as any).$store.dispatch).toHaveBeenCalledTimes(2)
+ })
+
+ it('should call mark feed read for all feeds in state with matching folderId', () => {
+ window.confirm = jest.fn().mockReturnValue(true);
+ (wrapper.vm as any).markFolderRead({ id: 123 })
+ expect((wrapper.vm as any).$store.dispatch).toHaveBeenCalledTimes(1)
+ })
+
+ it('should call disptch rename folder with response from user', () => {
+ const name = 'new name'
+ window.prompt = jest.fn().mockReturnValue(name);
+ (wrapper.vm as any).renameFolder({ id: 123 })
+ expect((wrapper.vm as any).$store.dispatch).toHaveBeenCalledWith(ACTIONS.FOLDER_SET_NAME, { folder: { id: 123 }, name })
+ })
+ })
+
+ 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])
+ })
+
+ it('should set pinned feeds at beginning top level nav with feeds and folders', () => {
+ const feeds: any[] = [{ name: 'feed1', id: 1 }, { name: 'feed2', id: 2, folderId: 123 }, { name: 'feed3', id: 3, pinned: true }]
+ 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[0].name).toEqual('feed3')
+ })
+ })
+
+ // TODO: More Template Testing with https://test-utils.vuejs.org/guide/essentials/a-crash-course.html#adding-a-new-todo
+
+ afterEach(() => {
+ jest.clearAllMocks()
+ })
+})
diff --git a/tests/javascript/unit/components/SidebarFeedLinkActions.spec.ts b/tests/javascript/unit/components/SidebarFeedLinkActions.spec.ts
new file mode 100644
index 000000000..1a4ee6358
--- /dev/null
+++ b/tests/javascript/unit/components/SidebarFeedLinkActions.spec.ts
@@ -0,0 +1,95 @@
+import { ACTIONS } from '../../../../src/store'
+import { Wrapper, shallowMount, createLocalVue } from '@vue/test-utils'
+
+import SidebarFeedLinkActions from '../../../../src/components/SidebarFeedLinkActions.vue'
+import { FEED_UPDATE_MODE, FEED_ORDER } from '../../../../src/dataservices/feed.service'
+
+describe('SidebarFeedLinkActions.vue', () => {
+ 'use strict'
+
+ let wrapper: Wrapper<SidebarFeedLinkActions>
+
+ const feeds = [{
+ id: 1, title: 'first',
+ }, {
+ id: 2, title: 'second', folderId: 123,
+ }]
+
+ beforeAll(() => {
+ const localVue = createLocalVue()
+ wrapper = shallowMount(SidebarFeedLinkActions, {
+ localVue,
+ propsData: {
+ feedId: 1,
+ },
+ mocks: {
+ $store: {
+ state: {
+ feeds,
+ folders: [],
+ },
+ getters: {
+ feeds,
+ },
+ dispatch: jest.fn(),
+ },
+ },
+ })
+ })
+
+ beforeEach(() => {
+ (wrapper.vm as any).$store.dispatch.mockReset()
+ })
+
+ describe('User Actions', () => {
+ it('should dispatch message to store with feed object', () => {
+ (wrapper.vm as any).markRead()
+
+ expect((wrapper.vm as any).$store.dispatch).toHaveBeenCalledWith(ACTIONS.FEED_MARK_READ, { feed: feeds[0] })
+ })
+
+ it('should dispatch message to store with feed object and pinned', () => {
+ (wrapper.vm as any).setPinned(true)
+
+ expect((wrapper.vm as any).$store.dispatch).toHaveBeenCalledWith(ACTIONS.FEED_SET_PINNED, { feed: feeds[0], pinned: true })
+ })
+
+ it('should dispatch message to store with feed object and fullTextEnabled', () => {
+ (wrapper.vm as any).setOrdering(FEED_ORDER.NEWEST)
+
+ expect((wrapper.vm as any).$store.dispatch).toHaveBeenCalledWith(ACTIONS.FEED_SET_ORDERING, { feed: feeds[0], ordering: FEED_ORDER.NEWEST })
+ })
+
+ it('should dispatch message to store with feed object and fullTextEnabled', () => {
+ (wrapper.vm as any).setFullText(true)
+
+ expect((wrapper.vm as any).$store.dispatch).toHaveBeenCalledWith(ACTIONS.FEED_SET_FULL_TEXT, { feed: feeds[0], fullTextEnabled: true })
+ })
+
+ it('should dispatch message to store with feed object and new updateMode', () => {
+ (wrapper.vm as any).setUpdateMode(FEED_UPDATE_MODE.IGNORE)
+
+ expect((wrapper.vm as any).$store.dispatch).toHaveBeenCalledWith(ACTIONS.FEED_SET_UPDATE_MODE, { feed: feeds[0], updateMode: FEED_UPDATE_MODE.IGNORE })
+ })
+
+ it('should dispatch message to store with feed object on rename feed', () => {
+ window.prompt = jest.fn().mockReturnValue('test');
+
+ (wrapper.vm as any).rename()
+
+ expect((wrapper.vm as any).$store.dispatch).toHaveBeenCalledWith(ACTIONS.FEED_SET_TITLE, { feed: feeds[0], title: 'test' })
+ })
+
+ it('should dispatch message to store with feed object on delete feed', () => {
+ window.confirm = jest.fn().mockReturnValue(true);
+
+ (wrapper.vm as any).deleteFeed()
+
+ expect((wrapper.vm as any).$store.dispatch).toHaveBeenCalledWith(ACTIONS.FEED_DELETE, { feed: feeds[0] })
+ })
+ })
+
+ afterEach(() => {
+ jest.clearAllMocks()
+ })
+})
diff --git a/tests/javascript/unit/components/feed-display/FeedItemDisplay.spec.ts b/tests/javascript/unit/components/feed-display/FeedItemDisplay.spec.ts
new file mode 100644
index 000000000..81c171114
--- /dev/null
+++ b/tests/javascript/unit/components/feed-display/FeedItemDisplay.spec.ts
@@ -0,0 +1,99 @@
+import { shallowMount, createLocalVue, Wrapper } from '@vue/test-utils'
+
+import FeedItemDisplay from '../../../../../src/components/feed-display/FeedItemDisplay.vue'
+import { ACTIONS, MUTATIONS } from '../../../../../src/store'
+
+describe('FeedItemDisplay.vue', () => {
+ 'use strict'
+ const localVue = createLocalVue()
+ let wrapper: Wrapper<FeedItemDisplay>
+
+ const mockItem = {
+ feedId: 1,
+ title: 'feed item',
+ pubDate: Date.now() / 1000,
+ }
+ const mockFeed = {
+ id: 1,
+ }
+
+ const dispatchStub = jest.fn()
+ const commitStub = jest.fn()
+ beforeAll(() => {
+ wrapper = shallowMount(FeedItemDisplay, {
+ propsData: {
+ item: mockItem,
+ },
+ localVue,
+ mocks: {
+ $store: {
+ getters: {
+ feeds: [mockFeed],
+ },
+ state: {
+ feeds: [],
+ folders: [],
+ },
+ dispatch: dispatchStub,
+ commit: commitStub,
+ },
+ },
+ })
+ })
+
+ beforeEach(() => {
+ dispatchStub.mockReset()
+ commitStub.mockReset()
+ })
+
+ it('should send SET_SELECTED_ITEM with undefined id', () => {
+ (wrapper.vm as any).clearSelected()
+
+ expect(commitStub).toBeCalledWith(MUTATIONS.SET_SELECTED_ITEM, { id: undefined })
+ })
+
+ it('should format date to match locale', () => {
+ const epoch = Date.now() // Provide an epoch timestamp
+ const formattedDate = (wrapper.vm as any).formatDate(epoch)
+
+ expect(formattedDate).toEqual(new Date(epoch).toLocaleString())
+ })
+
+ it('should retrieve feed by ID', () => {
+ const feed = (wrapper.vm as any).getFeed(mockFeed.id)
+
+ expect(feed).toEqual(mockFeed)
+ })
+
+ it('should toggle starred state', () => {
+ wrapper.vm.$props.item.starred = true;
+
+ (wrapper.vm as any).toggleStarred(wrapper.vm.$props.item)
+ expect(dispatchStub).toHaveBeenCalledWith(ACTIONS.UNSTAR_ITEM, {
+ item: wrapper.vm.$props.item,
+ })
+
+ wrapper.vm.$props.item.starred = false;
+
+ (wrapper.vm as any).toggleStarred(wrapper.vm.$props.item)
+ expect(dispatchStub).toHaveBeenCalledWith(ACTIONS.STAR_ITEM, {
+ item: wrapper.vm.$props.item,
+ })
+ })
+
+ it('should send SET_PLAYING_ITEM with item', () => {
+ const item = { id: 123 };
+ (wrapper.vm as any).playAudio(item)
+
+ expect(commitStub).toBeCalledWith(MUTATIONS.SET_PLAYING_ITEM, item)
+ })
+
+ it('should stop all audio elements in page when playing video', () => {
+ const pauseStub = jest.fn()
+ document.getElementsByTagName = jest.fn().mockReturnValue([{ pause: pauseStub }]);
+
+ (wrapper.vm as any).stopAudio()
+
+ expect(pauseStub).toBeCalled()
+ })
+})
diff --git a/tests/javascript/unit/components/feed-display/FeedItemDisplayList.spec.ts b/tests/javascript/unit/components/feed-display/FeedItemDisplayList.spec.ts
new file mode 100644
index 000000000..cd11b7ea9
--- /dev/null
+++ b/tests/javascript/unit/components/feed-display/FeedItemDisplayList.spec.ts
@@ -0,0 +1,66 @@
+import Vuex, { Store } from 'vuex'
+import { shallowMount, createLocalVue, Wrapper } from '@vue/test-utils'
+
+import FeedItemDisplayList from '../../../../../src/components/feed-display/FeedItemDisplayList.vue'
+import VirtualScroll from '../../../../../src/components/feed-display/VirtualScroll.vue'
+import FeedItemRow from '../../../../../src/components/feed-display/FeedItemRow.vue'
+
+jest.mock('@nextcloud/axios')
+
+describe('FeedItemDisplayList.vue', () => {
+ 'use strict'
+ const localVue = createLocalVue()
+ localVue.use(Vuex)
+ let wrapper: Wrapper<FeedItemDisplayList>
+<