refactor: Use composable for `currentView` and `views` to make it reactive when shared with other Vue apps
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>pull/46374/head
parent
7a2fbe11ec
commit
57869f565f
@ -0,0 +1,98 @@
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
import { beforeEach, describe, expect, it, jest } from '@jest/globals'
|
||||
import { Navigation, View } from '@nextcloud/files'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { defineComponent, nextTick } from 'vue'
|
||||
import { useNavigation } from './useNavigation'
|
||||
|
||||
import nextcloudFiles from '@nextcloud/files'
|
||||
|
||||
// Just a wrapper so we can test the composable
|
||||
const TestComponent = defineComponent({
|
||||
template: '<div></div>',
|
||||
setup() {
|
||||
const { currentView, views } = useNavigation()
|
||||
return {
|
||||
currentView,
|
||||
views,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
describe('Composables: useNavigation', () => {
|
||||
const spy = jest.spyOn(nextcloudFiles, 'getNavigation')
|
||||
let navigation: Navigation
|
||||
|
||||
describe('currentView', () => {
|
||||
beforeEach(() => {
|
||||
navigation = new Navigation()
|
||||
spy.mockImplementation(() => navigation)
|
||||
})
|
||||
|
||||
it('should return null without active navigation', () => {
|
||||
const wrapper = mount(TestComponent)
|
||||
expect((wrapper.vm as unknown as { currentView: View | null}).currentView).toBe(null)
|
||||
})
|
||||
|
||||
it('should return already active navigation', async () => {
|
||||
const view = new View({ getContents: () => Promise.reject(), icon: '<svg></svg>', id: 'view-1', name: 'My View 1', order: 0 })
|
||||
navigation.register(view)
|
||||
navigation.setActive(view)
|
||||
// Now the navigation is already set it should take the active navigation
|
||||
const wrapper = mount(TestComponent)
|
||||
expect((wrapper.vm as unknown as { currentView: View | null}).currentView).toBe(view)
|
||||
})
|
||||
|
||||
it('should be reactive on updating active navigation', async () => {
|
||||
const view = new View({ getContents: () => Promise.reject(), icon: '<svg></svg>', id: 'view-1', name: 'My View 1', order: 0 })
|
||||
navigation.register(view)
|
||||
const wrapper = mount(TestComponent)
|
||||
|
||||
// no active navigation
|
||||
expect((wrapper.vm as unknown as { currentView: View | null}).currentView).toBe(null)
|
||||
|
||||
navigation.setActive(view)
|
||||
// Now the navigation is set it should take the active navigation
|
||||
expect((wrapper.vm as unknown as { currentView: View | null}).currentView).toBe(view)
|
||||
})
|
||||
})
|
||||
|
||||
describe('views', () => {
|
||||
beforeEach(() => {
|
||||
navigation = new Navigation()
|
||||
spy.mockImplementation(() => navigation)
|
||||
})
|
||||
|
||||
it('should return empty array without registered views', () => {
|
||||
const wrapper = mount(TestComponent)
|
||||
expect((wrapper.vm as unknown as { views: View[]}).views).toStrictEqual([])
|
||||
})
|
||||
|
||||
it('should return already registered views', () => {
|
||||
const view = new View({ getContents: () => Promise.reject(), icon: '<svg></svg>', id: 'view-1', name: 'My View 1', order: 0 })
|
||||
// register before mount
|
||||
navigation.register(view)
|
||||
// now mount and check that the view is listed
|
||||
const wrapper = mount(TestComponent)
|
||||
expect((wrapper.vm as unknown as { views: View[]}).views).toStrictEqual([view])
|
||||
})
|
||||
|
||||
it('should be reactive on registering new views', () => {
|
||||
const view = new View({ getContents: () => Promise.reject(), icon: '<svg></svg>', id: 'view-1', name: 'My View 1', order: 0 })
|
||||
const view2 = new View({ getContents: () => Promise.reject(), icon: '<svg></svg>', id: 'view-2', name: 'My View 2', order: 1 })
|
||||
|
||||
// register before mount
|
||||
navigation.register(view)
|
||||
// now mount and check that the view is listed
|
||||
const wrapper = mount(TestComponent)
|
||||
expect((wrapper.vm as unknown as { views: View[]}).views).toStrictEqual([view])
|
||||
|
||||
// now register view 2 and check it is reactivly added
|
||||
navigation.register(view2)
|
||||
expect((wrapper.vm as unknown as { views: View[]}).views).toStrictEqual([view, view2])
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
import type { View } from '@nextcloud/files'
|
||||
|
||||
import { getNavigation } from '@nextcloud/files'
|
||||
import { onMounted, onUnmounted, shallowRef, type ShallowRef } from 'vue'
|
||||
|
||||
/**
|
||||
* Composable to get the currently active files view from the files navigation
|
||||
*/
|
||||
export function useNavigation() {
|
||||
const navigation = getNavigation()
|
||||
const views: ShallowRef<View[]> = shallowRef(navigation.views)
|
||||
const currentView: ShallowRef<View | null> = shallowRef(navigation.active)
|
||||
|
||||
/**
|
||||
* Event listener to update the `currentView`
|
||||
* @param event The update event
|
||||
*/
|
||||
function onUpdateActive(event: CustomEvent<View|null>) {
|
||||
currentView.value = event.detail
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener to update all registered views
|
||||
*/
|
||||
function onUpdateViews() {
|
||||
views.value = navigation.views
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
navigation.addEventListener('update', onUpdateViews)
|
||||
navigation.addEventListener('updateActive', onUpdateActive)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
navigation.removeEventListener('update', onUpdateViews)
|
||||
navigation.removeEventListener('updateActive', onUpdateActive)
|
||||
})
|
||||
|
||||
return {
|
||||
currentView,
|
||||
views,
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue