From b413395f14258b71ecf39b67c8b4166abb1175b4 Mon Sep 17 00:00:00 2001 From: Jannik Stehle Date: Mon, 18 Dec 2023 08:13:26 +0100 Subject: [PATCH 1/6] refactor: modal implementation --- changelog/unreleased/change-creating-modals | 8 + .../src/components/OcModal/OcModal.vue | 128 +++------------ .../__snapshots__/OcModal.spec.ts.snap | 24 ++- .../components/Groups/CreateGroupModal.vue | 57 +++---- .../src/components/Spaces/ContextActions.vue | 2 +- .../Spaces/SideBar/ActionsPanel.vue | 2 +- .../src/components/Users/AddToGroupsModal.vue | 51 +++--- .../src/components/Users/CreateUserModal.vue | 58 +++---- .../src/components/Users/LoginModal.vue | 75 ++++----- .../Users/RemoveFromGroupsModal.vue | 51 +++--- .../groups/useGroupActionsCreateGroup.ts | 21 +-- .../actions/groups/useGroupActionsDelete.ts | 21 +-- .../users/useUserActionsAddToGroups.ts | 8 +- .../actions/users/useUserActionsCreateUser.ts | 20 +-- .../actions/users/useUserActionsDelete.ts | 20 +-- .../actions/users/useUserActionsEditLogin.ts | 8 +- .../actions/users/useUserActionsEditQuota.ts | 12 +- .../users/useUserActionsRemoveFromGroups.ts | 8 +- .../src/views/Spaces.vue | 2 +- .../Groups/CreateGroupModal.spec.ts | 17 +- .../components/Users/AddToGroupsModal.spec.ts | 10 +- .../components/Users/CreateUserModal.spec.ts | 17 +- .../unit/components/Users/LoginModal.spec.ts | 10 +- .../Users/RemoveFromGroupsModal.spec.ts | 10 +- .../__snapshots__/LoginModal.spec.ts.snap | 7 +- .../groups/useGroupActionsCreateGroup.spec.ts | 24 ++- .../groups/useGroupActionsDelete.spec.ts | 6 +- .../users/useUserActionsAddToGroups.spec.ts | 30 ++-- .../users/useUserActionsCreateUser.spec.ts | 25 ++- .../users/useUserActionsDelete.spec.ts | 6 +- .../users/useUserActionsEditLogin.spec.ts | 7 +- .../users/useUserActionsEditQuota.spec.ts | 6 +- .../useUserActionsRemoveFromGroups.spec.ts | 30 ++-- .../src/components/AppBar/CreateAndUpload.vue | 4 +- .../src/components/AppBar/CreateSpace.vue | 71 ++++---- .../Modals/SetLinkPasswordModal.vue | 45 ++---- .../SideBar/Actions/SpaceActions.vue | 2 +- .../SideBar/Shares/Collaborators/ListItem.vue | 14 +- .../components/SideBar/Shares/FileLinks.vue | 33 ++-- .../components/SideBar/Shares/FileShares.vue | 18 +-- .../SideBar/Shares/Links/DetailsAndEdit.vue | 98 +++++------ .../SideBar/Shares/SpaceMembers.vue | 24 ++- .../components/Spaces/SpaceContextActions.vue | 2 +- .../src/helpers/resource/actions/transfer.ts | 4 +- .../src/helpers/resource/actions/upload.ts | 2 - packages/web-app-files/src/store/actions.ts | 4 - .../src/views/spaces/GenericSpace.vue | 4 +- .../components/AppBar/CreateSpace.spec.ts | 21 ++- .../Modals/SetLinkPasswordModal.spec.ts | 3 + .../Shares/Links/DetailsAndEdit.spec.ts | 15 -- .../resource/resourcesTransfer.spec.ts | 8 - .../tests/unit/views/FilesDrop.spec.ts | 3 +- .../unit/views/spaces/GenericSpace.spec.ts | 3 +- .../unit/views/spaces/GenericTrash.spec.ts | 3 +- packages/web-app-importer/src/extensions.ts | 20 +-- .../web-pkg/src/components/AppBar/AppBar.vue | 2 +- .../components/AppTemplates/AppWrapper.vue | 11 +- .../src/components/CreateLinkModal.vue | 58 +++---- .../src/components/CreateShortcutModal.vue | 61 ++++--- .../Modals/ResourceConflictModal.vue | 69 ++++---- .../components/Modals/SpaceMoveInfoModal.vue | 22 +++ .../web-pkg/src/components/Modals/index.ts | 1 + .../src/components/Spaces/QuotaModal.vue | 56 +++---- .../components/Spaces/ReadmeContentModal.vue | 17 +- .../actions/files/useFileActionsCreateLink.ts | 6 +- .../files/useFileActionsCreateNewFile.ts | 34 ++-- .../files/useFileActionsCreateNewFolder.ts | 37 ++--- .../files/useFileActionsCreateNewShortcut.ts | 21 ++- .../useFileActionsCreateSpaceFromResource.ts | 17 +- .../files/useFileActionsEmptyTrashBin.ts | 19 +-- .../actions/files/useFileActionsPaste.ts | 2 - .../actions/files/useFileActionsRename.ts | 67 +++----- .../actions/files/useFileActionsRestore.ts | 4 +- .../helpers/useFileActionsDeleteResources.ts | 23 +-- .../actions/spaces/useSpaceActionsDelete.ts | 22 +-- .../actions/spaces/useSpaceActionsDisable.ts | 23 +-- .../spaces/useSpaceActionsEditDescription.ts | 14 +- .../spaces/useSpaceActionsEditQuota.ts | 13 +- .../useSpaceActionsEditReadmeContent.ts | 5 +- .../actions/spaces/useSpaceActionsRename.ts | 17 +- .../actions/spaces/useSpaceActionsRestore.ts | 13 +- .../src/composables/piniaStores/index.ts | 1 + .../src/composables/piniaStores/modals.ts | 102 ++++++++++++ .../src/composables/piniaStores/theme.ts | 4 +- .../src/composables/spaces/useSpaceHelpers.ts | 16 +- .../conflictHandling/conflictDialog.ts | 29 ++-- .../tests/unit/components/AppBanner.spec.ts | 24 +-- .../unit/components/CreateLinkModal.spec.ts | 39 ++--- .../Modals/ResourceConflictModal.spec.ts | 10 +- .../files/useFileActionsCreateLink.spec.ts | 11 +- .../files/useFileActionsCreateNewFile.spec.ts | 19 +-- .../useFileActionsCreateNewFolder.spec.ts | 20 +-- .../useFileActionsCreateNewShortcut.spec.ts | 7 +- .../files/useFileActionsEmptyTrashBin.spec.ts | 23 +-- .../files/useFileActionsRename.spec.ts | 70 ++++---- .../spaces/useSpaceActionsDelete.spec.ts | 38 ++--- .../spaces/useSpaceActionsDisable.spec.ts | 37 +++-- .../useSpaceActionsEditDescription.spec.ts | 25 +-- .../spaces/useSpaceActionsEditQuota.spec.ts | 8 +- .../useSpaceActionsEditReadmeContent.spec.ts | 6 +- .../spaces/useSpaceActionsRename.spec.ts | 30 ++-- .../spaces/useSpaceActionsRestore.spec.ts | 35 ++-- .../composables/piniaStores/useModals.spec.ts | 108 +++++++++++++ .../spaces/useSpaceHelpers.spec.ts | 54 +++---- .../conflictHandling/conflictDialog.spec.ts | 14 +- packages/web-runtime/src/App.vue | 77 +-------- .../src/components/EditPasswordModal.vue | 128 +++++++++------ .../src/components/ModalWrapper.vue | 107 ++++++++++++ .../web-runtime/src/container/bootstrap.ts | 6 +- packages/web-runtime/src/index.ts | 2 + packages/web-runtime/src/pages/account.vue | 50 ++---- .../src/services/auth/authService.ts | 5 +- packages/web-runtime/src/store/index.ts | 2 - packages/web-runtime/src/store/modal.ts | 106 ------------ .../unit/components/EditPasswordModal.spec.ts | 10 +- .../unit/components/ModalWrapper.spec.ts | 153 ++++++++++++++++++ .../components/Topbar/ThemeSwitcher.spec.ts | 22 ++- .../unit/components/Topbar/TopBar.spec.ts | 3 +- .../unit/components/Topbar/UserMenu.spec.ts | 12 +- .../tests/unit/pages/accessDenied.spec.ts | 3 +- .../tests/unit/pages/account.spec.ts | 31 ---- .../tests/unit/pages/oidcCallback.spec.ts | 3 +- .../unit/pages/resolvePublicLink.spec.ts | 3 +- .../web-test-helpers/src/defaultPlugins.ts | 9 +- packages/web-test-helpers/src/mocks/index.ts | 1 + packages/web-test-helpers/src/mocks/pinia.ts | 31 ++-- .../mocks/store/defaultStoreMockOptions.ts | 5 - 127 files changed, 1598 insertions(+), 1717 deletions(-) create mode 100644 changelog/unreleased/change-creating-modals create mode 100644 packages/web-pkg/src/components/Modals/SpaceMoveInfoModal.vue create mode 100644 packages/web-pkg/src/composables/piniaStores/modals.ts create mode 100644 packages/web-pkg/tests/unit/composables/piniaStores/useModals.spec.ts create mode 100644 packages/web-runtime/src/components/ModalWrapper.vue delete mode 100644 packages/web-runtime/src/store/modal.ts create mode 100644 packages/web-runtime/tests/unit/components/ModalWrapper.spec.ts diff --git a/changelog/unreleased/change-creating-modals b/changelog/unreleased/change-creating-modals new file mode 100644 index 00000000000..d60c0624d96 --- /dev/null +++ b/changelog/unreleased/change-creating-modals @@ -0,0 +1,8 @@ +Change: Creating modals + +BREAKING CHANGE for developers: The way how to work with modals has been reworked. Modals can now be registered via the `registerModal` method provided by the `useModals` composable, instead of calling `createModal` or `hideModal` from the store. + +For more details on how to use the modal please see the linked PR down below. + +https://github.com/owncloud/web/pull/10212 +https://github.com/owncloud/web/issues/10095 diff --git a/packages/design-system/src/components/OcModal/OcModal.vue b/packages/design-system/src/components/OcModal/OcModal.vue index c8cfaae15e9..98e81417e5f 100644 --- a/packages/design-system/src/components/OcModal/OcModal.vue +++ b/packages/design-system/src/components/OcModal/OcModal.vue @@ -40,11 +40,9 @@ v-model="userInputValue" class="oc-modal-body-input" :error-message="inputError" - :placeholder="inputPlaceholder" :label="inputLabel" :type="inputType" :description-message="inputDescription" - :disabled="inputDisabled" :fix-message-line="true" :selection-range="inputSelectionRange" @update:model-value="inputOnInput" @@ -54,24 +52,24 @@
- - +
+ {{ buttonCancelText }} + {{ buttonConfirmText }} +
@@ -79,7 +77,7 @@ diff --git a/packages/web-app-files/src/components/Modals/SetLinkPasswordModal.vue b/packages/web-app-files/src/components/Modals/SetLinkPasswordModal.vue index ec8d6886cf7..eebccd95d53 100644 --- a/packages/web-app-files/src/components/Modals/SetLinkPasswordModal.vue +++ b/packages/web-app-files/src/components/Modals/SetLinkPasswordModal.vue @@ -9,41 +9,26 @@ :placeholder="link.password ? '●●●●●●●●' : null" :error-message="errorMessage" class="oc-modal-body-input" - @password-challenge-completed="confirmDisabled = false" - @password-challenge-failed="confirmDisabled = true" - @keydown.enter.prevent="onConfirm" + @password-challenge-completed="$emit('update:confirmDisabled', false)" + @password-challenge-failed="$emit('update:confirmDisabled', true)" + @keydown.enter.prevent="$emit('confirm')" @update:model-value="onInput" /> -
- {{ $gettext('Cancel') }} - - {{ link.password ? $gettext('Apply') : $gettext('Set') }} - -
diff --git a/packages/web-pkg/src/components/Modals/index.ts b/packages/web-pkg/src/components/Modals/index.ts index 9f20b06fe93..f63c0388f67 100644 --- a/packages/web-pkg/src/components/Modals/index.ts +++ b/packages/web-pkg/src/components/Modals/index.ts @@ -1 +1,2 @@ export { default as ResourceConflictModal } from './ResourceConflictModal.vue' +export { default as SpaceMoveInfoModal } from './SpaceMoveInfoModal.vue' diff --git a/packages/web-pkg/src/components/Spaces/QuotaModal.vue b/packages/web-pkg/src/components/Spaces/QuotaModal.vue index c93eb084776..cb41746ac89 100644 --- a/packages/web-pkg/src/components/Spaces/QuotaModal.vue +++ b/packages/web-pkg/src/components/Spaces/QuotaModal.vue @@ -14,35 +14,17 @@ v-bind="warningMessageContextualHelperData" /> - -
- {{ $gettext('Cancel') }} - - {{ $gettext('Confirm') }} - -
diff --git a/packages/web-pkg/src/composables/actions/files/useFileActionsCreateLink.ts b/packages/web-pkg/src/composables/actions/files/useFileActionsCreateLink.ts index 50eb871890b..572b6de281a 100644 --- a/packages/web-pkg/src/composables/actions/files/useFileActionsCreateLink.ts +++ b/packages/web-pkg/src/composables/actions/files/useFileActionsCreateLink.ts @@ -12,6 +12,7 @@ import { import { useCapabilityFilesSharingPublicPasswordEnforcedFor } from '../../capability' import { useCreateLink, useDefaultLinkPermissions } from '../../links' import { useLoadingService } from '../../loadingService' +import { useModals } from '../../piniaStores' export const useFileActionsCreateLink = ({ store, @@ -30,6 +31,7 @@ export const useFileActionsCreateLink = ({ const passwordEnforcedCapabilities = useCapabilityFilesSharingPublicPasswordEnforcedFor() const { defaultLinkPermissions } = useDefaultLinkPermissions() const { createLink } = useCreateLink() + const { registerModal } = useModals() const proceedResult = (result: PromiseSettledResult[]) => { const succeeded = result.filter( @@ -67,8 +69,7 @@ export const useFileActionsCreateLink = ({ enforceModal || (passwordEnforced && unref(defaultLinkPermissions) > SharePermissionBit.Internal) ) { - return store.dispatch('createModal', { - variation: 'passive', + registerModal({ title: $ngettext( 'Create link for "%{resourceName}"', 'Create links for the selected items', @@ -84,6 +85,7 @@ export const useFileActionsCreateLink = ({ }), hideActions: true }) + return } const promises = resources.map((resource) => diff --git a/packages/web-pkg/src/composables/actions/files/useFileActionsCreateNewFile.ts b/packages/web-pkg/src/composables/actions/files/useFileActionsCreateNewFile.ts index 5c91af284da..e0d192ecb5b 100644 --- a/packages/web-pkg/src/composables/actions/files/useFileActionsCreateNewFile.ts +++ b/packages/web-pkg/src/composables/actions/files/useFileActionsCreateNewFile.ts @@ -21,6 +21,7 @@ import { urlJoin } from '@ownclouders/web-client/src/utils' import { configurationManager } from '../../../configuration' import { stringify } from 'qs' import { AncestorMetaData } from '../../../types' +import { useModals } from '../../piniaStores' export const useFileActionsCreateNewFile = ({ store, @@ -37,6 +38,7 @@ export const useFileActionsCreateNewFile = ({ const router = useRouter() const { $gettext } = useGettext() const { makeRequest } = useRequest() + const { registerModal } = useModals() const { openEditor, triggerDefaultAction } = useFileActions() const clientService = useClientService() @@ -49,7 +51,7 @@ export const useFileActionsCreateNewFile = ({ const capabilities = computed(() => store.getters['capabilities']) - const checkNewFileName = (fileName) => { + const getNameErrorMsg = (fileName: string) => { if (fileName === '') { return $gettext('File name cannot be empty') } @@ -79,7 +81,7 @@ export const useFileActionsCreateNewFile = ({ return null } - const addAppProviderFileFunc = async (fileName) => { + const addAppProviderFileFunc = async (fileName: string) => { // FIXME: this belongs in web-app-external, but the app provider handles file creation differently than other editor extensions. Needs more refactoring. if (fileName === '') { return @@ -107,7 +109,6 @@ export const useFileActionsCreateNewFile = ({ } triggerDefaultAction({ space: space, resources: [resource] }) store.commit('Files/UPSERT_RESOURCE', resource) - store.dispatch('hideModal') store.dispatch('showMessage', { title: $gettext('"%{fileName}" was created successfully', { fileName }) }) @@ -150,12 +151,10 @@ export const useFileActionsCreateNewFile = ({ EDITOR_MODE_CREATE, space.shareId ) - store.dispatch('hideModal') return } - store.dispatch('hideModal') store.dispatch('showMessage', { title: $gettext('"%{fileName}" was created successfully', { fileName }) }) @@ -173,12 +172,6 @@ export const useFileActionsCreateNewFile = ({ extension: string, openAction: any // FIXME: type? ) => { - const checkInputValue = (value) => { - store.dispatch( - 'setModalInputErrorMessage', - checkNewFileName(areFileExtensionsShown.value ? value : `${value}.${extension}`) - ) - } let defaultName = $gettext('New file') + `.${extension}` if (unref(files).some((f) => f.name === defaultName)) { @@ -191,21 +184,15 @@ export const useFileActionsCreateNewFile = ({ const inputSelectionRange = !areFileExtensionsShown.value ? null - : [0, defaultName.length - (extension.length + 1)] + : ([0, defaultName.length - (extension.length + 1)] as [number, number]) - const modal = { - variation: 'passive', + registerModal({ title: $gettext('Create a new file'), - cancelText: $gettext('Cancel'), confirmText: $gettext('Create'), hasInput: true, inputValue: defaultName, inputLabel: $gettext('File name'), - inputError: checkNewFileName( - areFileExtensionsShown.value ? defaultName : `${defaultName}.${extension}` - ), inputSelectionRange, - onCancel: () => store.dispatch('hideModal'), onConfirm: (fileName: string) => { if (!areFileExtensionsShown.value) { fileName = `${fileName}.${extension}` @@ -217,10 +204,9 @@ export const useFileActionsCreateNewFile = ({ return addNewFile(fileName, openAction) }, - onInput: checkInputValue - } - - store.dispatch('createModal', modal) + onInput: (name, setError) => + setError(getNameErrorMsg(areFileExtensionsShown.value ? name : `${name}.${extension}`)) + }) } const actions = computed((): FileAction[] => { @@ -261,7 +247,7 @@ export const useFileActionsCreateNewFile = ({ return { actions, - checkNewFileName, + getNameErrorMsg, addNewFile } } diff --git a/packages/web-pkg/src/composables/actions/files/useFileActionsCreateNewFolder.ts b/packages/web-pkg/src/composables/actions/files/useFileActionsCreateNewFolder.ts index fa10561922b..16efa520668 100644 --- a/packages/web-pkg/src/composables/actions/files/useFileActionsCreateNewFolder.ts +++ b/packages/web-pkg/src/composables/actions/files/useFileActionsCreateNewFolder.ts @@ -13,6 +13,7 @@ import { isLocationSpacesActive } from '../../../router' import { getIndicators } from '../../../helpers/statusIndicators' import { useScrollTo } from '../../scrollTo' import { AncestorMetaData } from '../../../types' +import { useModals } from '../../../composables/piniaStores' export const useFileActionsCreateNewFolder = ({ store, @@ -20,6 +21,7 @@ export const useFileActionsCreateNewFolder = ({ }: { store?: Store; space?: SpaceResource } = {}) => { store = store || useStore() const router = useRouter() + const { registerModal } = useModals() const { $gettext } = useGettext() const { scrollToResource } = useScrollTo() @@ -30,37 +32,37 @@ export const useFileActionsCreateNewFolder = ({ () => store.getters['runtime/ancestorMetaData/ancestorMetaData'] ) - const checkNewFolderName = (folderName) => { + const checkNewFolderName = (folderName: string, setError: (error: string) => void) => { if (folderName.trim() === '') { - return $gettext('Folder name cannot be empty') + return setError($gettext('Folder name cannot be empty')) } if (/[/]/.test(folderName)) { - return $gettext('Folder name cannot contain "/"') + return setError($gettext('Folder name cannot contain "/"')) } if (folderName === '.') { - return $gettext('Folder name cannot be equal to "."') + return setError($gettext('Folder name cannot be equal to "."')) } if (folderName === '..') { - return $gettext('Folder name cannot be equal to ".."') + return setError($gettext('Folder name cannot be equal to ".."')) } const exists = unref(files).find((file) => file.name === folderName) if (exists) { - return $gettext('%{name} already exists', { name: folderName }, true) + return setError($gettext('%{name} already exists', { name: folderName }, true)) } - return null + return setError(null) } const loadIndicatorsForNewFile = computed(() => { return isLocationSpacesActive(router, 'files-spaces-generic') && space.driveType !== 'share' }) - const addNewFolder = async (folderName) => { + const addNewFolder = async (folderName: string) => { folderName = folderName.trimEnd() try { @@ -74,7 +76,6 @@ export const useFileActionsCreateNewFolder = ({ } store.commit('Files/UPSERT_RESOURCE', resource) - store.dispatch('hideModal') store.dispatch('showMessage', { title: $gettext('"%{folderName}" was created successfully', { folderName }) @@ -92,33 +93,21 @@ export const useFileActionsCreateNewFolder = ({ } const handler = () => { - const checkInputValue = (value) => { - store.dispatch('setModalInputErrorMessage', checkNewFolderName(value)) - } let defaultName = $gettext('New folder') if (unref(files).some((f) => f.name === defaultName)) { defaultName = resolveFileNameDuplicate(defaultName, '', unref(files)) } - const inputSelectionRange = null - - const modal = { - variation: 'passive', + registerModal({ title: $gettext('Create a new folder'), - cancelText: $gettext('Cancel'), confirmText: $gettext('Create'), hasInput: true, inputValue: defaultName, inputLabel: $gettext('Folder name'), - inputError: checkNewFolderName(defaultName), - inputSelectionRange, - onCancel: () => store.dispatch('hideModal'), onConfirm: addNewFolder, - onInput: checkInputValue - } - - store.dispatch('createModal', modal) + onInput: checkNewFolderName + }) } const actions = computed((): FileAction[] => { diff --git a/packages/web-pkg/src/composables/actions/files/useFileActionsCreateNewShortcut.ts b/packages/web-pkg/src/composables/actions/files/useFileActionsCreateNewShortcut.ts index ba1e091717b..a1fcdf33f08 100644 --- a/packages/web-pkg/src/composables/actions/files/useFileActionsCreateNewShortcut.ts +++ b/packages/web-pkg/src/composables/actions/files/useFileActionsCreateNewShortcut.ts @@ -1,30 +1,29 @@ import { Resource, SpaceResource } from '@ownclouders/web-client/src/helpers' import { computed, unref } from 'vue' import { useStore } from '../../store' -import { FileAction } from '../../../composables' +import { FileAction, useModals } from '../../../composables' import { CreateShortcutModal } from '../../../components' import { useGettext } from 'vue3-gettext' export const useFileActionsCreateNewShortcut = ({ space }: { space: SpaceResource }) => { const store = useStore() + const { registerModal } = useModals() const { $gettext } = useGettext() const currentFolder = computed((): Resource => store.getters['Files/currentFolder']) - const handler = () => { - return store.dispatch('createModal', { - title: $gettext('Create a Shortcut'), - hideActions: true, - customComponent: CreateShortcutModal, - customComponentAttrs: () => ({ space }) - }) - } - const actions = computed((): FileAction[] => { return [ { name: 'create-shortcut', icon: 'external-link', - handler, + handler: () => { + registerModal({ + title: $gettext('Create a Shortcut'), + confirmText: $gettext('Create'), + customComponent: CreateShortcutModal, + customComponentAttrs: () => ({ space }) + }) + }, label: () => { return $gettext('New Shortcut') }, diff --git a/packages/web-pkg/src/composables/actions/files/useFileActionsCreateSpaceFromResource.ts b/packages/web-pkg/src/composables/actions/files/useFileActionsCreateSpaceFromResource.ts index cc57c638474..42366eafe1e 100644 --- a/packages/web-pkg/src/composables/actions/files/useFileActionsCreateSpaceFromResource.ts +++ b/packages/web-pkg/src/composables/actions/files/useFileActionsCreateSpaceFromResource.ts @@ -6,26 +6,25 @@ import { FileAction, FileActionOptions } from '../../actions' import { useAbility } from '../../ability' import { useClientService } from '../../clientService' import { useRouter } from '../../router' -import { useLoadingService } from '../../loadingService' import { isPersonalSpaceResource } from '@ownclouders/web-client/src/helpers' import { isLocationSpacesActive } from '../../../router' import { useCreateSpace } from '../../spaces' import { useSpaceHelpers } from '../../spaces' import PQueue from 'p-queue' +import { useModals } from '../../piniaStores' export const useFileActionsCreateSpaceFromResource = ({ store }: { store?: Store } = {}) => { const { can } = useAbility() const { $gettext, $ngettext } = useGettext() - const loadingService = useLoadingService() const { createSpace } = useCreateSpace() const { checkSpaceNameModalInput } = useSpaceHelpers() const clientService = useClientService() const router = useRouter() const hasCreatePermission = computed(() => can('create-all', 'Drive')) + const { registerModal } = useModals() const confirmAction = async ({ spaceName, resources, space }) => { const { webdav } = clientService - store.dispatch('hideModal') const queue = new PQueue({ concurrency: 4 }) const copyOps = [] @@ -58,8 +57,7 @@ export const useFileActionsCreateSpaceFromResource = ({ store }: { store?: Store } } const handler = ({ resources, space }: FileActionOptions) => { - const modal = { - variation: 'passive', + registerModal({ title: $ngettext( 'Create Space from "%{resourceName}"', 'Create Space from selection', @@ -81,17 +79,12 @@ export const useFileActionsCreateSpaceFromResource = ({ store }: { store?: Store title: $gettext('Restrictions'), text: $gettext('Shares, versions and tags will not be copied.') }, - cancelText: $gettext('Cancel'), confirmText: $gettext('Create'), hasInput: true, inputLabel: $gettext('Space name'), onInput: checkSpaceNameModalInput, - onCancel: () => store.dispatch('hideModal'), - onConfirm: (spaceName) => - loadingService.addTask(() => confirmAction({ spaceName, space, resources })) - } - - store.dispatch('createModal', modal) + onConfirm: (spaceName: string) => confirmAction({ spaceName, space, resources }) + }) } const actions = computed((): FileAction[] => { diff --git a/packages/web-pkg/src/composables/actions/files/useFileActionsEmptyTrashBin.ts b/packages/web-pkg/src/composables/actions/files/useFileActionsEmptyTrashBin.ts index 60aa103f8cd..a70ec4f568d 100644 --- a/packages/web-pkg/src/composables/actions/files/useFileActionsEmptyTrashBin.ts +++ b/packages/web-pkg/src/composables/actions/files/useFileActionsEmptyTrashBin.ts @@ -10,15 +10,15 @@ import { useStore } from '../../store' import { useGettext } from 'vue3-gettext' import { FileAction, FileActionOptions } from '../types' -import { useLoadingService } from '../../loadingService' +import { useModals } from '../../piniaStores' export const useFileActionsEmptyTrashBin = ({ store }: { store?: Store } = {}) => { store = store || useStore() const router = useRouter() const { $gettext, $pgettext } = useGettext() const clientService = useClientService() - const loadingService = useLoadingService() const hasPermanentDeletion = useCapabilityFilesPermanentDeletion() + const { registerModal } = useModals() const emptyTrashBin = ({ space }: { space: SpaceResource }) => { return clientService.webdav @@ -39,26 +39,19 @@ export const useFileActionsEmptyTrashBin = ({ store }: { store?: Store } = error }) }) - .finally(() => { - store.dispatch('hideModal') - }) } const handler = ({ space }: FileActionOptions) => { - const modal = { + registerModal({ variation: 'danger', title: $gettext('Empty trash bin'), - cancelText: $gettext('Cancel'), confirmText: $gettext('Delete'), message: $gettext( 'Are you sure you want to permanently delete the listed items? You can’t undo this action.' ), hasInput: false, - onCancel: () => store.dispatch('hideModal'), - onConfirm: () => loadingService.addTask(() => emptyTrashBin({ space })) - } - - store.dispatch('createModal', modal) + onConfirm: () => emptyTrashBin({ space }) + }) } const actions = computed((): FileAction[] => [ @@ -67,7 +60,7 @@ export const useFileActionsEmptyTrashBin = ({ store }: { store?: Store } = icon: 'delete-bin-5', label: () => $gettext('Empty trash bin'), handler, - isEnabled: ({ space, resources }) => { + isEnabled: ({ space }) => { if (!isLocationTrashActive(router, 'files-trash-generic')) { return false } diff --git a/packages/web-pkg/src/composables/actions/files/useFileActionsPaste.ts b/packages/web-pkg/src/composables/actions/files/useFileActionsPaste.ts index 7b3e23d8f17..d7007bf7067 100644 --- a/packages/web-pkg/src/composables/actions/files/useFileActionsPaste.ts +++ b/packages/web-pkg/src/composables/actions/files/useFileActionsPaste.ts @@ -59,8 +59,6 @@ export const useFileActionsPaste = ({ store }: { store?: Store } = {}) => { resources: resourcesToCopy, clientService, loadingService, - createModal: (...args) => store.dispatch('createModal', ...args), - hideModal: (...args) => store.dispatch('hideModal', ...args), showMessage: (...args) => store.dispatch('showMessage', ...args), showErrorMessage: (...args) => store.dispatch('showErrorMessage', ...args), $gettext, diff --git a/packages/web-pkg/src/composables/actions/files/useFileActionsRename.ts b/packages/web-pkg/src/composables/actions/files/useFileActionsRename.ts index 69455de1696..64fe29c5611 100644 --- a/packages/web-pkg/src/composables/actions/files/useFileActionsRename.ts +++ b/packages/web-pkg/src/composables/actions/files/useFileActionsRename.ts @@ -14,53 +14,44 @@ import { renameResource as _renameResource } from '../../../helpers/resource' import { computed, unref } from 'vue' import { useClientService } from '../../clientService' import { useConfigurationManager } from '../../configuration' -import { useLoadingService } from '../../loadingService' import { useRouter } from '../../router' import { useStore } from '../../store' import { useGettext } from 'vue3-gettext' import { FileAction, FileActionOptions } from '../types' import { useCapabilityFilesSharingCanRename } from '../../capability' +import { useModals } from '../../piniaStores' export const useFileActionsRename = ({ store }: { store?: Store } = {}) => { store = store || useStore() const router = useRouter() const { $gettext } = useGettext() const clientService = useClientService() - const loadingService = useLoadingService() const canRename = useCapabilityFilesSharingCanRename() const configurationManager = useConfigurationManager() + const { registerModal } = useModals() - const checkNewName = (resource, newName, parentResources = undefined) => { + const getNameErrorMsg = (resource: Resource, newName: string, parentResources = undefined) => { const newPath = resource.path.substring(0, resource.path.length - resource.name.length) + newName if (!newName) { - return store.dispatch('setModalInputErrorMessage', $gettext('The name cannot be empty')) + return $gettext('The name cannot be empty') } if (/[/]/.test(newName)) { - return store.dispatch('setModalInputErrorMessage', $gettext('The name cannot contain "/"')) + return $gettext('The name cannot contain "/"') } if (newName === '.') { - return store.dispatch( - 'setModalInputErrorMessage', - $gettext('The name cannot be equal to "."') - ) + return $gettext('The name cannot be equal to "."') } if (newName === '..') { - return store.dispatch( - 'setModalInputErrorMessage', - $gettext('The name cannot be equal to ".."') - ) + return $gettext('The name cannot be equal to ".."') } if (/\s+$/.test(newName)) { - return store.dispatch( - 'setModalInputErrorMessage', - $gettext('The name cannot end with whitespace') - ) + return $gettext('The name cannot end with whitespace') } const exists = store.getters['Files/files'].find( @@ -68,10 +59,7 @@ export const useFileActionsRename = ({ store }: { store?: Store } = {}) => ) if (exists) { const translated = $gettext('The name "%{name}" is already taken') - return store.dispatch( - 'setModalInputErrorMessage', - $gettext(translated, { name: newName }, true) - ) + return $gettext(translated, { name: newName }, true) } if (parentResources) { @@ -81,19 +69,14 @@ export const useFileActionsRename = ({ store }: { store?: Store } = {}) => if (exists) { const translated = $gettext('The name "%{name}" is already taken') - - return store.dispatch( - 'setModalInputErrorMessage', - $gettext(translated, { name: newName }, true) - ) + return $gettext(translated, { name: newName }, true) } } - store.dispatch('setModalInputErrorMessage', null) + return null } const renameResource = async (space: SpaceResource, resource: Resource, newName: string) => { - store.dispatch('toggleModalConfirmButton') let currentFolder = store.getters['Files/currentFolder'] try { @@ -101,7 +84,6 @@ export const useFileActionsRename = ({ store }: { store?: Store } = {}) => await (clientService.webdav as WebDAV).moveFiles(space, resource, space, { path: newPath }) - store.dispatch('hideModal') const isCurrentFolder = isSameResource(resource, currentFolder) @@ -142,7 +124,6 @@ export const useFileActionsRename = ({ store }: { store?: Store } = {}) => store.commit('Files/UPSERT_RESOURCE', fileResource) } catch (error) { console.error(error) - store.dispatch('toggleModalConfirmButton') let title = $gettext( 'Failed to rename "%{file}" to "%{newName}"', { file: resource.name, newName }, @@ -175,18 +156,20 @@ export const useFileActionsRename = ({ store }: { store?: Store } = {}) => } const areFileExtensionsShown = store.state.Files.areFileExtensionsShown - const confirmAction = (newName) => { + const onConfirm = async (newName: string) => { if (!areFileExtensionsShown) { newName = `${newName}.${resources[0].extension}` } - return renameResource(space, resources[0], newName) + await renameResource(space, resources[0], newName) } - const checkName = (newName) => { + const checkName = (newName: string, setError: (error: string) => void) => { if (!areFileExtensionsShown) { newName = `${newName}.${resources[0].extension}` } - checkNewName(resources[0], newName, parentResources) + + const error = getNameErrorMsg(resources[0], newName, parentResources) + setError(error) } const nameWithoutExtension = extractNameWithoutExtension(resources[0]) const modalTitle = @@ -200,23 +183,21 @@ export const useFileActionsRename = ({ store }: { store?: Store } = {}) => !resources[0].isFolder && !areFileExtensionsShown ? nameWithoutExtension : resources[0].name const inputSelectionRange = - resources[0].isFolder || !areFileExtensionsShown ? null : [0, nameWithoutExtension.length] + resources[0].isFolder || !areFileExtensionsShown + ? null + : ([0, nameWithoutExtension.length] as [number, number]) - const modal = { + registerModal({ variation: 'passive', title, - cancelText: $gettext('Cancel'), confirmText: $gettext('Rename'), hasInput: true, inputValue, inputSelectionRange, inputLabel: resources[0].isFolder ? $gettext('Folder name') : $gettext('File name'), - onCancel: () => store.dispatch('hideModal'), - onConfirm: (args) => loadingService.addTask(() => confirmAction(args)), + onConfirm, onInput: checkName - } - - store.dispatch('createModal', modal) + }) } const actions = computed((): FileAction[] => [ @@ -264,7 +245,7 @@ export const useFileActionsRename = ({ store }: { store?: Store } = {}) => return { actions, // HACK: exported for unit tests: - checkNewName, + getNameErrorMsg, renameResource } } diff --git a/packages/web-pkg/src/composables/actions/files/useFileActionsRestore.ts b/packages/web-pkg/src/composables/actions/files/useFileActionsRestore.ts index eca1d76409e..d751a6acbec 100644 --- a/packages/web-pkg/src/composables/actions/files/useFileActionsRestore.ts +++ b/packages/web-pkg/src/composables/actions/files/useFileActionsRestore.ts @@ -24,6 +24,7 @@ import { computed, unref } from 'vue' import { useGettext } from 'vue3-gettext' import { FileAction, FileActionOptions } from '../types' import { LoadingTaskCallbackArguments } from '../../../services' +import { useModals } from '../../piniaStores' export const useFileActionsRestore = ({ store }: { store?: Store } = {}) => { store = store || useStore() @@ -31,6 +32,7 @@ export const useFileActionsRestore = ({ store }: { store?: Store } = {}) => const { $gettext, $ngettext } = useGettext() const clientService = useClientService() const loadingService = useLoadingService() + const { registerModal } = useModals() const hasSpacesEnabled = useCapabilitySpacesEnabled() @@ -92,8 +94,6 @@ export const useFileActionsRestore = ({ store }: { store?: Store } = {}) => } const remainingConflictCount = allConflictsCount - count const conflictDialog = new ConflictDialog( - (...args) => store.dispatch('createModal', ...args), - (...args) => store.dispatch('hideModal', ...args), (...args) => store.dispatch('showMessage', ...args), (...args) => store.dispatch('showErrorMessage', ...args), $gettext, diff --git a/packages/web-pkg/src/composables/actions/helpers/useFileActionsDeleteResources.ts b/packages/web-pkg/src/composables/actions/helpers/useFileActionsDeleteResources.ts index e08bb8a4b63..823d8a3f35d 100644 --- a/packages/web-pkg/src/composables/actions/helpers/useFileActionsDeleteResources.ts +++ b/packages/web-pkg/src/composables/actions/helpers/useFileActionsDeleteResources.ts @@ -17,6 +17,7 @@ import { useRouter } from '../../router' import { useStore } from '../../store' import { useGettext } from 'vue3-gettext' import { ref } from 'vue' +import { useModals } from '../../piniaStores' export const useFileActionsDeleteResources = ({ store }: { store?: Store }) => { store = store || useStore() @@ -28,6 +29,7 @@ export const useFileActionsDeleteResources = ({ store }: { store?: Store }) const clientService = useClientService() const loadingService = useLoadingService() const { owncloudSdk } = clientService + const { registerModal } = useModals() const queue = new PQueue({ concurrency: 4 }) const deleteOps = [] @@ -137,7 +139,7 @@ export const useFileActionsDeleteResources = ({ store }: { store?: Store }) }) } - const trashbin_delete = (space: SpaceResource) => { + const trashbin_delete = async (space: SpaceResource) => { // TODO: use clear all if all files are selected // FIXME: Implement proper batch delete and add loading indicator for (const file of unref(resources)) { @@ -147,10 +149,7 @@ export const useFileActionsDeleteResources = ({ store }: { store?: Store }) deleteOps.push(p) } - Promise.all(deleteOps).then(() => { - store.dispatch('hideModal') - store.dispatch('toggleModalConfirmButton') - }) + await Promise.all(deleteOps) } const filesList_delete = (resources: Resource[]) => { @@ -237,21 +236,13 @@ export const useFileActionsDeleteResources = ({ store }: { store?: Store }) const displayDialog = (space: SpaceResource, resources: Resource[]) => { resourcesToDelete.value = [...resources] - const modal = { + registerModal({ variation: 'danger', title: unref(dialogTitle), message: unref(dialogMessage), - cancelText: $gettext('Cancel'), confirmText: $gettext('Delete'), - onCancel: () => { - store.dispatch('hideModal') - }, - onConfirm: () => { - trashbin_delete(space) - } - } - - store.dispatch('createModal', modal) + onConfirm: () => trashbin_delete(space) + }) } return { diff --git a/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsDelete.ts b/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsDelete.ts index 1f609678c47..ccbd2bafc0c 100644 --- a/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsDelete.ts +++ b/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsDelete.ts @@ -2,7 +2,6 @@ import { computed, unref } from 'vue' import { useGettext } from 'vue3-gettext' import { SpaceResource } from '@ownclouders/web-client/src' import { useClientService } from '../../clientService' -import { useLoadingService } from '../../loadingService' import { useRoute } from '../../router' import { eventBus } from '../../../services' import { useAbility } from '../../ability' @@ -10,14 +9,15 @@ import { useStore } from '../../store' import { SpaceAction, SpaceActionOptions } from '../types' import { Store } from 'vuex' import { isProjectSpaceResource } from '@ownclouders/web-client/src/helpers' +import { useModals } from '../../piniaStores' export const useSpaceActionsDelete = ({ store }: { store?: Store } = {}) => { store = store || useStore() const { $gettext, $ngettext } = useGettext() const ability = useAbility() const clientService = useClientService() - const loadingService = useLoadingService() const route = useRoute() + const { registerModal } = useModals() const filterResourcesToDelete = (resources: SpaceResource[]) => { return resources.filter( @@ -34,9 +34,8 @@ export const useSpaceActionsDelete = ({ store }: { store?: Store } = {}) => return true }) ) - const results = await loadingService.addTask(() => { - return Promise.allSettled(promises) - }) + const results = await Promise.allSettled(promises) + const succeeded = results.filter((r) => r.status === 'fulfilled') if (succeeded.length) { const title = @@ -72,7 +71,6 @@ export const useSpaceActionsDelete = ({ store }: { store?: Store } = {}) => }) } - store.dispatch('hideModal') if (unref(route).name === 'admin-settings-spaces') { eventBus.publish('app.admin-settings.list.load') } @@ -89,10 +87,8 @@ export const useSpaceActionsDelete = ({ store }: { store?: Store } = {}) => allowedResources.length, { count: allowedResources.length.toString() } ) - const confirmText = $gettext('Delete') - const modal = { - variation: 'danger', + registerModal({ title: $ngettext( 'Delete Space "%{space}"?', 'Delete %{spaceCount} Spaces?', @@ -102,15 +98,11 @@ export const useSpaceActionsDelete = ({ store }: { store?: Store } = {}) => spaceCount: allowedResources.length.toString() } ), - cancelText: $gettext('Cancel'), - confirmText, + confirmText: $gettext('Delete'), message: message, hasInput: false, - onCancel: () => store.dispatch('hideModal'), onConfirm: () => deleteSpaces(allowedResources) - } - - store.dispatch('createModal', modal) + }) } const actions = computed((): SpaceAction[] => [ diff --git a/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsDisable.ts b/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsDisable.ts index ba29bce35bb..f5a33449ebb 100644 --- a/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsDisable.ts +++ b/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsDisable.ts @@ -6,9 +6,9 @@ import { useRoute, useRouter } from '../../router' import { useStore } from '../../store' import { useAbility } from '../../ability' import { useClientService } from '../../clientService' -import { useLoadingService } from '../../loadingService' import { Store } from 'vuex' import { isProjectSpaceResource } from '@ownclouders/web-client/src/helpers' +import { useModals } from '../../piniaStores' export const useSpaceActionsDisable = ({ store }: { store?: Store } = {}) => { store = store || useStore() @@ -17,9 +17,9 @@ export const useSpaceActionsDisable = ({ store }: { store?: Store } = {}) = const clientService = useClientService() const route = useRoute() const router = useRouter() - const loadingService = useLoadingService() + const { registerModal } = useModals() - const filterResourcesToDisable = (resources): SpaceResource[] => { + const filterResourcesToDisable = (resources: SpaceResource[]): SpaceResource[] => { return resources.filter( (r) => isProjectSpaceResource(r) && r.canDisable({ user: store.getters.user, ability }) ) @@ -46,9 +46,8 @@ export const useSpaceActionsDisable = ({ store }: { store?: Store } = {}) = return true }) ) - const results = await loadingService.addTask(() => { - return Promise.allSettled(promises) - }) + const results = await Promise.allSettled(promises) + const succeeded = results.filter((r) => r.status === 'fulfilled') if (succeeded.length) { const title = @@ -83,8 +82,6 @@ export const useSpaceActionsDisable = ({ store }: { store?: Store } = {}) = errors: (failed as PromiseRejectedResult[]).map((f) => f.reason) }) } - - store.dispatch('hideModal') } const handler = ({ resources }: SpaceActionOptions) => { @@ -99,8 +96,8 @@ export const useSpaceActionsDisable = ({ store }: { store?: Store } = {}) = { count: allowedResources.length.toString() } ) const confirmText = $gettext('Disable') - const modal = { - variation: 'danger', + + registerModal({ title: $ngettext( 'Disable Space "%{space}"?', 'Disable %{spaceCount} Spaces?', @@ -110,15 +107,11 @@ export const useSpaceActionsDisable = ({ store }: { store?: Store } = {}) = spaceCount: allowedResources.length.toString() } ), - cancelText: $gettext('Cancel'), confirmText, message, hasInput: false, - onCancel: () => store.dispatch('hideModal'), onConfirm: () => disableSpaces(allowedResources) - } - - store.dispatch('createModal', modal) + }) } const actions = computed((): SpaceAction[] => [ diff --git a/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsEditDescription.ts b/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsEditDescription.ts index f5c69bd154b..f1e06624fcc 100644 --- a/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsEditDescription.ts +++ b/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsEditDescription.ts @@ -8,6 +8,7 @@ import { useStore } from '../../store' import { useGettext } from 'vue3-gettext' import { Store } from 'vuex' import { SpaceResource } from '@ownclouders/web-client/src' +import { useModals } from '../../piniaStores' export const useSpaceActionsEditDescription = ({ store }: { store?: Store } = {}) => { store = store || useStore() @@ -15,13 +16,13 @@ export const useSpaceActionsEditDescription = ({ store }: { store?: Store } const ability = useAbility() const clientService = useClientService() const route = useRoute() + const { registerModal } = useModals() const editDescriptionSpace = (space: SpaceResource, description: string) => { const graphClient = clientService.graphAuthenticated return graphClient.drives .updateDrive(space.id as string, { description } as Drive, {}) .then(() => { - store.dispatch('hideModal') store.commit('runtime/spaces/UPDATE_SPACE_FIELD', { id: space.id, field: 'description', @@ -48,19 +49,14 @@ export const useSpaceActionsEditDescription = ({ store }: { store?: Store } return } - const modal = { - variation: 'passive', + registerModal({ title: $gettext('Change subtitle for space') + ' ' + resources[0].name, - cancelText: $gettext('Cancel'), confirmText: $gettext('Confirm'), hasInput: true, inputLabel: $gettext('Space subtitle'), inputValue: resources[0].description, - onCancel: () => store.dispatch('hideModal'), - onConfirm: (description) => editDescriptionSpace(resources[0], description) - } - - store.dispatch('createModal', modal) + onConfirm: (description: string) => editDescriptionSpace(resources[0], description) + }) } const actions = computed((): SpaceAction[] => [ diff --git a/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsEditQuota.ts b/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsEditQuota.ts index a7758f7b8cd..b4c6c14dffe 100644 --- a/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsEditQuota.ts +++ b/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsEditQuota.ts @@ -1,14 +1,13 @@ -import { Store } from 'vuex' import { computed } from 'vue' import { SpaceAction, SpaceActionOptions } from '../types' import { useGettext } from 'vue3-gettext' import { useAbility } from '../../ability' import { SpaceResource, isProjectSpaceResource } from '@ownclouders/web-client/src/helpers' -import { useStore } from '../../store' import { QuotaModal } from '../../../components' +import { useModals } from '../../piniaStores' -export const useSpaceActionsEditQuota = ({ store }: { store?: Store } = {}) => { - store = store || useStore() +export const useSpaceActionsEditQuota = () => { + const { registerModal } = useModals() const { $gettext } = useGettext() const ability = useAbility() @@ -24,15 +23,13 @@ export const useSpaceActionsEditQuota = ({ store }: { store?: Store } = {}) } const handler = ({ resources }: SpaceActionOptions) => { - return store.dispatch('createModal', { - variation: 'passive', + registerModal({ title: getModalTitle({ resources }), customComponent: QuotaModal, customComponentAttrs: () => ({ spaces: resources, resourceType: 'space' - }), - hideActions: true + }) }) } diff --git a/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsEditReadmeContent.ts b/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsEditReadmeContent.ts index 21e356896c3..1e53f2b13e0 100644 --- a/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsEditReadmeContent.ts +++ b/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsEditReadmeContent.ts @@ -4,14 +4,15 @@ import { SpaceAction, SpaceActionOptions } from '../types' import { computed } from 'vue' import { useGettext } from 'vue3-gettext' import { ReadmeContentModal } from '../../../components' +import { useModals } from '../../piniaStores' export const useSpaceActionsEditReadmeContent = ({ store }: { store?: Store } = {}) => { store = store || useStore() + const { registerModal } = useModals() const { $gettext } = useGettext() const handler = ({ resources }: SpaceActionOptions) => { - return store.dispatch('createModal', { - variation: 'passive', + registerModal({ title: $gettext('Edit description for space %{name}', { name: resources[0].name }), diff --git a/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsRename.ts b/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsRename.ts index c60ade7b2b6..6f6d14ab0c3 100644 --- a/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsRename.ts +++ b/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsRename.ts @@ -7,6 +7,8 @@ import { useAbility } from '../../ability' import { useRoute } from '../../router' import { useStore } from '../../store' import { SpaceAction, SpaceActionOptions } from '../types' +import { SpaceResource } from '@ownclouders/web-client' +import { useModals } from '../../piniaStores' export const useSpaceActionsRename = ({ store }: { store?: Store } = {}) => { store = store || useStore() @@ -15,13 +17,13 @@ export const useSpaceActionsRename = ({ store }: { store?: Store } = {}) => const clientService = useClientService() const route = useRoute() const { checkSpaceNameModalInput } = useSpaceHelpers() + const { registerModal } = useModals() - const renameSpace = (space, name) => { + const renameSpace = (space: SpaceResource, name: string) => { const graphClient = clientService.graphAuthenticated return graphClient.drives .updateDrive(space.id, { name }, {}) .then(() => { - store.dispatch('hideModal') if (unref(route).name === 'admin-settings-spaces') { space.name = name } @@ -48,20 +50,15 @@ export const useSpaceActionsRename = ({ store }: { store?: Store } = {}) => return } - const modal = { - variation: 'passive', + registerModal({ title: $gettext('Rename space') + ' ' + resources[0].name, - cancelText: $gettext('Cancel'), confirmText: $gettext('Rename'), hasInput: true, inputLabel: $gettext('Space name'), inputValue: resources[0].name, - onCancel: () => store.dispatch('hideModal'), - onConfirm: (name) => renameSpace(resources[0], name), + onConfirm: (name: string) => renameSpace(resources[0], name), onInput: checkSpaceNameModalInput - } - - store.dispatch('createModal', modal) + }) } const actions = computed((): SpaceAction[] => [ diff --git a/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsRestore.ts b/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsRestore.ts index 70da21b4e67..f977d386918 100644 --- a/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsRestore.ts +++ b/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsRestore.ts @@ -9,6 +9,7 @@ import { useLoadingService } from '../../loadingService' import { useStore } from '../../store' import { useGettext } from 'vue3-gettext' import { isProjectSpaceResource } from '@ownclouders/web-client/src/helpers' +import { useModals } from '../../piniaStores' export const useSpaceActionsRestore = ({ store }: { store?: Store } = {}) => { store = store || useStore() @@ -17,6 +18,7 @@ export const useSpaceActionsRestore = ({ store }: { store?: Store } = {}) = const clientService = useClientService() const loadingService = useLoadingService() const route = useRoute() + const { registerModal } = useModals() const filterResourcesToRestore = (resources): SpaceResource[] => { return resources.filter( @@ -87,8 +89,6 @@ export const useSpaceActionsRestore = ({ store }: { store?: Store } = {}) = errors: (failed as PromiseRejectedResult[]).map((f) => f.reason) }) } - - store.dispatch('hideModal') } const handler = ({ resources }: SpaceActionOptions) => { @@ -104,8 +104,7 @@ export const useSpaceActionsRestore = ({ store }: { store?: Store } = {}) = ) const confirmText = $gettext('Enable') - const modal = { - variation: 'passive', + registerModal({ title: $ngettext( 'Enable Space "%{space}"?', 'Enable %{spaceCount} Spaces?', @@ -115,16 +114,12 @@ export const useSpaceActionsRestore = ({ store }: { store?: Store } = {}) = spaceCount: allowedResources.length.toString() } ), - cancelText: $gettext('Cancel'), confirmText, icon: 'alert', message, hasInput: false, - onCancel: () => store.dispatch('hideModal'), onConfirm: () => restoreSpaces(allowedResources) - } - - store.dispatch('createModal', modal) + }) } const actions = computed((): SpaceAction[] => [ diff --git a/packages/web-pkg/src/composables/piniaStores/index.ts b/packages/web-pkg/src/composables/piniaStores/index.ts index 08dd4593f0c..99c17ac4c57 100644 --- a/packages/web-pkg/src/composables/piniaStores/index.ts +++ b/packages/web-pkg/src/composables/piniaStores/index.ts @@ -1,2 +1,3 @@ export * from './extensionRegistry' +export * from './modals' export * from './theme' diff --git a/packages/web-pkg/src/composables/piniaStores/modals.ts b/packages/web-pkg/src/composables/piniaStores/modals.ts new file mode 100644 index 00000000000..806ffb51968 --- /dev/null +++ b/packages/web-pkg/src/composables/piniaStores/modals.ts @@ -0,0 +1,102 @@ +import { ContextualHelperData } from 'design-system/src/helpers' +import { defineStore } from 'pinia' +import { v4 as uuidV4 } from 'uuid' +import { Component, ComponentPublicInstance, computed, ref, unref } from 'vue' + +export type CustomModalComponent = Component<{ modal: Modal } & Record> + +export type CustomModalComponentEmits = { + confirm?: () => Promise + cancel?: () => void + 'update:confirmDisabled': (value: boolean) => void +} & Record + +export type CustomModalComponentInstance = ComponentPublicInstance< + { modal: Modal } & unknown, + { onConfirm?: () => Promise; onCancel?: () => unknown }, + any, + any, + any, + CustomModalComponentEmits +> + +export type Modal = { + id: string + active: boolean + title: string + variation?: string + icon?: string + message?: string + cancelText?: string + confirmDisabled?: boolean + confirmText?: string + hideActions?: boolean + hideConfirmButton?: boolean + hasInput?: boolean + inputValue?: string + inputType?: string + inputSelectionRange?: [number, number] + inputLabel?: string + inputError?: string + inputDescription?: string + focusTrapInitial?: string + contextualHelperLabel?: string + contextualHelperData?: ContextualHelperData + customComponent?: CustomModalComponent + customComponentAttrs?: () => Record + onCancel?: () => void + onConfirm?: (value: string) => Promise + onInput?: (value: string, setError: (error: string) => void) => void +} + +export const useModals = defineStore('modals', () => { + const modals = ref([]) + const activeModal = computed(() => unref(modals).find(({ active }) => active === true)) + + const getModal = (id: Modal['id']) => { + return unref(modals).find((modal) => modal.id === id) + } + + const registerModal = ( + data: Omit, + { isActive = true }: { isActive?: boolean } = {} + ) => { + const modal = { ...data, id: uuidV4() as string, active: isActive } + + if (isActive && unref(activeModal)) { + unref(activeModal).active = false + } + + modals.value.push(modal) + return modal + } + + const updateModal = ( + id: T['id'], + key: K, + value: T[K] + ) => { + const modal = getModal(id) + modal[key] = value + } + + const removeModal = (id: Modal['id']) => { + modals.value = unref(modals).filter((modal) => modal.id !== id) + + if (unref(modals).length && !unref(activeModal)) { + // set next modal active + unref(modals)[0].active = true + } + } + + const setModalActive = (id: Modal['id']) => { + if (unref(activeModal)) { + activeModal.value.active = false + } + + const modal = getModal(id) + modal.active = true + } + + return { modals, activeModal, registerModal, updateModal, removeModal, setModalActive } +}) diff --git a/packages/web-pkg/src/composables/piniaStores/theme.ts b/packages/web-pkg/src/composables/piniaStores/theme.ts index 335ed272f10..7e4a5ead09b 100644 --- a/packages/web-pkg/src/composables/piniaStores/theme.ts +++ b/packages/web-pkg/src/composables/piniaStores/theme.ts @@ -76,8 +76,8 @@ export const ThemingConfig = z.object({ }) }) -type WebThemeType = z.infer -type WebThemeConfigType = z.infer +export type WebThemeType = z.infer +export type WebThemeConfigType = z.infer const themeStorageKey = 'oc_currentThemeName' diff --git a/packages/web-pkg/src/composables/spaces/useSpaceHelpers.ts b/packages/web-pkg/src/composables/spaces/useSpaceHelpers.ts index 686da0e1639..f4fc0aef5e5 100644 --- a/packages/web-pkg/src/composables/spaces/useSpaceHelpers.ts +++ b/packages/web-pkg/src/composables/spaces/useSpaceHelpers.ts @@ -1,27 +1,21 @@ -import { useStore } from '../store' import { useGettext } from 'vue3-gettext' export const useSpaceHelpers = () => { - const store = useStore() const { $gettext } = useGettext() - const checkSpaceNameModalInput = (name) => { + const checkSpaceNameModalInput = (name: string, setError: (value: string) => void) => { if (name.trim() === '') { - return store.dispatch('setModalInputErrorMessage', $gettext('Space name cannot be empty')) + return setError($gettext('Space name cannot be empty')) } if (name.length > 255) { - return store.dispatch( - 'setModalInputErrorMessage', - $gettext('Space name cannot exceed 255 characters') - ) + return setError($gettext('Space name cannot exceed 255 characters')) } if (/[/\\.:?*"><|]/.test(name)) { - return store.dispatch( - 'setModalInputErrorMessage', + return setError( $gettext('Space name cannot contain the following characters: / \\\\ . : ? * " > < |\'') ) } - store.dispatch('setModalInputErrorMessage', null) + return setError(null) } return { checkSpaceNameModalInput } diff --git a/packages/web-pkg/src/helpers/resource/conflictHandling/conflictDialog.ts b/packages/web-pkg/src/helpers/resource/conflictHandling/conflictDialog.ts index de27f5a967f..9a938cf3dd4 100644 --- a/packages/web-pkg/src/helpers/resource/conflictHandling/conflictDialog.ts +++ b/packages/web-pkg/src/helpers/resource/conflictHandling/conflictDialog.ts @@ -1,7 +1,8 @@ import { join } from 'path' import { Resource } from '@ownclouders/web-client' import { ResolveConflict, ResolveStrategy } from '.' -import { ResourceConflictModal } from '../../../components' +import { useModals } from '../../../composables' +import { SpaceMoveInfoModal, ResourceConflictModal } from '../../../components' export interface FileConflict { resource: Resource @@ -11,8 +12,6 @@ export interface FileConflict { export class ConflictDialog { /* eslint-disable no-useless-constructor */ constructor( - protected createModal: (modal: object) => void, - protected hideModal: () => void, protected showMessage: (data: object) => void, protected showErrorMessage: (data: object) => void, protected $gettext: ( @@ -82,8 +81,10 @@ export class ConflictDialog { suggestMerge = false, separateSkipHandling = false // separate skip-handling between files and folders ): Promise { + const { registerModal } = useModals() + return new Promise((resolve) => { - this.createModal({ + registerModal({ variation: 'danger', title: resource.isFolder ? this.$gettext('Folder already exists') @@ -104,27 +105,19 @@ export class ConflictDialog { } resolveDoCopyInsteadOfMoveForSpaces(): Promise { + const { registerModal } = useModals() + return new Promise((resolve) => { - const modal = { + registerModal({ variation: 'danger', title: this.$gettext('Copy here?'), - customContent: `

${this.$gettext( - 'Moving files from one space to another is not possible. Do you want to copy instead?' - )}

${this.$gettext( - 'Note: Links and shares of the original file are not copied.' - )}

`, - cancelText: this.$gettext('Cancel'), + customComponent: SpaceMoveInfoModal, confirmText: this.$gettext('Copy here'), onCancel: () => { - this.hideModal() resolve(false) }, - onConfirm: () => { - this.hideModal() - resolve(true) - } - } - this.createModal(modal) + onConfirm: () => Promise.resolve(resolve(true)) + }) }) } } diff --git a/packages/web-pkg/tests/unit/components/AppBanner.spec.ts b/packages/web-pkg/tests/unit/components/AppBanner.spec.ts index 473f6edd950..6b6457cd685 100644 --- a/packages/web-pkg/tests/unit/components/AppBanner.spec.ts +++ b/packages/web-pkg/tests/unit/components/AppBanner.spec.ts @@ -3,7 +3,6 @@ import AppBanner from '../../../src/components/AppBanner.vue' import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router' import { useSessionStorage } from '@vueuse/core' import { ref } from 'vue' -import { createMockThemeStore } from 'web-test-helpers/src/mocks/pinia' jest.mock('@vueuse/core') @@ -62,15 +61,20 @@ function getWrapper({ fileId, sessionStorageReturnValue }) { }, global: { plugins: [ - ...defaultPlugins(), - createMockThemeStore({ - appBanner: { - title: 'ownCloud', - publisher: 'ownCloud GmbH', - additionalInformation: '', - ctaText: 'OPEN', - icon: 'themes/owncloud/assets/owncloud-app-icon.png', - appScheme: 'owncloud' + ...defaultPlugins({ + piniaOptions: { + themeState: { + currentTheme: { + appBanner: { + title: 'ownCloud', + publisher: 'ownCloud GmbH', + additionalInformation: '', + ctaText: 'OPEN', + icon: 'themes/owncloud/assets/owncloud-app-icon.png', + appScheme: 'owncloud' + } + } + } } }) ], diff --git a/packages/web-pkg/tests/unit/components/CreateLinkModal.spec.ts b/packages/web-pkg/tests/unit/components/CreateLinkModal.spec.ts index 2f816716553..90193d401b2 100644 --- a/packages/web-pkg/tests/unit/components/CreateLinkModal.spec.ts +++ b/packages/web-pkg/tests/unit/components/CreateLinkModal.spec.ts @@ -126,28 +126,38 @@ describe('CreateLinkModal', () => { }) describe('method "confirm"', () => { it('shows an error if a password is enforced but empty', async () => { + jest.spyOn(console, 'error').mockImplementation(undefined) const { wrapper } = getWrapper({ passwordEnforced: true }) - await wrapper.find(selectors.confirmBtn).trigger('click') + try { + await wrapper.vm.onConfirm() + } catch (error) {} + expect(wrapper.vm.password.error).toBeDefined() }) it('does not create links when the password policy is not fulfilled', async () => { - const { wrapper, storeOptions } = getWrapper({ passwordPolicyFulfilled: false }) - await wrapper.find(selectors.confirmBtn).trigger('click') - expect(storeOptions.actions.hideModal).not.toHaveBeenCalled() + jest.spyOn(console, 'error').mockImplementation(undefined) + const callbackFn = jest.fn() + const { wrapper } = getWrapper({ passwordPolicyFulfilled: false, callbackFn }) + try { + await wrapper.vm.onConfirm() + } catch (error) {} + + expect(callbackFn).not.toHaveBeenCalled() }) it('creates links for all resources', async () => { + const callbackFn = jest.fn() const resources = [mock({ isFolder: false }), mock({ isFolder: false })] - const { wrapper, storeOptions, mocks } = getWrapper({ resources }) - await wrapper.find(selectors.confirmBtn).trigger('click') + const { wrapper, mocks } = getWrapper({ resources, callbackFn }) + await wrapper.vm.onConfirm() expect(mocks.createLinkMock).toHaveBeenCalledTimes(resources.length) - expect(storeOptions.actions.hideModal).toHaveBeenCalledTimes(1) + expect(callbackFn).toHaveBeenCalledTimes(1) }) it('emits event in embed mode including the created links', async () => { const resources = [mock({ isFolder: false })] const { wrapper, mocks } = getWrapper({ resources, embedModeEnabled: true }) const share = mock({ url: 'someurl' }) mocks.createLinkMock.mockResolvedValue(share) - await wrapper.find(selectors.confirmBtn).trigger('click') + await wrapper.vm.onConfirm() expect(mocks.postMessageMock).toHaveBeenCalledWith('owncloud-embed:share', [share.url]) }) it('shows error messages for links that failed to be created', async () => { @@ -156,14 +166,14 @@ describe('CreateLinkModal', () => { const resources = [mock({ isFolder: false })] const { wrapper, mocks } = getWrapper({ resources }) mocks.createLinkMock.mockRejectedValue(new Error('')) - await wrapper.find(selectors.confirmBtn).trigger('click') + await wrapper.vm.onConfirm() expect(consoleMock).toHaveBeenCalledTimes(1) }) it('calls the callback at the end if given', async () => { const resources = [mock({ isFolder: false })] const callbackFn = jest.fn() const { wrapper } = getWrapper({ resources, callbackFn }) - await wrapper.find(selectors.confirmBtn).trigger('click') + await wrapper.vm.onConfirm() expect(callbackFn).toHaveBeenCalledTimes(1) }) it.each([true, false])( @@ -171,20 +181,13 @@ describe('CreateLinkModal', () => { async (isQuickLink) => { const resources = [mock({ isFolder: false })] const { wrapper, mocks } = getWrapper({ resources, isQuickLink }) - await wrapper.find(selectors.confirmBtn).trigger('click') + await wrapper.vm.onConfirm() expect(mocks.createLinkMock).toHaveBeenCalledWith( expect.objectContaining({ quicklink: isQuickLink }) ) } ) }) - describe('method "cancel"', () => { - it('hides the modal', async () => { - const { wrapper, storeOptions } = getWrapper() - await wrapper.find(selectors.cancelBtn).trigger('click') - expect(storeOptions.actions.hideModal).toHaveBeenCalledTimes(1) - }) - }) }) function getWrapper({ diff --git a/packages/web-pkg/tests/unit/components/Modals/ResourceConflictModal.spec.ts b/packages/web-pkg/tests/unit/components/Modals/ResourceConflictModal.spec.ts index 732a83ab21c..76dbb1f562c 100644 --- a/packages/web-pkg/tests/unit/components/Modals/ResourceConflictModal.spec.ts +++ b/packages/web-pkg/tests/unit/components/Modals/ResourceConflictModal.spec.ts @@ -9,6 +9,7 @@ import { import { mock } from 'jest-mock-extended' import { Resource } from '@ownclouders/web-client' import { ResolveStrategy } from '../../../../src/helpers/resource' +import { Modal } from '../../../../src/composables/piniaStores' describe('ResourceConflictModal', () => { describe('checkbox', () => { @@ -28,7 +29,7 @@ describe('ResourceConflictModal', () => { await wrapper.vm.onConfirm() expect(callbackFn).toHaveBeenCalledWith({ strategy: ResolveStrategy.KEEP_BOTH, - doForAllConflicts: undefined + doForAllConflicts: false }) }) }) @@ -39,7 +40,7 @@ describe('ResourceConflictModal', () => { await wrapper.vm.onConfirmSecondary() expect(callbackFn).toHaveBeenCalledWith({ strategy: ResolveStrategy.MERGE, - doForAllConflicts: undefined + doForAllConflicts: false }) }) it('should call the callback with replace strategy if merge not suggested', async () => { @@ -48,7 +49,7 @@ describe('ResourceConflictModal', () => { await wrapper.vm.onConfirmSecondary() expect(callbackFn).toHaveBeenCalledWith({ strategy: ResolveStrategy.REPLACE, - doForAllConflicts: undefined + doForAllConflicts: false }) }) }) @@ -59,7 +60,7 @@ describe('ResourceConflictModal', () => { await wrapper.vm.onCancel() expect(callbackFn).toHaveBeenCalledWith({ strategy: ResolveStrategy.SKIP, - doForAllConflicts: undefined + doForAllConflicts: false }) }) }) @@ -74,6 +75,7 @@ function getWrapper({ props = {} } = {}) { mocks, wrapper: shallowMount(ResourceConflictModal, { props: { + modal: mock(), resource: mock, conflictCount: 1, callbackFn: () => ({}), diff --git a/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateLink.spec.ts b/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateLink.spec.ts index 77b35e4b67a..12cb69244cb 100644 --- a/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateLink.spec.ts +++ b/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateLink.spec.ts @@ -1,5 +1,6 @@ import { computed, unref } from 'vue' import { useFileActionsCreateLink } from '../../../../../src/composables/actions/files/useFileActionsCreateLink' +import { useModals } from '../../../../../src/composables/piniaStores' import { createStore, defaultComponentMocks, @@ -85,10 +86,11 @@ describe('useFileActionsCreateLink', () => { it('shows a modal if enforced', () => { getWrapper({ enforceModal: true, - setup: ({ actions }, { mocks, storeOptions }) => { + setup: ({ actions }, { mocks }) => { + const { registerModal } = useModals() unref(actions)[0].handler({ resources: [mock({ canShare: () => true })] }) expect(mocks.createLinkMock).not.toHaveBeenCalled() - expect(storeOptions.actions.createModal).toHaveBeenCalledTimes(1) + expect(registerModal).toHaveBeenCalledTimes(1) } }) }) @@ -96,10 +98,11 @@ describe('useFileActionsCreateLink', () => { getWrapper({ passwordEnforced: true, defaultLinkPermissions: SharePermissionBit.Read, - setup: ({ actions }, { mocks, storeOptions }) => { + setup: ({ actions }, { mocks }) => { + const { registerModal } = useModals() unref(actions)[0].handler({ resources: [mock({ canShare: () => true })] }) expect(mocks.createLinkMock).not.toHaveBeenCalled() - expect(storeOptions.actions.createModal).toHaveBeenCalledTimes(1) + expect(registerModal).toHaveBeenCalledTimes(1) } }) }) diff --git a/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateNewFile.spec.ts b/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateNewFile.spec.ts index eaf440ad87e..f256699703b 100644 --- a/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateNewFile.spec.ts +++ b/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateNewFile.spec.ts @@ -1,6 +1,7 @@ import { mock } from 'jest-mock-extended' import { nextTick, ref, unref } from 'vue' import { useFileActionsCreateNewFile } from '../../../../../src/composables/actions' +import { useModals } from '../../../../../src/composables/piniaStores' import { SpaceResource } from '@ownclouders/web-client/src' import { Resource } from '@ownclouders/web-client/src/helpers' import { FileActionOptions } from '../../../../../src/composables/actions' @@ -20,12 +21,12 @@ describe('useFileActionsCreateNewFile', () => { { input: '.', output: 'File name cannot be equal to "."' }, { input: '..', output: 'File name cannot be equal to ".."' }, { input: 'myfile.txt', output: null } - ])('should validate file name %s', async (data) => { + ])('should validate file name %s', (data) => { const space = mock({ id: '1' }) getWrapper({ space, - setup: async ({ checkNewFileName }) => { - const result = checkNewFileName(data.input) + setup: ({ getNameErrorMsg }) => { + const result = getNameErrorMsg(data.input) expect(result).toBe(data.output) } }) @@ -33,7 +34,7 @@ describe('useFileActionsCreateNewFile', () => { }) describe('addNewFile', () => { - it('create new file', async () => { + it('create new file', () => { const space = mock({ id: '1' }) getWrapper({ space, @@ -41,7 +42,6 @@ describe('useFileActionsCreateNewFile', () => { await addNewFile('myfile.txt', null) await nextTick() expect(storeOptions.modules.Files.mutations.UPSERT_RESOURCE).toHaveBeenCalled() - expect(storeOptions.actions.hideModal).toHaveBeenCalled() expect(storeOptions.actions.showMessage).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ @@ -51,7 +51,7 @@ describe('useFileActionsCreateNewFile', () => { } }) }) - it('show error message if createFile fails', async () => { + it('show error message if createFile fails', () => { const consoleErrorMock = jest.spyOn(console, 'error').mockImplementation() const space = mock({ id: '1' }) getWrapper({ @@ -72,15 +72,16 @@ describe('useFileActionsCreateNewFile', () => { }) }) describe('createNewFileModal', () => { - it('should show modal', async () => { + it('should show modal', () => { const space = mock({ id: '1' }) getWrapper({ space, - setup: async ({ actions }, { storeOptions }) => { + setup: async ({ actions }) => { + const { registerModal } = useModals() const fileActionOptions: FileActionOptions = { space, resources: [] } as FileActionOptions unref(actions)[0].handler(fileActionOptions) await nextTick() - expect(storeOptions.actions.createModal).toHaveBeenCalled() + expect(registerModal).toHaveBeenCalled() } }) }) diff --git a/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateNewFolder.spec.ts b/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateNewFolder.spec.ts index 450c94414d3..c0e99bd25b9 100644 --- a/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateNewFolder.spec.ts +++ b/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateNewFolder.spec.ts @@ -1,6 +1,7 @@ import { mock } from 'jest-mock-extended' import { nextTick, unref } from 'vue' import { useFileActionsCreateNewFolder } from '../../../../../src/composables/actions' +import { useModals } from '../../../../../src/composables/piniaStores' import { SpaceResource } from '@ownclouders/web-client/src' import { FolderResource } from '@ownclouders/web-client/src/helpers' import { @@ -23,13 +24,14 @@ describe('useFileActionsCreateNewFolder', () => { { input: '.', output: 'Folder name cannot be equal to "."' }, { input: '..', output: 'Folder name cannot be equal to ".."' }, { input: 'myfolder', output: null } - ])('should validate folder name %s', async (data) => { + ])('should validate folder name %s', (data) => { const space = mock({ id: '1' }) getWrapper({ space, - setup: async ({ checkNewFolderName }) => { - const result = checkNewFolderName(data.input) - expect(result).toBe(data.output) + setup: ({ checkNewFolderName }) => { + checkNewFolderName(data.input, (str: string) => { + expect(str).toBe(data.output) + }) } }) }) @@ -43,7 +45,6 @@ describe('useFileActionsCreateNewFolder', () => { await addNewFolder('myfolder') await nextTick() expect(storeOptions.modules.Files.mutations.UPSERT_RESOURCE).toHaveBeenCalled() - expect(storeOptions.actions.hideModal).toHaveBeenCalled() expect(storeOptions.actions.showMessage).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ @@ -76,14 +77,15 @@ describe('useFileActionsCreateNewFolder', () => { }) }) describe('createNewFolderModal', () => { - it('should show modal', async () => { + it('should show modal', () => { const space = mock({ id: '1' }) getWrapper({ space, - setup: async ({ actions }, { storeOptions }) => { + setup: ({ actions }) => { + const { registerModal } = useModals() unref(actions)[0].handler() - await nextTick() - expect(storeOptions.actions.createModal).toHaveBeenCalled() + + expect(registerModal).toHaveBeenCalled() } }) }) diff --git a/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateNewShortcut.spec.ts b/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateNewShortcut.spec.ts index 86049b47ad4..39accdf12da 100644 --- a/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateNewShortcut.spec.ts +++ b/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateNewShortcut.spec.ts @@ -7,7 +7,7 @@ import { RouteLocation, getComposableWrapper } from 'web-test-helpers' -import { useFileActionsCreateNewShortcut } from '../../../../../src' +import { useFileActionsCreateNewShortcut, useModals } from '../../../../../src' import { Resource, SpaceResource } from '@ownclouders/web-client' describe('createNewShortcut', () => { @@ -34,9 +34,10 @@ describe('createNewShortcut', () => { describe('method "handler"', () => { it('creates a modal', () => { getWrapper({ - setup: async ({ actions }, { storeOptions }) => { + setup: async ({ actions }) => { + const { registerModal } = useModals() await unref(actions)[0].handler() - expect(storeOptions.actions.createModal).toHaveBeenCalled() + expect(registerModal).toHaveBeenCalled() } }) }) diff --git a/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsEmptyTrashBin.spec.ts b/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsEmptyTrashBin.spec.ts index f92a137c9ec..0aa3b789379 100644 --- a/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsEmptyTrashBin.spec.ts +++ b/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsEmptyTrashBin.spec.ts @@ -1,4 +1,5 @@ import { useFileActionsEmptyTrashBin } from '../../../../../src/composables/actions' +import { useModals } from '../../../../../src/composables/piniaStores' import { createLocationTrash, createLocationSpaces } from '../../../../../src/router' import { mock } from 'jest-mock-extended' import { @@ -18,7 +19,7 @@ describe('emptyTrashBin', () => { describe('isEnabled property', () => { it('should be false when location is invalid', () => { - const { wrapper } = getWrapper({ + getWrapper({ invalidLocation: true, setup: ({ actions }, { space }) => { expect(unref(actions)[0].isEnabled({ space, resources: [] })).toBe(false) @@ -26,7 +27,7 @@ describe('emptyTrashBin', () => { }) }) it('should be false in a space trash bin with insufficient permissions', () => { - const { wrapper } = getWrapper({ + getWrapper({ driveType: 'project', setup: ({ actions }, { space }) => { expect( @@ -41,33 +42,33 @@ describe('emptyTrashBin', () => { }) describe('empty trashbin action', () => { - it('should trigger the empty trash bin modal window', async () => { - const { wrapper } = getWrapper({ - setup: async ({ actions }, { storeOptions }) => { + it('should trigger the empty trash bin modal window', () => { + getWrapper({ + setup: async ({ actions }) => { + const { registerModal } = useModals() await unref(actions)[0].handler(mock()) - expect(storeOptions.actions.createModal).toHaveBeenCalledTimes(1) + expect(registerModal).toHaveBeenCalledTimes(1) } }) }) }) describe('method "emptyTrashBin"', () => { - it('should hide the modal and show message on success', async () => { - const { wrapper } = getWrapper({ + it('should show message on success', () => { + getWrapper({ setup: async ({ emptyTrashBin }, { space, storeOptions }) => { await emptyTrashBin({ space }) - expect(storeOptions.actions.hideModal).toHaveBeenCalledTimes(1) expect(storeOptions.actions.showMessage).toHaveBeenCalledTimes(1) } }) }) - it('should show message on error', async () => { + it('should show message on error', () => { jest.spyOn(console, 'error').mockImplementation(() => undefined) - const { wrapper } = getWrapper({ + getWrapper({ resolveClearTrashBin: false, setup: async ({ emptyTrashBin }, { space, storeOptions }) => { await emptyTrashBin({ space }) diff --git a/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsRename.spec.ts b/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsRename.spec.ts index a0f98f39fe0..4319dca67e0 100644 --- a/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsRename.spec.ts +++ b/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsRename.spec.ts @@ -1,5 +1,6 @@ import { ConfigurationManager, useFileActionsRename, useStore } from '../../../../../src' -import { mockDeep } from 'jest-mock-extended' +import { useModals } from '../../../../../src/composables/piniaStores' +import { mock, mockDeep } from 'jest-mock-extended' import { Resource, SpaceResource } from '@ownclouders/web-client/src/helpers' import { createStore, @@ -40,7 +41,7 @@ describe('rename', () => { expectedStatus: false } ])('should be set correctly', (inputData) => { - const { wrapper } = getWrapper({ + getWrapper({ setup: ({ actions }, { space }) => { const resources = inputData.resources expect(unref(actions)[0].isEnabled({ space, resources })).toBe(inputData.expectedStatus) @@ -51,44 +52,45 @@ describe('rename', () => { }) describe('rename action handler', () => { - it('should trigger the rename modal window', async () => { - const { wrapper } = getWrapper({ - setup: async ({ actions }, { space, storeOptions }) => { + it('should trigger the rename modal window', () => { + getWrapper({ + setup: async ({ actions }, { space }) => { + const { registerModal } = useModals() const resources = [currentFolder] await unref(actions)[0].handler({ space, resources }) - expect(storeOptions.actions.createModal).toHaveBeenCalledTimes(1) + expect(registerModal).toHaveBeenCalledTimes(1) } }) }) }) - describe('method "checkNewName"', () => { + describe('method "getNameErrorMsg"', () => { it('should not show an error if new name not taken', () => { - const { wrapper } = getWrapper({ - setup: ({ checkNewName }, { storeOptions }) => { + getWrapper({ + setup: ({ getNameErrorMsg }, { storeOptions }) => { storeOptions.modules.Files.getters.files.mockReturnValue([ { name: 'file1', path: '/file1' } ]) - checkNewName({ name: 'currentName', path: '/currentName' } as Resource, 'newName') - expect(storeOptions.actions.setModalInputErrorMessage).toHaveBeenCalledWith( - expect.anything(), - null + const message = getNameErrorMsg( + { name: 'currentName', path: '/currentName' } as Resource, + 'newName' ) + expect(message).toEqual(null) } }) }) it('should not show an error if new name already exists but in different folder', () => { - const { wrapper } = getWrapper({ - setup: ({ checkNewName }, { storeOptions }) => { + getWrapper({ + setup: ({ getNameErrorMsg }, { storeOptions }) => { storeOptions.modules.Files.getters.files.mockReturnValue([ { name: 'file1', path: '/file1' } ]) - checkNewName({ name: 'currentName', path: '/favorites/currentName' }, 'file1') - expect(storeOptions.actions.setModalInputErrorMessage).toHaveBeenCalledWith( - expect.anything(), - null + const message = getNameErrorMsg( + mock({ name: 'currentName', path: '/favorites/currentName' }), + 'file1' ) + expect(message).toEqual(null) } }) }) @@ -115,55 +117,51 @@ describe('rename', () => { message: 'The name "newname" is already taken' } ])('should detect name errors and display error messages accordingly', (inputData) => { - const { wrapper } = getWrapper({ - setup: ({ checkNewName }, { storeOptions }) => { - checkNewName( - { name: inputData.currentName, path: `/${inputData.currentName}` }, + getWrapper({ + setup: ({ getNameErrorMsg }) => { + const message = getNameErrorMsg( + mock({ name: inputData.currentName, path: `/${inputData.currentName}` }), inputData.newName, inputData.parentResources ) - expect(storeOptions.actions.setModalInputErrorMessage).toHaveBeenCalledWith( - expect.anything(), - inputData.message - ) + expect(message).toEqual(inputData.message) } }) }) }) describe('method "renameResource"', () => { - it('should call the rename action on a resource in the file list', async () => { - const { wrapper } = getWrapper({ + it('should call the rename action on a resource in the file list', () => { + getWrapper({ setup: async ({ renameResource }, { space, storeOptions }) => { const resource = { id: '2', path: '/folder', webDavPath: '/files/admin/folder' } renameResource(space, resource, 'new name') await nextTick() - expect(storeOptions.actions.hideModal).toHaveBeenCalledTimes(1) + expect(storeOptions.modules.Files.mutations.UPSERT_RESOURCE).toHaveBeenCalledTimes(1) } }) }) - it('should call the rename action on the current folder', async () => { - const { wrapper } = getWrapper({ + it('should call the rename action on the current folder', () => { + getWrapper({ setup: async ({ renameResource }, { space, storeOptions }) => { renameResource(space, currentFolder, 'new name') await nextTick() - expect(storeOptions.actions.hideModal).toHaveBeenCalledTimes(1) + expect(storeOptions.modules.Files.mutations.UPSERT_RESOURCE).toHaveBeenCalledTimes(1) } }) }) - it('should handle errors properly', async () => { + it('should handle errors properly', () => { jest.spyOn(console, 'error').mockImplementation(() => undefined) - const { wrapper } = getWrapper({ + getWrapper({ setup: async ({ renameResource }, { space, storeOptions, clientService }) => { clientService.webdav.moveFiles.mockRejectedValueOnce(new Error()) renameResource(space, currentFolder, 'new name') await nextTick() await nextTick() - expect(storeOptions.actions.hideModal).toHaveBeenCalledTimes(0) expect(storeOptions.actions.showErrorMessage).toHaveBeenCalledTimes(1) } }) diff --git a/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsDelete.spec.ts b/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsDelete.spec.ts index 2883b0f59b6..8fe6356ff44 100644 --- a/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsDelete.spec.ts +++ b/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsDelete.spec.ts @@ -1,4 +1,5 @@ -import { useSpaceActionsDelete } from '../../../../../src' +import { useSpaceActionsDelete } from '../../../../../src/composables/actions' +import { useModals } from '../../../../../src/composables/piniaStores' import { buildSpace, SpaceResource } from '@ownclouders/web-client/src/helpers' import { createStore, @@ -15,7 +16,7 @@ import { Drive } from '@ownclouders/web-client/src/generated' describe('delete', () => { describe('isEnabled property', () => { it('should be false when no resource given', () => { - const { wrapper } = getWrapper({ + getWrapper({ setup: ({ actions }) => { expect(unref(actions)[0].isEnabled({ resources: [] })).toBe(false) } @@ -30,7 +31,7 @@ describe('delete', () => { driveType: 'project', special: null }) - const { wrapper } = getWrapper({ + getWrapper({ setup: ({ actions }) => { expect(unref(actions)[0].isEnabled({ resources: [buildSpace(spaceMock)] })).toBe(false) } @@ -46,7 +47,7 @@ describe('delete', () => { driveType: 'project', special: null }) - const { wrapper } = getWrapper({ + getWrapper({ setup: ({ actions }) => { expect(unref(actions)[0].isEnabled({ resources: [buildSpace(spaceMock)] })).toBe(true) } @@ -62,7 +63,7 @@ describe('delete', () => { driveType: 'project', special: null }) - const { wrapper } = getWrapper({ + getWrapper({ setup: ({ actions }) => { expect(unref(actions)[0].isEnabled({ resources: [buildSpace(spaceMock)] })).toBe(false) } @@ -71,54 +72,55 @@ describe('delete', () => { }) describe('handler', () => { - it('should trigger the delete modal window', async () => { - const { wrapper } = getWrapper({ + it('should trigger the delete modal window', () => { + getWrapper({ setup: async ({ actions }, { storeOptions }) => { + const { registerModal } = useModals() await unref(actions)[0].handler({ resources: [ mock({ id: '1', canBeDeleted: () => true, driveType: 'project' }) ] }) - expect(storeOptions.actions.createModal).toHaveBeenCalledTimes(1) + expect(registerModal).toHaveBeenCalledTimes(1) } }) }) - it('should not trigger the delete modal window without any resource to delete', async () => { - const { wrapper } = getWrapper({ + it('should not trigger the delete modal window without any resource to delete', () => { + getWrapper({ setup: async ({ actions }, { storeOptions }) => { + const { registerModal } = useModals() await unref(actions)[0].handler({ resources: [ mock({ id: '1', canBeDeleted: () => false, driveType: 'project' }) ] }) - expect(storeOptions.actions.createModal).toHaveBeenCalledTimes(0) + expect(registerModal).toHaveBeenCalledTimes(0) } }) }) }) describe('method "deleteSpace"', () => { - it('should hide the modal and show message on success', async () => { - const { wrapper } = getWrapper({ - setup: async ({ actions, deleteSpaces }, { storeOptions, clientService }) => { + it('should show message on success', () => { + getWrapper({ + setup: async ({ deleteSpaces }, { storeOptions, clientService }) => { clientService.graphAuthenticated.drives.deleteDrive.mockResolvedValue(mockAxiosResolve()) await deleteSpaces([ mock({ id: '1', canBeDeleted: () => true, driveType: 'project' }) ]) - expect(storeOptions.actions.hideModal).toHaveBeenCalledTimes(1) expect(storeOptions.actions.showMessage).toHaveBeenCalledTimes(1) } }) }) - it('should show message on error', async () => { + it('should show message on error', () => { jest.spyOn(console, 'error').mockImplementation(() => undefined) - const { wrapper } = getWrapper({ - setup: async ({ actions, deleteSpaces }, { clientService, storeOptions }) => { + getWrapper({ + setup: async ({ deleteSpaces }, { clientService, storeOptions }) => { clientService.graphAuthenticated.drives.deleteDrive.mockRejectedValue(new Error()) await deleteSpaces([ mock({ id: '1', canBeDeleted: () => true, driveType: 'project' }) diff --git a/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsDisable.spec.ts b/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsDisable.spec.ts index 5c9b06bbfd9..114a19845bd 100644 --- a/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsDisable.spec.ts +++ b/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsDisable.spec.ts @@ -1,4 +1,5 @@ -import { useSpaceActionsDisable } from '../../../../../src' +import { useSpaceActionsDisable } from '../../../../../src/composables/actions/spaces' +import { useModals } from '../../../../../src/composables/piniaStores' import { buildSpace, SpaceResource } from '@ownclouders/web-client/src/helpers' import { createStore, @@ -15,7 +16,7 @@ import { Drive } from '@ownclouders/web-client/src/generated' describe('disable', () => { describe('isEnabled property', () => { it('should be false when no resource given', () => { - const { wrapper } = getWrapper({ + getWrapper({ setup: ({ actions }) => { expect(unref(actions)[0].isEnabled({ resources: [] })).toBe(false) } @@ -30,7 +31,7 @@ describe('disable', () => { driveType: 'project', special: null }) - const { wrapper } = getWrapper({ + getWrapper({ setup: ({ actions }) => { expect(unref(actions)[0].isEnabled({ resources: [buildSpace(spaceMock)] })).toBe(true) } @@ -45,7 +46,7 @@ describe('disable', () => { }, special: null }) - const { wrapper } = getWrapper({ + getWrapper({ setup: ({ actions }) => { expect(unref(actions)[0].isEnabled({ resources: [buildSpace(spaceMock)] })).toBe(false) } @@ -60,7 +61,7 @@ describe('disable', () => { driveType: 'project', special: null }) - const { wrapper } = getWrapper({ + getWrapper({ setup: ({ actions }) => { expect(unref(actions)[0].isEnabled({ resources: [buildSpace(spaceMock)] })).toBe(false) } @@ -69,52 +70,54 @@ describe('disable', () => { }) describe('handler', () => { - it('should trigger the disable modal window', async () => { - const { wrapper } = getWrapper({ + it('should trigger the disable modal window', () => { + getWrapper({ setup: async ({ actions }, { storeOptions }) => { + const { registerModal } = useModals() await unref(actions)[0].handler({ resources: [ mock({ id: '1', canDisable: () => true, driveType: 'project' }) ] }) - expect(storeOptions.actions.createModal).toHaveBeenCalledTimes(1) + expect(registerModal).toHaveBeenCalledTimes(1) } }) }) - it('should not trigger the disable modal window without any resource', async () => { - const { wrapper } = getWrapper({ + it('should not trigger the disable modal window without any resource', () => { + getWrapper({ setup: async ({ actions }, { storeOptions }) => { + const { registerModal } = useModals() await unref(actions)[0].handler({ resources: [ mock({ id: '1', canDisable: () => false, driveType: 'project' }) ] }) - expect(storeOptions.actions.createModal).toHaveBeenCalledTimes(0) + expect(registerModal).toHaveBeenCalledTimes(0) } }) }) }) describe('method "disableSpace"', () => { - it('should hide the modal on success', async () => { - const { wrapper, mocks } = getWrapper({ + it('should show message on success', () => { + getWrapper({ setup: async ({ disableSpaces }, { storeOptions, clientService }) => { clientService.graphAuthenticated.drives.disableDrive.mockResolvedValue(mockAxiosResolve()) await disableSpaces([ mock({ id: '1', canDisable: () => true, driveType: 'project' }) ]) - expect(storeOptions.actions.hideModal).toHaveBeenCalledTimes(1) + expect(storeOptions.actions.showMessage).toHaveBeenCalledTimes(1) } }) }) - it('should show message on error', async () => { + it('should show message on error', () => { jest.spyOn(console, 'error').mockImplementation(() => undefined) - const { wrapper } = getWrapper({ - setup: async ({ actions, disableSpaces }, { storeOptions, clientService }) => { + getWrapper({ + setup: async ({ disableSpaces }, { storeOptions, clientService }) => { clientService.graphAuthenticated.drives.disableDrive.mockRejectedValue(new Error()) await disableSpaces([ mock({ id: '1', canDisable: () => true, driveType: 'project' }) diff --git a/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsEditDescription.spec.ts b/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsEditDescription.spec.ts index cbc05f4843a..f2b41718218 100644 --- a/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsEditDescription.spec.ts +++ b/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsEditDescription.spec.ts @@ -1,4 +1,5 @@ import { useSpaceActionsEditDescription } from '../../../../../src/composables/actions' +import { useModals } from '../../../../../src/composables/piniaStores' import { createStore, defaultComponentMocks, @@ -13,41 +14,43 @@ import { SpaceResource } from '@ownclouders/web-client/src' describe('editDescription', () => { describe('handler', () => { - it('should trigger the editDescription modal window with one resource', async () => { - const { wrapper } = getWrapper({ + it('should trigger the editDescription modal window with one resource', () => { + getWrapper({ setup: async ({ actions }, { storeOptions }) => { + const { registerModal } = useModals() await unref(actions)[0].handler({ resources: [{ id: '1' } as SpaceResource] }) - expect(storeOptions.actions.createModal).toHaveBeenCalledTimes(1) + expect(registerModal).toHaveBeenCalledTimes(1) } }) }) - it('should not trigger the editDescription modal window with no resource', async () => { - const { wrapper } = getWrapper({ + it('should not trigger the editDescription modal window with no resource', () => { + getWrapper({ setup: async ({ actions }, { storeOptions }) => { + const { registerModal } = useModals() await unref(actions)[0].handler({ resources: [] }) - expect(storeOptions.actions.createModal).toHaveBeenCalledTimes(0) + expect(registerModal).toHaveBeenCalledTimes(0) } }) }) }) describe('method "editDescriptionSpace"', () => { - it('should hide the modal on success', async () => { - const { wrapper, mocks } = getWrapper({ + it('should show message on success', () => { + getWrapper({ setup: async ({ actions, editDescriptionSpace }, { storeOptions, clientService }) => { clientService.graphAuthenticated.drives.updateDrive.mockResolvedValue(mockAxiosResolve()) await editDescriptionSpace(mock(), 'doesntmatter') - expect(storeOptions.actions.hideModal).toHaveBeenCalledTimes(1) + expect(storeOptions.actions.showMessage).toHaveBeenCalledTimes(1) } }) }) - it('should show message on error', async () => { + it('should show message on error', () => { jest.spyOn(console, 'error').mockImplementation(() => undefined) - const { wrapper, mocks } = getWrapper({ + getWrapper({ setup: async ({ actions, editDescriptionSpace }, { storeOptions, clientService }) => { clientService.graphAuthenticated.drives.updateDrive.mockRejectedValue(new Error()) await editDescriptionSpace(mock(), 'doesntmatter') diff --git a/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsEditQuota.spec.ts b/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsEditQuota.spec.ts index 29e259faef8..ff062adf1bd 100644 --- a/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsEditQuota.spec.ts +++ b/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsEditQuota.spec.ts @@ -1,4 +1,5 @@ import { useSpaceActionsEditQuota } from '../../../../../src/composables/actions' +import { useModals } from '../../../../../src/composables/piniaStores' import { buildSpace } from '@ownclouders/web-client/src/helpers' import { createStore, @@ -57,9 +58,10 @@ describe('editQuota', () => { describe('handler', () => { it('should create a modal', () => { getWrapper({ - setup: async ({ actions }, { storeOptions }) => { + setup: async ({ actions }) => { + const { registerModal } = useModals() await unref(actions)[0].handler({ resources: [] }) - expect(storeOptions.actions.createModal).toHaveBeenCalled() + expect(registerModal).toHaveBeenCalled() } }) }) @@ -91,7 +93,7 @@ function getWrapper({ return { wrapper: getComposableWrapper( () => { - const instance = useSpaceActionsEditQuota({ store }) + const instance = useSpaceActionsEditQuota() setup(instance, { storeOptions }) }, { diff --git a/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsEditReadmeContent.spec.ts b/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsEditReadmeContent.spec.ts index 70eb2b48dd8..cac9abc2a81 100644 --- a/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsEditReadmeContent.spec.ts +++ b/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsEditReadmeContent.spec.ts @@ -1,4 +1,5 @@ import { useSpaceActionsEditReadmeContent } from '../../../../../src/composables/actions' +import { useModals } from '../../../../../src/composables/piniaStores' import { SpaceResource, buildSpace } from '@ownclouders/web-client/src/helpers' import { createStore, defaultStoreMockOptions, getComposableWrapper } from 'web-test-helpers' import { unref } from 'vue' @@ -75,9 +76,10 @@ describe('editReadmeContent', () => { describe('method "handler"', () => { it('creates a modal', () => { getWrapper({ - setup: async ({ actions }, { storeOptions }) => { + setup: async ({ actions }) => { + const { registerModal } = useModals() await unref(actions)[0].handler({ resources: [mock()] }) - expect(storeOptions.actions.createModal).toHaveBeenCalled() + expect(registerModal).toHaveBeenCalled() } }) }) diff --git a/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsRename.spec.ts b/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsRename.spec.ts index 0e7c5d4af8a..04191001e24 100644 --- a/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsRename.spec.ts +++ b/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsRename.spec.ts @@ -1,4 +1,5 @@ -import { useSpaceActionsRename } from '../../../../../src' +import { useSpaceActionsRename } from '../../../../../src/composables/actions/spaces' +import { useModals } from '../../../../../src/composables/piniaStores' import { mock } from 'jest-mock-extended' import { createStore, @@ -13,46 +14,47 @@ import { SpaceResource } from '@ownclouders/web-client/src' describe('rename', () => { describe('handler', () => { - it('should trigger the rename modal window', async () => { - const { wrapper } = getWrapper({ + it('should trigger the rename modal window', () => { + getWrapper({ setup: async ({ actions }, { storeOptions }) => { + const { registerModal } = useModals() await unref(actions)[0].handler({ resources: [{ id: '1', name: 'renamed space' } as SpaceResource] }) - expect(storeOptions.actions.createModal).toHaveBeenCalledTimes(1) + expect(registerModal).toHaveBeenCalledTimes(1) } }) }) - it('should not trigger the rename modal window without any resource', async () => { - const { wrapper } = getWrapper({ + it('should not trigger the rename modal window without any resource', () => { + getWrapper({ setup: async ({ actions }, { storeOptions }) => { + const { registerModal } = useModals() await unref(actions)[0].handler({ resources: [] }) - expect(storeOptions.actions.createModal).toHaveBeenCalledTimes(0) + expect(registerModal).toHaveBeenCalledTimes(0) } }) }) }) describe('method "renameSpace"', () => { - it('should hide the modal and show message on success', async () => { - const { wrapper, mocks } = getWrapper({ + it('should show message on success', () => { + getWrapper({ setup: async ({ renameSpace }, { storeOptions, clientService }) => { clientService.graphAuthenticated.drives.updateDrive.mockResolvedValue(mockAxiosResolve()) - await renameSpace(1, 'renamed space') + await renameSpace(mock({ id: '1' }), 'renamed space') - expect(storeOptions.actions.hideModal).toHaveBeenCalledTimes(1) expect(storeOptions.actions.showMessage).toHaveBeenCalledTimes(1) } }) }) - it('should show message on error', async () => { + it('should show message on error', () => { jest.spyOn(console, 'error').mockImplementation(() => undefined) - const { wrapper } = getWrapper({ + getWrapper({ setup: async ({ renameSpace }, { storeOptions, clientService }) => { clientService.graphAuthenticated.drives.updateDrive.mockRejectedValue(new Error()) - await renameSpace(1, 'renamed space') + await renameSpace(mock({ id: '1' }), 'renamed space') expect(storeOptions.actions.showErrorMessage).toHaveBeenCalledTimes(1) } diff --git a/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsRestore.spec.ts b/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsRestore.spec.ts index 22505646135..28d0ab96fa1 100644 --- a/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsRestore.spec.ts +++ b/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsRestore.spec.ts @@ -1,4 +1,4 @@ -import { useSpaceActionsRestore } from '../../../../../src' +import { useSpaceActionsRestore } from '../../../../../src/composables/actions/spaces' import { buildSpace, SpaceResource } from '@ownclouders/web-client/src/helpers' import { mock } from 'jest-mock-extended' import { @@ -11,11 +11,12 @@ import { } from 'web-test-helpers' import { unref } from 'vue' import { Drive } from '@ownclouders/web-client/src/generated' +import { useModals } from '../../../../../src/composables/piniaStores' describe('restore', () => { describe('isEnabled property', () => { it('should be false when no resource given', () => { - const { wrapper } = getWrapper({ + getWrapper({ setup: ({ actions }, { storeOptions }) => { expect(unref(actions)[0].isEnabled({ resources: [] })).toBe(false) } @@ -30,7 +31,7 @@ describe('restore', () => { driveType: 'project', special: null }) - const { wrapper } = getWrapper({ + getWrapper({ setup: ({ actions }, { storeOptions }) => { expect(unref(actions)[0].isEnabled({ resources: [buildSpace(spaceMock)] })).toBe(false) } @@ -46,7 +47,7 @@ describe('restore', () => { driveType: 'project', special: null }) - const { wrapper } = getWrapper({ + getWrapper({ setup: ({ actions }, { storeOptions }) => { expect(unref(actions)[0].isEnabled({ resources: [buildSpace(spaceMock)] })).toBe(true) } @@ -62,7 +63,7 @@ describe('restore', () => { driveType: 'project', special: null }) - const { wrapper } = getWrapper({ + getWrapper({ setup: ({ actions }, { storeOptions }) => { expect(unref(actions)[0].isEnabled({ resources: [buildSpace(spaceMock)] })).toBe(false) } @@ -71,47 +72,49 @@ describe('restore', () => { }) describe('handler', () => { - it('should trigger the restore modal window', async () => { - const { wrapper } = getWrapper({ + it('should trigger the restore modal window', () => { + getWrapper({ setup: async ({ actions }, { storeOptions }) => { + const { registerModal } = useModals() await unref(actions)[0].handler({ resources: [ mock({ id: '1', canRestore: () => true, driveType: 'project' }) ] }) - expect(storeOptions.actions.createModal).toHaveBeenCalledTimes(1) + expect(registerModal).toHaveBeenCalledTimes(1) } }) }) - it('should not trigger the restore modal window without any resource', async () => { - const { wrapper } = getWrapper({ + it('should not trigger the restore modal window without any resource', () => { + getWrapper({ setup: async ({ actions }, { storeOptions }) => { + const { registerModal } = useModals() await unref(actions)[0].handler({ resources: [mock({ id: '1', canRestore: () => false })] }) - expect(storeOptions.actions.createModal).toHaveBeenCalledTimes(0) + expect(registerModal).toHaveBeenCalledTimes(0) } }) }) }) describe('method "restoreSpace"', () => { - it('should hide the modal on success', async () => { - const { wrapper } = getWrapper({ + it('should show message on success', () => { + getWrapper({ setup: async ({ restoreSpaces }, { storeOptions, clientService }) => { clientService.graphAuthenticated.drives.updateDrive.mockResolvedValue(mockAxiosResolve()) await restoreSpaces([mock({ id: '1', canRestore: () => true })]) - expect(storeOptions.actions.hideModal).toHaveBeenCalledTimes(1) + expect(storeOptions.actions.showMessage).toHaveBeenCalledTimes(1) } }) }) - it('should show message on error', async () => { + it('should show message on error', () => { jest.spyOn(console, 'error').mockImplementation(() => undefined) - const { wrapper } = getWrapper({ + getWrapper({ setup: async ({ restoreSpaces }, { storeOptions, clientService }) => { clientService.graphAuthenticated.drives.updateDrive.mockRejectedValue(new Error()) await restoreSpaces([mock({ id: '1', canRestore: () => true })]) diff --git a/packages/web-pkg/tests/unit/composables/piniaStores/useModals.spec.ts b/packages/web-pkg/tests/unit/composables/piniaStores/useModals.spec.ts new file mode 100644 index 00000000000..902c874e3d9 --- /dev/null +++ b/packages/web-pkg/tests/unit/composables/piniaStores/useModals.spec.ts @@ -0,0 +1,108 @@ +import { getComposableWrapper } from 'web-test-helpers' +import { useModals } from '../../../../src/composables/piniaStores' +import { createPinia, setActivePinia } from 'pinia' + +describe('useModals', () => { + beforeEach(() => { + setActivePinia(createPinia()) + }) + + describe('method "registerModal"', () => { + it('registers a modal and adds id and active state', () => { + getWrapper({ + setup: (instance) => { + const data = { title: 'test' } + const modal = instance.registerModal(data) + + expect(modal.id).toBeDefined() + expect(modal.active).toBeTruthy() + expect(modal.title).toEqual(data.title) + + expect(instance.modals[0]).toEqual(modal) + } + }) + }) + it('deactivates existing modal if a new active modal is being registered', () => { + getWrapper({ + setup: (instance) => { + instance.registerModal({ title: 'test' }) + expect(instance.modals[0].active).toBeTruthy() + + instance.registerModal({ title: 'test2' }) + expect(instance.modals[0].active).toBeFalsy() + } + }) + }) + it('can register a modal in an inactive state', () => { + getWrapper({ + setup: (instance) => { + instance.registerModal({ title: 'test' }, { isActive: false }) + expect(instance.modals[0].active).toBeFalsy() + } + }) + }) + }) + describe('method "updateModal"', () => { + it('updates a modal with new data', () => { + getWrapper({ + setup: (instance) => { + const modal = instance.registerModal({ title: 'test' }) + const newTitle = 'new title' + instance.updateModal(modal.id, 'title', newTitle) + expect(instance.modals[0].title).toEqual(newTitle) + } + }) + }) + }) + describe('method "removeModal"', () => { + it('removes an existing modal', () => { + getWrapper({ + setup: (instance) => { + const modal = instance.registerModal({ title: 'test' }) + instance.removeModal(modal.id) + expect(instance.modals.length).toBe(0) + } + }) + }) + }) + it('activates another inactive modal after removing the current active one', () => { + getWrapper({ + setup: (instance) => { + const modal = instance.registerModal({ title: 'test' }) + instance.registerModal({ title: 'test2' }) + expect(instance.modals[0].active).toBeFalsy() + instance.removeModal(modal.id) + expect(instance.modals[0].active).toBeTruthy() + } + }) + }) + describe('method "setModalActive"', () => { + it('activates a modal and deactivates another active modal if present', () => { + getWrapper({ + setup: (instance) => { + const modal = instance.registerModal({ title: 'test' }) + instance.registerModal({ title: 'test2' }) + expect(instance.modals[0].active).toBeFalsy() + expect(instance.modals[1].active).toBeTruthy() + + instance.setModalActive(modal.id) + + expect(instance.modals[0].active).toBeTruthy() + expect(instance.modals[1].active).toBeFalsy() + } + }) + }) + }) +}) + +function getWrapper({ setup }: { setup: (instance: ReturnType) => void }) { + return { + wrapper: getComposableWrapper( + () => { + const instance = useModals() + setup(instance) + }, + { pluginOptions: { pinia: false } } + ) + } +} diff --git a/packages/web-pkg/tests/unit/composables/spaces/useSpaceHelpers.spec.ts b/packages/web-pkg/tests/unit/composables/spaces/useSpaceHelpers.spec.ts index 1c37c817da5..b5d7a7cd090 100644 --- a/packages/web-pkg/tests/unit/composables/spaces/useSpaceHelpers.spec.ts +++ b/packages/web-pkg/tests/unit/composables/spaces/useSpaceHelpers.spec.ts @@ -1,10 +1,5 @@ import { useSpaceHelpers } from '../../../../src/composables/spaces' -import { - createStore, - defaultComponentMocks, - defaultStoreMockOptions, - getComposableWrapper -} from 'web-test-helpers' +import { defaultComponentMocks, getComposableWrapper } from 'web-test-helpers' describe('useSpaceHelpers', () => { it('should be valid', () => { @@ -13,28 +8,28 @@ describe('useSpaceHelpers', () => { describe('method "checkSpaceNameModalInput"', () => { it('should not show an error message with a valid space name', () => { getWrapper({ - setup: ({ checkSpaceNameModalInput }, { storeOptions }) => { - checkSpaceNameModalInput('Space') - const { setModalInputErrorMessage } = storeOptions.actions - expect(setModalInputErrorMessage).toHaveBeenCalledWith(expect.anything(), null) + setup: ({ checkSpaceNameModalInput }) => { + checkSpaceNameModalInput('Space', (value) => { + expect(value).toEqual(null) + }) } }) }) it('should show an error message with an empty name', () => { getWrapper({ - setup: ({ checkSpaceNameModalInput }, { storeOptions }) => { - checkSpaceNameModalInput('') - const { setModalInputErrorMessage } = storeOptions.actions - expect(setModalInputErrorMessage).not.toHaveBeenCalledWith(expect.anything(), null) + setup: ({ checkSpaceNameModalInput }) => { + checkSpaceNameModalInput('', (value) => { + expect(value).not.toEqual(null) + }) } }) }) it('should show an error with an name longer than 255 characters', () => { getWrapper({ - setup: ({ checkSpaceNameModalInput }, { storeOptions }) => { - checkSpaceNameModalInput('n'.repeat(256)) - const { setModalInputErrorMessage } = storeOptions.actions - expect(setModalInputErrorMessage).not.toHaveBeenCalledWith(expect.anything(), null) + setup: ({ checkSpaceNameModalInput }) => { + checkSpaceNameModalInput('n'.repeat(256), (value) => { + expect(value).not.toEqual(null) + }) } }) }) @@ -42,10 +37,10 @@ describe('useSpaceHelpers', () => { 'should show an error message with a name including a special character', (specialChar) => { getWrapper({ - setup: ({ checkSpaceNameModalInput }, { storeOptions }) => { - checkSpaceNameModalInput(specialChar) - const { setModalInputErrorMessage } = storeOptions.actions - expect(setModalInputErrorMessage).not.toHaveBeenCalledWith(expect.anything(), null) + setup: ({ checkSpaceNameModalInput }) => { + checkSpaceNameModalInput(specialChar, (value) => { + expect(value).not.toEqual(null) + }) } }) } @@ -53,25 +48,16 @@ describe('useSpaceHelpers', () => { }) }) -function getWrapper({ - setup -}: { - setup: ( - instance: ReturnType, - options: { storeOptions: typeof defaultStoreMockOptions } - ) => void -}) { +function getWrapper({ setup }: { setup: (instance: ReturnType) => void }) { const mocks = defaultComponentMocks() - const storeOptions = defaultStoreMockOptions - const store = createStore(storeOptions) + return { wrapper: getComposableWrapper( () => { const instance = useSpaceHelpers() - setup(instance, { storeOptions }) + setup(instance) }, { - store, mocks } ) diff --git a/packages/web-pkg/tests/unit/helpers/resource/conflictHandling/conflictDialog.spec.ts b/packages/web-pkg/tests/unit/helpers/resource/conflictHandling/conflictDialog.spec.ts index 0804910e13f..44b7f296406 100644 --- a/packages/web-pkg/tests/unit/helpers/resource/conflictHandling/conflictDialog.spec.ts +++ b/packages/web-pkg/tests/unit/helpers/resource/conflictHandling/conflictDialog.spec.ts @@ -1,9 +1,12 @@ import { mockDeep } from 'jest-mock-extended' import { Resource } from '@ownclouders/web-client' import { ConflictDialog, ResolveConflict } from '../../../../../src/helpers/resource' +import { useModals } from '../../../../../src/composables/piniaStores' +import { setActivePinia } from 'pinia' +import { createMockStore } from 'web-test-helpers' -const getConflictDialogInstance = ({ createModal = jest.fn() } = {}) => { - return new ConflictDialog(createModal, jest.fn(), jest.fn(), jest.fn(), jest.fn(), jest.fn()) +const getConflictDialogInstance = () => { + return new ConflictDialog(jest.fn(), jest.fn(), jest.fn(), jest.fn()) } describe('conflict dialog', () => { @@ -31,10 +34,11 @@ describe('conflict dialog', () => { }) describe('method "resolveFileExists"', () => { it('should create the modal in the end', () => { - const createModal = jest.fn() - const conflictDialog = getConflictDialogInstance({ createModal }) + setActivePinia(createMockStore()) + const { registerModal } = useModals() + const conflictDialog = getConflictDialogInstance() conflictDialog.resolveFileExists(mockDeep(), 2, true) - expect(createModal).toHaveBeenCalledTimes(1) + expect(registerModal).toHaveBeenCalledTimes(1) }) }) }) diff --git a/packages/web-runtime/src/App.vue b/packages/web-runtime/src/App.vue index a860977de2e..05b491801d4 100644 --- a/packages/web-runtime/src/App.vue +++ b/packages/web-runtime/src/App.vue @@ -6,53 +6,15 @@ - - - - + diff --git a/packages/web-runtime/src/container/bootstrap.ts b/packages/web-runtime/src/container/bootstrap.ts index 2d87fd13b3c..477c4644c0f 100644 --- a/packages/web-runtime/src/container/bootstrap.ts +++ b/packages/web-runtime/src/container/bootstrap.ts @@ -8,7 +8,7 @@ import { loadTheme } from '../helpers/theme' import OwnCloud from 'owncloud-sdk' import { createGettext, GetTextOptions, Language } from 'vue3-gettext' import { getBackendVersion, getWebVersion } from './versions' -import { useThemeStore } from '@ownclouders/web-pkg' +import { useModals, useThemeStore } from '@ownclouders/web-pkg' import { authService } from '../services/auth' import { ClientService, @@ -330,6 +330,10 @@ export const announceTheme = async ({ }) } +export const announcePiniaStores = () => { + useModals() +} + /** * announce runtime translations by injecting them into the getTextPlugin * diff --git a/packages/web-runtime/src/index.ts b/packages/web-runtime/src/index.ts index 7bfa4099683..1ca5687a9d1 100644 --- a/packages/web-runtime/src/index.ts +++ b/packages/web-runtime/src/index.ts @@ -15,6 +15,7 @@ import { announceClientService, announceStore, announceTheme, + announcePiniaStores, announceCustomStyles, announceTranslations, announceVersions, @@ -92,6 +93,7 @@ export const bootstrapApp = async (configurationPath: string): Promise => announcePreviewService({ app, store, configurationManager }) announcePasswordPolicyService({ app }) await announceClient(runtimeConfiguration) + announcePiniaStores() app.config.globalProperties.$wormhole = createWormhole() app.use(PortalVue, { diff --git a/packages/web-runtime/src/pages/account.vue b/packages/web-runtime/src/pages/account.vue index fa78f2c700d..09e083c85a2 100644 --- a/packages/web-runtime/src/pages/account.vue +++ b/packages/web-runtime/src/pages/account.vue @@ -4,11 +4,6 @@

{{ pageTitle }}

- diff --git a/packages/web-runtime/src/services/auth/authService.ts b/packages/web-runtime/src/services/auth/authService.ts index 5b9bc458959..0d588838d46 100644 --- a/packages/web-runtime/src/services/auth/authService.ts +++ b/packages/web-runtime/src/services/auth/authService.ts @@ -298,10 +298,7 @@ export class AuthService { // TODO: create UserUnloadTask interface and allow registering unload-tasks in the authService await this.store.dispatch('runtime/auth/clearUserContext') await this.store.dispatch('resetUserState') - await Promise.all([ - this.store.dispatch('clearDynamicNavItems'), - this.store.dispatch('hideModal') - ]) + await this.store.dispatch('clearDynamicNavItems') } private handleDelegatedTokenUpdate(event: MessageEvent): void { diff --git a/packages/web-runtime/src/store/index.ts b/packages/web-runtime/src/store/index.ts index 6b3d651cf93..2c63a6f851d 100644 --- a/packages/web-runtime/src/store/index.ts +++ b/packages/web-runtime/src/store/index.ts @@ -4,7 +4,6 @@ import apps from './apps' import auth from './auth' import config from './config' import user from './user' -import modal from './modal' import navigation from './navigation' import spaces from './spaces' @@ -25,7 +24,6 @@ export default { apps, user, config, - modal, navigation, runtime }, diff --git a/packages/web-runtime/src/store/modal.ts b/packages/web-runtime/src/store/modal.ts deleted file mode 100644 index 09d4b9465b3..00000000000 --- a/packages/web-runtime/src/store/modal.ts +++ /dev/null @@ -1,106 +0,0 @@ -const emptyReturn = () => undefined - -const state = { - displayed: false, - variation: 'passive', - icon: '', - title: '', - message: '', - cancelText: '', - confirmText: '', - withoutButtonConfirm: false, - // Input values - confirmDisabled: false, - hasInput: false, - inputDisabled: false, - inputValue: '', - inputSelectionRange: null, - inputPlaceholder: '', - inputLabel: '', - inputError: '', - inputType: 'text', - // Events - onCancel: emptyReturn, - onConfirm: emptyReturn, - onInput: emptyReturn, - contextualHelperLabel: '', - contextualHelperData: {}, - customContent: '', - hideActions: false -} - -const actions = { - createModal({ commit }, modal) { - commit('CREATE_MODAL', modal) - }, - - hideModal({ commit }) { - commit('HIDE_MODAL') - }, - - setModalInputErrorMessage({ commit }, error) { - commit('SET_INPUT_ERROR_MESSAGE', error) - }, - - toggleModalConfirmButton({ commit }) { - commit('TOGGLE_MODAL_CONFIRM_BUTTON') - }, - - setModalConfirmButtonDisabled({ commit }, status) { - commit('SET_MODAL_CONFIRM_BUTTON_DISABLED', status) - } -} - -const mutations = { - CREATE_MODAL(state, modal) { - state.displayed = true - state.variation = modal.variation || 'passive' - state.icon = modal.icon - state.title = modal.title - state.message = modal.message - state.withoutButtonConfirm = modal.withoutButtonConfirm || false - state.cancelText = modal.cancelText || 'Cancel' - state.confirmText = modal.confirmText || 'Confirm' - state.confirmDisabled = modal.confirmDisabled || false - state.onCancel = modal.onCancel - state.onConfirm = modal.onConfirm - state.hasInput = modal.hasInput || false - state.inputValue = modal.inputValue || null - state.inputSelectionRange = modal.inputSelectionRange - state.inputDescription = modal.inputDescription || null - state.inputPlaceholder = modal.inputPlaceholder || null - state.inputLabel = modal.inputLabel || null - state.inputError = modal.inputError || null - state.inputDisabled = modal.inputDisabled || false - state.inputType = modal.inputType || 'text' - state.onInput = modal.onInput || emptyReturn - state.contextualHelperLabel = modal.contextualHelperLabel - state.contextualHelperData = modal.contextualHelperData - state.customComponent = modal.customComponent - state.customComponentAttrs = modal.customComponentAttrs - state.customContent = modal.customContent || '' - state.hideActions = modal.hideActions || false - }, - - HIDE_MODAL(state) { - state.displayed = false - }, - - SET_INPUT_ERROR_MESSAGE(state, error) { - state.inputError = error - }, - - TOGGLE_MODAL_CONFIRM_BUTTON(state) { - state.confirmDisabled = !state.confirmDisabled - }, - - SET_MODAL_CONFIRM_BUTTON_DISABLED(state, status) { - state.confirmDisabled = status - } -} - -export default { - state, - actions, - mutations -} diff --git a/packages/web-runtime/tests/unit/components/EditPasswordModal.spec.ts b/packages/web-runtime/tests/unit/components/EditPasswordModal.spec.ts index 2d8b5c6b457..5adbe578cac 100644 --- a/packages/web-runtime/tests/unit/components/EditPasswordModal.spec.ts +++ b/packages/web-runtime/tests/unit/components/EditPasswordModal.spec.ts @@ -1,5 +1,7 @@ +import { Modal } from '@ownclouders/web-pkg' import EditPasswordModal from '../../../src/components/EditPasswordModal.vue' import { defaultPlugins, shallowMount } from 'web-test-helpers' +import { mock } from 'jest-mock-extended' afterEach(() => jest.clearAllMocks()) @@ -39,13 +41,7 @@ function getWrapper() { return { wrapper: shallowMount(EditPasswordModal, { props: { - cancel: jest.fn(), - confirm: jest.fn(), - existingGroups: [ - { - displayName: 'admins' - } - ] + modal: mock() }, global: { plugins: [...defaultPlugins()] diff --git a/packages/web-runtime/tests/unit/components/ModalWrapper.spec.ts b/packages/web-runtime/tests/unit/components/ModalWrapper.spec.ts new file mode 100644 index 00000000000..50c42e46628 --- /dev/null +++ b/packages/web-runtime/tests/unit/components/ModalWrapper.spec.ts @@ -0,0 +1,153 @@ +import { Modal, useModals } from '@ownclouders/web-pkg' +import { mock } from 'jest-mock-extended' +import { PropType, defineComponent } from 'vue' +import ModalWrapper from 'web-runtime/src/components/ModalWrapper.vue' +import { + createStore, + defaultPlugins, + shallowMount, + defaultStoreMockOptions, + defaultComponentMocks +} from 'web-test-helpers' + +const CustomModalComponent = defineComponent({ + name: 'CustomModalComponent', + props: { + modal: { type: Object as PropType, required: true } + }, + setup() { + return { onConfirm: jest.fn() } + }, + template: '
' +}) + +describe('ModalWrapper', () => { + it('renders OcModal when a modal is active', async () => { + const modal = mock() + const { wrapper } = getShallowWrapper({ modals: [modal] }) + const modalStore = useModals() + ;(modalStore.activeModal as any) = modal + await wrapper.vm.$nextTick() + + expect(wrapper.find('.oc-modal').exists()).toBeTruthy() + }) + it('renders a custom component if given', async () => { + const modal = mock({ + customComponent: CustomModalComponent, + customComponentAttrs: jest.fn() + }) + const { wrapper } = getShallowWrapper({ modals: [modal] }) + const modalStore = useModals() + ;(modalStore.activeModal as any) = modal + await wrapper.vm.$nextTick() + + expect(wrapper.find('custom-modal-component-stub').exists()).toBeTruthy() + }) + describe('method "onModalConfirm"', () => { + it('calls the modal "onConfirm" if given, disables the confirm button and removes the modal', async () => { + const modal = mock({ onConfirm: jest.fn().mockResolvedValue(undefined) }) + const { wrapper } = getShallowWrapper({ modals: [modal] }) + const modalStore = useModals() + ;(modalStore.activeModal as any) = modal + + const value = 'value' + await wrapper.vm.onModalConfirm(value) + + expect(modal.onConfirm).toHaveBeenCalledWith(value) + expect(modalStore.updateModal).toHaveBeenCalled() + expect(modalStore.removeModal).toHaveBeenCalled() + }) + it('does not remove the modal if the promise has not been resolved', async () => { + const modal = mock({ onConfirm: jest.fn().mockRejectedValue(new Error('')) }) + const { wrapper } = getShallowWrapper({ modals: [modal] }) + const modalStore = useModals() + ;(modalStore.activeModal as any) = modal + + await wrapper.vm.onModalConfirm() + + expect(modalStore.removeModal).not.toHaveBeenCalled() + }) + it('calls the custom component "onConfirm" if given', async () => { + const modal = mock({ onConfirm: null }) + const { wrapper } = getShallowWrapper({ modals: [modal] }) + const modalStore = useModals() + ;(modalStore.activeModal as any) = modal + await wrapper.vm.$nextTick() + wrapper.vm.customComponentRef = { onConfirm: jest.fn() } + + await wrapper.vm.onModalConfirm() + + expect(wrapper.vm.customComponentRef.onConfirm).toHaveBeenCalled() + }) + }) + describe('method "onModalCancel"', () => { + it('calls the modal "onCancel" if given and removes the modal', async () => { + const modal = mock({ onCancel: jest.fn() }) + const { wrapper } = getShallowWrapper({ modals: [modal] }) + const modalStore = useModals() + ;(modalStore.activeModal as any) = modal + + await wrapper.vm.onModalCancel() + + expect(modal.onCancel).toHaveBeenCalled() + expect(modalStore.removeModal).toHaveBeenCalled() + }) + it('calls the custom component "onCancel" if given', async () => { + const modal = mock({ onCancel: null }) + const { wrapper } = getShallowWrapper({ modals: [modal] }) + const modalStore = useModals() + ;(modalStore.activeModal as any) = modal + await wrapper.vm.$nextTick() + wrapper.vm.customComponentRef = { onCancel: jest.fn() } + + await wrapper.vm.onModalCancel() + + expect(wrapper.vm.customComponentRef.onCancel).toHaveBeenCalled() + }) + }) + describe('method "onModalInput"', () => { + it('calls the modal "onInput" if given', async () => { + const modal = mock({ onInput: jest.fn() }) + const { wrapper } = getShallowWrapper({ modals: [modal] }) + const modalStore = useModals() + ;(modalStore.activeModal as any) = modal + + const value = 'value' + await wrapper.vm.onModalInput(value) + + expect(modal.onInput).toHaveBeenCalledWith(value, expect.anything()) + }) + }) + describe('method "onModalConfirmDisabled"', () => { + it('updates the modal confirm button state', async () => { + const modal = mock() + const { wrapper } = getShallowWrapper({ modals: [modal] }) + const modalStore = useModals() + ;(modalStore.activeModal as any) = modal + + const value = true + await wrapper.vm.onModalConfirmDisabled(value) + + expect(modalStore.updateModal).toHaveBeenCalled() + }) + }) +}) + +function getShallowWrapper({ modals = [] } = {}) { + const storeOptions = defaultStoreMockOptions + const store = createStore(storeOptions) + + const mocks = defaultComponentMocks() + + return { + wrapper: shallowMount(ModalWrapper, { + global: { + plugins: [...defaultPlugins({ piniaOptions: { modalsState: { modals } } }), store], + renderStubDefaultSlot: true, + mocks, + provide: mocks, + stubs: { OcModal: false } + } + }) + } +} diff --git a/packages/web-runtime/tests/unit/components/Topbar/ThemeSwitcher.spec.ts b/packages/web-runtime/tests/unit/components/Topbar/ThemeSwitcher.spec.ts index 0d33c988286..b17ca0b92f1 100644 --- a/packages/web-runtime/tests/unit/components/Topbar/ThemeSwitcher.spec.ts +++ b/packages/web-runtime/tests/unit/components/Topbar/ThemeSwitcher.spec.ts @@ -1,8 +1,8 @@ -import { useThemeStore } from '@ownclouders/web-pkg' +import { WebThemeType, useThemeStore } from '@ownclouders/web-pkg' +import { mock } from 'jest-mock-extended' import ThemeSwitcher from 'web-runtime/src/components/Topbar/ThemeSwitcher.vue' import defaultTheme from 'web-runtime/themes/owncloud/theme.json' import { defaultPlugins, defaultStubs, mount } from 'web-test-helpers' -import { createMockThemeStore } from 'web-test-helpers/src/mocks/pinia' const defaultOwnCloudTheme = { defaults: { @@ -95,13 +95,25 @@ function mockDarkModePreferred(enabled = false) { } function getWrapper({ hasOnlyOneTheme = false } = {}) { + const availableThemes = hasOnlyOneTheme + ? [defaultTheme.clients.web.themes[0]] + : defaultTheme.clients.web.themes + return { wrapper: mount(ThemeSwitcher, { global: { plugins: [ - ...defaultPlugins(), - createMockThemeStore({ - hasOnlyOneTheme + ...defaultPlugins({ + piniaOptions: { + stubActions: false, + themeState: { + availableThemes, + currentTheme: mock({ + ...defaultOwnCloudTheme.defaults, + ...defaultOwnCloudTheme.themes[0] + }) + } + } }) ], stubs: { ...defaultStubs, 'oc-icon': true } diff --git a/packages/web-runtime/tests/unit/components/Topbar/TopBar.spec.ts b/packages/web-runtime/tests/unit/components/Topbar/TopBar.spec.ts index 109c8b6b3df..ef463788c3a 100644 --- a/packages/web-runtime/tests/unit/components/Topbar/TopBar.spec.ts +++ b/packages/web-runtime/tests/unit/components/Topbar/TopBar.spec.ts @@ -7,7 +7,6 @@ import { shallowMount, defaultStoreMockOptions } from 'web-test-helpers' -import { createMockThemeStore } from 'web-test-helpers/src/mocks/pinia' const mockUseEmbedMode = jest.fn().mockReturnValue({ isEnabled: computed(() => false) }) @@ -101,7 +100,7 @@ const getWrapper = ({ capabilities = {}, isUserContextReady = true } = {}) => { applicationsList: ['testApp'] }, global: { - plugins: [...defaultPlugins(), store, createMockThemeStore()], + plugins: [...defaultPlugins(), store], stubs: { 'router-link': true, 'portal-target': true, notifications: true }, mocks, provide: mocks diff --git a/packages/web-runtime/tests/unit/components/Topbar/UserMenu.spec.ts b/packages/web-runtime/tests/unit/components/Topbar/UserMenu.spec.ts index c791fded6c9..046be5febdc 100644 --- a/packages/web-runtime/tests/unit/components/Topbar/UserMenu.spec.ts +++ b/packages/web-runtime/tests/unit/components/Topbar/UserMenu.spec.ts @@ -9,7 +9,6 @@ import { RouteLocation } from 'web-test-helpers' import { mock } from 'jest-mock-extended' -import { createCustomThemeStore } from 'web-test-helpers/src/mocks/pinia' const totalQuota = 1000 const basicQuota = 300 @@ -175,11 +174,9 @@ const getMountedWrapper = (quota, userEmail, noUser = false, areThemeUrlsSet = f provide: mocks, renderStubDefaultSlot: true, plugins: [ - ...defaultPlugins(), - store, - createCustomThemeStore({ - initialState: { - theme: { + ...defaultPlugins({ + piniaOptions: { + themeState: { currentTheme: { common: { urls: { @@ -190,7 +187,8 @@ const getMountedWrapper = (quota, userEmail, noUser = false, areThemeUrlsSet = f } } } - }) + }), + store ], stubs: { ...defaultStubs, diff --git a/packages/web-runtime/tests/unit/pages/accessDenied.spec.ts b/packages/web-runtime/tests/unit/pages/accessDenied.spec.ts index 0d23cf7143e..5508e507a11 100644 --- a/packages/web-runtime/tests/unit/pages/accessDenied.spec.ts +++ b/packages/web-runtime/tests/unit/pages/accessDenied.spec.ts @@ -13,7 +13,6 @@ const selectors = { } import { ConfigurationManager, useConfigurationManager } from '@ownclouders/web-pkg' -import { createMockThemeStore } from 'web-test-helpers/src/mocks/pinia' jest.mock('@ownclouders/web-pkg', () => ({ ...jest.requireActual('@ownclouders/web-pkg'), @@ -61,7 +60,7 @@ function getWrapper({ loginUrl = '' } = {}) { mocks, wrapper: mount(accessDenied, { global: { - plugins: [...defaultPlugins(), store, createMockThemeStore()], + plugins: [...defaultPlugins(), store], mocks, provide: mocks } diff --git a/packages/web-runtime/tests/unit/pages/account.spec.ts b/packages/web-runtime/tests/unit/pages/account.spec.ts index f967bb66f09..28dbc256b69 100644 --- a/packages/web-runtime/tests/unit/pages/account.spec.ts +++ b/packages/web-runtime/tests/unit/pages/account.spec.ts @@ -199,37 +199,6 @@ describe('account page', () => { }) }) - describe('method "editPassword"', () => { - it('should show message on success', async () => { - const { wrapper, mocks } = getWrapper() - - await wrapper.vm.loadAccountBundleTask.last - await wrapper.vm.loadValuesListTask.last - await wrapper.vm.loadGraphUserTask.last - - mocks.$clientService.graphAuthenticated.users.changeOwnPassword.mockResolvedValue( - mockAxiosResolve() - ) - const showMessageStub = jest.spyOn(wrapper.vm, 'showMessage') - await wrapper.vm.editPassword('password', 'newPassword') - expect(showMessageStub).toHaveBeenCalled() - }) - - it('should show message on error', async () => { - jest.spyOn(console, 'error').mockImplementation(() => undefined) - const { wrapper, mocks } = getWrapper() - - await wrapper.vm.loadAccountBundleTask.last - await wrapper.vm.loadValuesListTask.last - await wrapper.vm.loadGraphUserTask.last - - mocks.$clientService.graphAuthenticated.users.changeOwnPassword.mockRejectedValue(new Error()) - const showErrorMessageStub = jest.spyOn(wrapper.vm, 'showErrorMessage') - await wrapper.vm.editPassword('password', 'newPassword') - expect(showErrorMessageStub).toHaveBeenCalled() - }) - }) - describe('Logout from all devices link', () => { it('should render the logout from active devices if logoutUrl is provided', async () => { const { wrapper } = getWrapper() diff --git a/packages/web-runtime/tests/unit/pages/oidcCallback.spec.ts b/packages/web-runtime/tests/unit/pages/oidcCallback.spec.ts index 579d152a253..ef363c689e5 100644 --- a/packages/web-runtime/tests/unit/pages/oidcCallback.spec.ts +++ b/packages/web-runtime/tests/unit/pages/oidcCallback.spec.ts @@ -10,7 +10,6 @@ import oidcCallback from '../../../src/pages/oidcCallback.vue' import { authService } from 'web-runtime/src/services/auth' import { mock } from 'jest-mock-extended' import { computed } from 'vue' -import { createMockThemeStore } from 'web-test-helpers/src/mocks/pinia' const mockUseEmbedMode = jest.fn() @@ -143,7 +142,7 @@ function getWrapper() { storeOptions, wrapper: shallowMount(oidcCallback, { global: { - plugins: [...defaultPlugins(), store, createMockThemeStore()], + plugins: [...defaultPlugins(), store], mocks, provide: {} } diff --git a/packages/web-runtime/tests/unit/pages/resolvePublicLink.spec.ts b/packages/web-runtime/tests/unit/pages/resolvePublicLink.spec.ts index aab3372ce27..57f1801d176 100644 --- a/packages/web-runtime/tests/unit/pages/resolvePublicLink.spec.ts +++ b/packages/web-runtime/tests/unit/pages/resolvePublicLink.spec.ts @@ -10,7 +10,6 @@ import { mockDeep } from 'jest-mock-extended' import { ClientService } from '@ownclouders/web-pkg' import { Resource } from '@ownclouders/web-client' import { authService } from 'web-runtime/src/services/auth' -import { createMockThemeStore } from 'web-test-helpers/src/mocks/pinia' jest.mock('web-runtime/src/services/auth') const selectors = { @@ -87,7 +86,7 @@ function getWrapper({ passwordRequired = false } = {}) { return { wrapper: shallowMount(ResolvePublicLink, { global: { - plugins: [...defaultPlugins(), store, createMockThemeStore()], + plugins: [...defaultPlugins(), store], mocks, provide: mocks } diff --git a/packages/web-test-helpers/src/defaultPlugins.ts b/packages/web-test-helpers/src/defaultPlugins.ts index 08a5b13c82d..08cd0d19562 100644 --- a/packages/web-test-helpers/src/defaultPlugins.ts +++ b/packages/web-test-helpers/src/defaultPlugins.ts @@ -4,20 +4,22 @@ import { h } from 'vue' import { abilitiesPlugin } from '@casl/vue' import { createMongoAbility } from '@casl/ability' import { AbilityRule } from '../../web-client/src/helpers/resource/types' -import { createPinia } from 'pinia' +import { PiniaMockOptions, createMockStore } from './mocks' export interface DefaultPluginsOptions { abilities?: AbilityRule[] designSystem?: boolean gettext?: boolean pinia?: boolean + piniaOptions?: PiniaMockOptions } export const defaultPlugins = ({ abilities = [], designSystem = true, gettext = true, - pinia = true + pinia = true, + piniaOptions = {} }: DefaultPluginsOptions = {}) => { const plugins = [] @@ -45,8 +47,7 @@ export const defaultPlugins = ({ } if (pinia) { - const pinia = createPinia() - plugins.push(pinia) + plugins.push(createMockStore(piniaOptions)) } plugins.push({ diff --git a/packages/web-test-helpers/src/mocks/index.ts b/packages/web-test-helpers/src/mocks/index.ts index f1bfc32f2e4..5776955e741 100644 --- a/packages/web-test-helpers/src/mocks/index.ts +++ b/packages/web-test-helpers/src/mocks/index.ts @@ -1,2 +1,3 @@ export * from './axios' export * from './useGetMatchingSpaceMock' +export * from './pinia' diff --git a/packages/web-test-helpers/src/mocks/pinia.ts b/packages/web-test-helpers/src/mocks/pinia.ts index c305c3b1216..bf94f609133 100644 --- a/packages/web-test-helpers/src/mocks/pinia.ts +++ b/packages/web-test-helpers/src/mocks/pinia.ts @@ -1,32 +1,43 @@ -import { createTestingPinia as createCustomThemeStore } from '@pinia/testing' +import { createTestingPinia } from '@pinia/testing' import defaultTheme from '../../../web-runtime/themes/owncloud/theme.json' +import { Modal, WebThemeType } from '../../../web-pkg/src/composables/piniaStores' -export { createCustomThemeStore } +export type PiniaMockOptions = { + stubActions?: boolean + themeState?: { availableThemes?: WebThemeType[]; currentTheme?: WebThemeType } + modalsState?: { modals?: Modal[] } +} -export function createMockThemeStore({ hasOnlyOneTheme = false, appBanner = undefined } = {}) { +export function createMockStore({ + stubActions = true, + themeState = {}, + modalsState = {} +}: PiniaMockOptions = {}) { const defaultOwnCloudTheme = { defaults: { ...defaultTheme.clients.web.defaults, common: { ...defaultTheme.common, urls: ['https://imprint.url.theme', 'https://privacy.url.theme'] - }, - ...(appBanner && { appBanner }) + } }, themes: defaultTheme.clients.web.themes } - return createCustomThemeStore({ - stubActions: false, + return createTestingPinia({ + stubActions, initialState: { + modals: { + modals: [], + ...modalsState + }, theme: { currentTheme: { ...defaultOwnCloudTheme.defaults, ...defaultOwnCloudTheme.themes[0] }, - availableThemes: hasOnlyOneTheme - ? [defaultOwnCloudTheme.themes[0]] - : defaultOwnCloudTheme.themes + availableThemes: defaultOwnCloudTheme.themes, + ...themeState } } }) diff --git a/packages/web-test-helpers/src/mocks/store/defaultStoreMockOptions.ts b/packages/web-test-helpers/src/mocks/store/defaultStoreMockOptions.ts index d534f199fe1..c6690c55486 100644 --- a/packages/web-test-helpers/src/mocks/store/defaultStoreMockOptions.ts +++ b/packages/web-test-helpers/src/mocks/store/defaultStoreMockOptions.ts @@ -38,14 +38,9 @@ export const defaultStoreMockOptions = { } }, actions: { - createModal: jest.fn(), - hideModal: jest.fn(), - toggleModalConfirmButton: jest.fn(), - setModalConfirmButtonDisabled: jest.fn(), showMessage: jest.fn(), showErrorMessage: jest.fn(), deleteNotification: jest.fn(), - setModalInputErrorMessage: jest.fn(), openNavigation: jest.fn(), closeNavigation: jest.fn() }, From 5101319ab1446209e8eae968f8ec091c9e51abc8 Mon Sep 17 00:00:00 2001 From: Jannik Stehle Date: Thu, 21 Dec 2023 13:41:28 +0100 Subject: [PATCH 2/6] refactor: rename registerModal to dispatchModal --- changelog/unreleased/change-creating-modals | 2 +- .../groups/useGroupActionsCreateGroup.ts | 4 ++-- .../actions/groups/useGroupActionsDelete.ts | 4 ++-- .../users/useUserActionsAddToGroups.ts | 4 ++-- .../actions/users/useUserActionsCreateUser.ts | 4 ++-- .../actions/users/useUserActionsDelete.ts | 4 ++-- .../actions/users/useUserActionsEditLogin.ts | 4 ++-- .../actions/users/useUserActionsEditQuota.ts | 4 ++-- .../users/useUserActionsRemoveFromGroups.ts | 4 ++-- .../groups/useGroupActionsCreateGroup.spec.ts | 4 ++-- .../users/useUserActionsAddToGroups.spec.ts | 4 ++-- .../users/useUserActionsCreateUser.spec.ts | 4 ++-- .../users/useUserActionsEditLogin.spec.ts | 4 ++-- .../users/useUserActionsEditQuota.spec.ts | 4 ++-- .../useUserActionsRemoveFromGroups.spec.ts | 4 ++-- .../src/components/AppBar/CreateSpace.vue | 4 ++-- .../SideBar/Shares/Collaborators/ListItem.vue | 8 +++---- .../components/SideBar/Shares/FileLinks.vue | 8 +++---- .../components/SideBar/Shares/FileShares.vue | 6 ++--- .../SideBar/Shares/Links/DetailsAndEdit.vue | 10 ++++----- .../SideBar/Shares/SpaceMembers.vue | 6 ++--- .../components/AppBar/CreateSpace.spec.ts | 4 ++-- packages/web-app-importer/src/extensions.ts | 4 ++-- .../components/AppTemplates/AppWrapper.vue | 19 +++++++--------- .../actions/files/useFileActionsCreateLink.ts | 4 ++-- .../files/useFileActionsCreateNewFile.ts | 4 ++-- .../files/useFileActionsCreateNewFolder.ts | 4 ++-- .../files/useFileActionsCreateNewShortcut.ts | 4 ++-- .../useFileActionsCreateSpaceFromResource.ts | 4 ++-- .../files/useFileActionsEmptyTrashBin.ts | 4 ++-- .../actions/files/useFileActionsRename.ts | 4 ++-- .../actions/files/useFileActionsRestore.ts | 2 +- .../helpers/useFileActionsDeleteResources.ts | 4 ++-- .../actions/spaces/useSpaceActionsDelete.ts | 4 ++-- .../actions/spaces/useSpaceActionsDisable.ts | 4 ++-- .../spaces/useSpaceActionsEditDescription.ts | 4 ++-- .../spaces/useSpaceActionsEditQuota.ts | 4 ++-- .../useSpaceActionsEditReadmeContent.ts | 4 ++-- .../actions/spaces/useSpaceActionsRename.ts | 4 ++-- .../actions/spaces/useSpaceActionsRestore.ts | 4 ++-- .../src/composables/piniaStores/modals.ts | 4 ++-- .../conflictHandling/conflictDialog.ts | 8 +++---- .../files/useFileActionsCreateLink.spec.ts | 8 +++---- .../files/useFileActionsCreateNewFile.spec.ts | 4 ++-- .../useFileActionsCreateNewFolder.spec.ts | 4 ++-- .../useFileActionsCreateNewShortcut.spec.ts | 4 ++-- .../files/useFileActionsEmptyTrashBin.spec.ts | 4 ++-- .../files/useFileActionsRename.spec.ts | 4 ++-- .../spaces/useSpaceActionsDelete.spec.ts | 8 +++---- .../spaces/useSpaceActionsDisable.spec.ts | 8 +++---- .../useSpaceActionsEditDescription.spec.ts | 8 +++---- .../spaces/useSpaceActionsEditQuota.spec.ts | 4 ++-- .../useSpaceActionsEditReadmeContent.spec.ts | 4 ++-- .../spaces/useSpaceActionsRename.spec.ts | 8 +++---- .../spaces/useSpaceActionsRestore.spec.ts | 8 +++---- .../composables/piniaStores/useModals.spec.ts | 22 +++++++++---------- .../conflictHandling/conflictDialog.spec.ts | 4 ++-- packages/web-runtime/src/pages/account.vue | 4 ++-- 58 files changed, 151 insertions(+), 156 deletions(-) diff --git a/changelog/unreleased/change-creating-modals b/changelog/unreleased/change-creating-modals index d60c0624d96..49a39eac2c7 100644 --- a/changelog/unreleased/change-creating-modals +++ b/changelog/unreleased/change-creating-modals @@ -1,6 +1,6 @@ Change: Creating modals -BREAKING CHANGE for developers: The way how to work with modals has been reworked. Modals can now be registered via the `registerModal` method provided by the `useModals` composable, instead of calling `createModal` or `hideModal` from the store. +BREAKING CHANGE for developers: The way how to work with modals has been reworked. Modals can now be registered via the `dispatchModal` method provided by the `useModals` composable, instead of calling `createModal` or `hideModal` from the store. For more details on how to use the modal please see the linked PR down below. diff --git a/packages/web-app-admin-settings/src/composables/actions/groups/useGroupActionsCreateGroup.ts b/packages/web-app-admin-settings/src/composables/actions/groups/useGroupActionsCreateGroup.ts index ce862e48262..9c620001ac3 100644 --- a/packages/web-app-admin-settings/src/composables/actions/groups/useGroupActionsCreateGroup.ts +++ b/packages/web-app-admin-settings/src/composables/actions/groups/useGroupActionsCreateGroup.ts @@ -4,7 +4,7 @@ import { useGettext } from 'vue3-gettext' import CreateGroupModal from '../../../components/Groups/CreateGroupModal.vue' export const useGroupActionsCreateGroup = () => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() const { $gettext } = useGettext() const actions = computed((): UserAction[] => [ @@ -16,7 +16,7 @@ export const useGroupActionsCreateGroup = () => { label: () => $gettext('New group'), isEnabled: () => true, handler: () => { - registerModal({ + dispatchModal({ title: $gettext('Create group'), customComponent: CreateGroupModal }) diff --git a/packages/web-app-admin-settings/src/composables/actions/groups/useGroupActionsDelete.ts b/packages/web-app-admin-settings/src/composables/actions/groups/useGroupActionsDelete.ts index dbd05c2483b..6055f227325 100644 --- a/packages/web-app-admin-settings/src/composables/actions/groups/useGroupActionsDelete.ts +++ b/packages/web-app-admin-settings/src/composables/actions/groups/useGroupActionsDelete.ts @@ -10,7 +10,7 @@ export const useGroupActionsDelete = ({ store }: { store?: Store }) => { store = store || useStore() const { $gettext, $ngettext } = useGettext() const clientService = useClientService() - const { registerModal } = useModals() + const { dispatchModal } = useModals() const deleteGroups = async (groups: Group[]) => { const graphClient = clientService.graphAuthenticated @@ -60,7 +60,7 @@ export const useGroupActionsDelete = ({ store }: { store?: Store }) => { return } - registerModal({ + dispatchModal({ variation: 'danger', title: $ngettext( 'Delete group "%{group}"?', diff --git a/packages/web-app-admin-settings/src/composables/actions/users/useUserActionsAddToGroups.ts b/packages/web-app-admin-settings/src/composables/actions/users/useUserActionsAddToGroups.ts index 6d163a802fc..50ed825c49d 100644 --- a/packages/web-app-admin-settings/src/composables/actions/users/useUserActionsAddToGroups.ts +++ b/packages/web-app-admin-settings/src/composables/actions/users/useUserActionsAddToGroups.ts @@ -5,11 +5,11 @@ import { Group } from '@ownclouders/web-client/src/generated' import AddToGroupsModal from '../../../components/Users/AddToGroupsModal.vue' export const useUserActionsAddToGroups = ({ groups }: { groups: Ref }) => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() const { $gettext, $ngettext } = useGettext() const handler = ({ resources }) => { - registerModal({ + dispatchModal({ title: $ngettext( 'Add user "%{user}" to groups', 'Add %{userCount} users to groups ', diff --git a/packages/web-app-admin-settings/src/composables/actions/users/useUserActionsCreateUser.ts b/packages/web-app-admin-settings/src/composables/actions/users/useUserActionsCreateUser.ts index 883d3dc1cd5..0081d9e0ba4 100644 --- a/packages/web-app-admin-settings/src/composables/actions/users/useUserActionsCreateUser.ts +++ b/packages/web-app-admin-settings/src/composables/actions/users/useUserActionsCreateUser.ts @@ -6,7 +6,7 @@ import { useCapabilityCreateUsersDisabled } from '@ownclouders/web-pkg' import CreateUserModal from '../../../components/Users/CreateUserModal.vue' export const useUserActionsCreateUser = () => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() const createUsersDisabled = useCapabilityCreateUsersDisabled() const { $gettext } = useGettext() @@ -19,7 +19,7 @@ export const useUserActionsCreateUser = () => { label: () => $gettext('New user'), isEnabled: () => !unref(createUsersDisabled), handler: () => { - registerModal({ + dispatchModal({ title: $gettext('Create user'), customComponent: CreateUserModal }) diff --git a/packages/web-app-admin-settings/src/composables/actions/users/useUserActionsDelete.ts b/packages/web-app-admin-settings/src/composables/actions/users/useUserActionsDelete.ts index c995fb46199..8dc8f3aadeb 100644 --- a/packages/web-app-admin-settings/src/composables/actions/users/useUserActionsDelete.ts +++ b/packages/web-app-admin-settings/src/composables/actions/users/useUserActionsDelete.ts @@ -10,7 +10,7 @@ export const useUserActionsDelete = ({ store }: { store?: Store }) => { store = store || useStore() const { $gettext, $ngettext } = useGettext() const clientService = useClientService() - const { registerModal } = useModals() + const { dispatchModal } = useModals() const deleteUsers = async (users: User[]) => { const graphClient = clientService.graphAuthenticated @@ -60,7 +60,7 @@ export const useUserActionsDelete = ({ store }: { store?: Store }) => { return } - registerModal({ + dispatchModal({ variation: 'danger', title: $ngettext('Delete user "%{user}"?', 'Delete %{userCount} users?', resources.length, { user: resources[0].displayName, diff --git a/packages/web-app-admin-settings/src/composables/actions/users/useUserActionsEditLogin.ts b/packages/web-app-admin-settings/src/composables/actions/users/useUserActionsEditLogin.ts index c920e79cb2b..6cfce8daf5b 100644 --- a/packages/web-app-admin-settings/src/composables/actions/users/useUserActionsEditLogin.ts +++ b/packages/web-app-admin-settings/src/composables/actions/users/useUserActionsEditLogin.ts @@ -4,12 +4,12 @@ import { UserAction, useCapabilityReadOnlyUserAttributes, useModals } from '@own import LoginModal from '../../../components/Users/LoginModal.vue' export const useUserActionsEditLogin = () => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() const readOnlyUserAttributes = useCapabilityReadOnlyUserAttributes() const { $gettext, $ngettext } = useGettext() const handler = ({ resources }) => { - registerModal({ + dispatchModal({ title: $ngettext( 'Edit login for "%{user}"', 'Edit login for %{userCount} users', diff --git a/packages/web-app-admin-settings/src/composables/actions/users/useUserActionsEditQuota.ts b/packages/web-app-admin-settings/src/composables/actions/users/useUserActionsEditQuota.ts index 1dac54126fc..1345ab5a808 100644 --- a/packages/web-app-admin-settings/src/composables/actions/users/useUserActionsEditQuota.ts +++ b/packages/web-app-admin-settings/src/composables/actions/users/useUserActionsEditQuota.ts @@ -13,7 +13,7 @@ import { isPersonalSpaceResource } from '@ownclouders/web-client/src/helpers' import { User } from '@ownclouders/web-client/src/generated' export const useUserActionsEditQuota = () => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() const { $gettext } = useGettext() const ability = useAbility() const readOnlyUserAttributes = useCapabilityReadOnlyUserAttributes() @@ -51,7 +51,7 @@ export const useUserActionsEditQuota = () => { ({ drive }) => !isPersonalSpaceResource(drive as SpaceResource) ) - registerModal({ + dispatchModal({ title: getModalTitle({ resources }), customComponent: QuotaModal, customComponentAttrs: () => ({ diff --git a/packages/web-app-admin-settings/src/composables/actions/users/useUserActionsRemoveFromGroups.ts b/packages/web-app-admin-settings/src/composables/actions/users/useUserActionsRemoveFromGroups.ts index a514d33e279..1d6b727db1f 100644 --- a/packages/web-app-admin-settings/src/composables/actions/users/useUserActionsRemoveFromGroups.ts +++ b/packages/web-app-admin-settings/src/composables/actions/users/useUserActionsRemoveFromGroups.ts @@ -5,11 +5,11 @@ import { Group } from '@ownclouders/web-client/src/generated' import RemoveFromGroupsModal from '../../../components/Users/RemoveFromGroupsModal.vue' export const useUserActionsRemoveFromGroups = ({ groups }: { groups: Ref }) => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() const { $gettext, $ngettext } = useGettext() const handler = ({ resources }) => { - registerModal({ + dispatchModal({ title: $ngettext( 'Remove user "%{user}" from groups', 'Remove %{userCount} users from groups ', diff --git a/packages/web-app-admin-settings/tests/unit/composables/actions/groups/useGroupActionsCreateGroup.spec.ts b/packages/web-app-admin-settings/tests/unit/composables/actions/groups/useGroupActionsCreateGroup.spec.ts index 6a5af3a5c43..6230c0b2494 100644 --- a/packages/web-app-admin-settings/tests/unit/composables/actions/groups/useGroupActionsCreateGroup.spec.ts +++ b/packages/web-app-admin-settings/tests/unit/composables/actions/groups/useGroupActionsCreateGroup.spec.ts @@ -8,9 +8,9 @@ describe('useGroupActionsCreateGroup', () => { it('creates a modal', () => { getWrapper({ setup: async ({ actions }) => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() await unref(actions)[0].handler() - expect(registerModal).toHaveBeenCalled() + expect(dispatchModal).toHaveBeenCalled() } }) }) diff --git a/packages/web-app-admin-settings/tests/unit/composables/actions/users/useUserActionsAddToGroups.spec.ts b/packages/web-app-admin-settings/tests/unit/composables/actions/users/useUserActionsAddToGroups.spec.ts index 88eaafc32e6..994ed051e01 100644 --- a/packages/web-app-admin-settings/tests/unit/composables/actions/users/useUserActionsAddToGroups.spec.ts +++ b/packages/web-app-admin-settings/tests/unit/composables/actions/users/useUserActionsAddToGroups.spec.ts @@ -23,9 +23,9 @@ describe('useUserActionsAddToGroups', () => { it('creates a modal', () => { getWrapper({ setup: async ({ actions }) => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() await unref(actions)[0].handler({ resources: [mock()] }) - expect(registerModal).toHaveBeenCalled() + expect(dispatchModal).toHaveBeenCalled() } }) }) diff --git a/packages/web-app-admin-settings/tests/unit/composables/actions/users/useUserActionsCreateUser.spec.ts b/packages/web-app-admin-settings/tests/unit/composables/actions/users/useUserActionsCreateUser.spec.ts index 684ff44d26c..2935358f83e 100644 --- a/packages/web-app-admin-settings/tests/unit/composables/actions/users/useUserActionsCreateUser.spec.ts +++ b/packages/web-app-admin-settings/tests/unit/composables/actions/users/useUserActionsCreateUser.spec.ts @@ -26,9 +26,9 @@ describe('useUserActionsCreateUser', () => { it('creates a modal', () => { getWrapper({ setup: async ({ actions }) => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() await unref(actions)[0].handler() - expect(registerModal).toHaveBeenCalled() + expect(dispatchModal).toHaveBeenCalled() } }) }) diff --git a/packages/web-app-admin-settings/tests/unit/composables/actions/users/useUserActionsEditLogin.spec.ts b/packages/web-app-admin-settings/tests/unit/composables/actions/users/useUserActionsEditLogin.spec.ts index 3210cbe4973..33b71486426 100644 --- a/packages/web-app-admin-settings/tests/unit/composables/actions/users/useUserActionsEditLogin.spec.ts +++ b/packages/web-app-admin-settings/tests/unit/composables/actions/users/useUserActionsEditLogin.spec.ts @@ -36,9 +36,9 @@ describe('useUserActionsEditLogin', () => { it('creates a modal', () => { getWrapper({ setup: async ({ actions }) => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() await unref(actions)[0].handler({ resources: [mock()] }) - expect(registerModal).toHaveBeenCalled() + expect(dispatchModal).toHaveBeenCalled() } }) }) diff --git a/packages/web-app-admin-settings/tests/unit/composables/actions/users/useUserActionsEditQuota.spec.ts b/packages/web-app-admin-settings/tests/unit/composables/actions/users/useUserActionsEditQuota.spec.ts index 20563e69ee6..930fcef0e55 100644 --- a/packages/web-app-admin-settings/tests/unit/composables/actions/users/useUserActionsEditQuota.spec.ts +++ b/packages/web-app-admin-settings/tests/unit/composables/actions/users/useUserActionsEditQuota.spec.ts @@ -72,9 +72,9 @@ describe('useUserActionsEditQuota', () => { it('should create a modal', () => { getWrapper({ setup: async ({ actions }) => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() await unref(actions)[0].handler({ resources: [] }) - expect(registerModal).toHaveBeenCalled() + expect(dispatchModal).toHaveBeenCalled() } }) }) diff --git a/packages/web-app-admin-settings/tests/unit/composables/actions/users/useUserActionsRemoveFromGroups.spec.ts b/packages/web-app-admin-settings/tests/unit/composables/actions/users/useUserActionsRemoveFromGroups.spec.ts index f35c922b6f8..a0acb14a96a 100644 --- a/packages/web-app-admin-settings/tests/unit/composables/actions/users/useUserActionsRemoveFromGroups.spec.ts +++ b/packages/web-app-admin-settings/tests/unit/composables/actions/users/useUserActionsRemoveFromGroups.spec.ts @@ -23,9 +23,9 @@ describe('useUserActionsRemoveFromGroups', () => { it('creates a modal', () => { getWrapper({ setup: async ({ actions }) => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() await unref(actions)[0].handler({ resources: [mock()] }) - expect(registerModal).toHaveBeenCalled() + expect(dispatchModal).toHaveBeenCalled() } }) }) diff --git a/packages/web-app-files/src/components/AppBar/CreateSpace.vue b/packages/web-app-files/src/components/AppBar/CreateSpace.vue index 79922b064f6..ca6248cbbcb 100644 --- a/packages/web-app-files/src/components/AppBar/CreateSpace.vue +++ b/packages/web-app-files/src/components/AppBar/CreateSpace.vue @@ -24,7 +24,7 @@ export default defineComponent({ const { $gettext } = useGettext() const { createSpace } = useCreateSpace() const { checkSpaceNameModalInput } = useSpaceHelpers() - const { registerModal } = useModals() + const { dispatchModal } = useModals() const addNewSpace = async (name: string) => { try { @@ -44,7 +44,7 @@ export default defineComponent({ } const showCreateSpaceModal = () => { - registerModal({ + dispatchModal({ title: $gettext('Create a new space'), confirmText: $gettext('Create'), hasInput: true, diff --git a/packages/web-app-files/src/components/SideBar/Shares/Collaborators/ListItem.vue b/packages/web-app-files/src/components/SideBar/Shares/Collaborators/ListItem.vue index f3f27177eb8..6860251b6bc 100644 --- a/packages/web-app-files/src/components/SideBar/Shares/Collaborators/ListItem.vue +++ b/packages/web-app-files/src/components/SideBar/Shares/Collaborators/ListItem.vue @@ -187,12 +187,10 @@ export default defineComponent({ const store = useStore() const clientService = useClientService() const { $gettext } = useGettext() - const { registerModal } = useModals() + const { dispatchModal } = useModals() const sharedParentDir = computed(() => { - return queryItemAsString(props.sharedParentRoute?.params?.driveAliasAndItem) - .split('/') - .pop() + return queryItemAsString(props.sharedParentRoute?.params?.driveAliasAndItem).split('/').pop() }) const setDenyShare = (value) => { @@ -200,7 +198,7 @@ export default defineComponent({ } const showNotifyShareModal = () => { - registerModal({ + dispatchModal({ variation: 'warning', icon: 'mail-send', title: $gettext('Send a reminder'), diff --git a/packages/web-app-files/src/components/SideBar/Shares/FileLinks.vue b/packages/web-app-files/src/components/SideBar/Shares/FileLinks.vue index c06378d017e..b91d616c259 100644 --- a/packages/web-app-files/src/components/SideBar/Shares/FileLinks.vue +++ b/packages/web-app-files/src/components/SideBar/Shares/FileLinks.vue @@ -173,7 +173,7 @@ export default defineComponent({ const hasResharing = useCapabilityFilesSharingResharing() const hasShareJail = useCapabilityShareJailEnabled() const { defaultLinkPermissions } = useDefaultLinkPermissions() - const { registerModal } = useModals() + const { dispatchModal } = useModals() const { actions: createLinkActions } = useFileActionsCreateLink({ store }) const createLinkAction = computed(() => @@ -265,7 +265,7 @@ export default defineComponent({ } const showPasswordModal = (params) => { - registerModal({ + dispatchModal({ title: $gettext('Set password'), customComponent: SetLinkPasswordModal, customComponentAttrs: () => ({ link: params }) @@ -298,7 +298,7 @@ export default defineComponent({ showPasswordModal, defaultLinkPermissions, addNewLink, - registerModal + dispatchModal } }, computed: { @@ -497,7 +497,7 @@ export default defineComponent({ }, deleteLinkConfirmation({ link }) { - this.registerModal({ + this.dispatchModal({ variation: 'danger', title: this.$gettext('Delete link'), message: this.$gettext( diff --git a/packages/web-app-files/src/components/SideBar/Shares/FileShares.vue b/packages/web-app-files/src/components/SideBar/Shares/FileShares.vue index 387274ddb8e..2d10dc1f1f3 100644 --- a/packages/web-app-files/src/components/SideBar/Shares/FileShares.vue +++ b/packages/web-app-files/src/components/SideBar/Shares/FileShares.vue @@ -129,7 +129,7 @@ export default defineComponent({ const store = useStore() const ability = useAbility() const { getMatchingSpace } = useGetMatchingSpace() - const { registerModal } = useModals() + const { dispatchModal } = useModals() const resource = inject>('resource') @@ -183,7 +183,7 @@ export default defineComponent({ hasShareCanDenyAccess: useCapabilityFilesSharingCanDenyAccess(), getSharedAncestor, configurationManager, - registerModal + dispatchModal } }, computed: { @@ -434,7 +434,7 @@ export default defineComponent({ }, $_ocCollaborators_deleteShare_trigger(share) { - this.registerModal({ + this.dispatchModal({ variation: 'danger', title: this.$gettext('Remove share'), confirmText: this.$gettext('Remove'), diff --git a/packages/web-app-files/src/components/SideBar/Shares/Links/DetailsAndEdit.vue b/packages/web-app-files/src/components/SideBar/Shares/Links/DetailsAndEdit.vue index 9503ba64c21..0d4acd50da4 100644 --- a/packages/web-app-files/src/components/SideBar/Shares/Links/DetailsAndEdit.vue +++ b/packages/web-app-files/src/components/SideBar/Shares/Links/DetailsAndEdit.vue @@ -212,7 +212,7 @@ export default defineComponent({ }, emits: ['removePublicLink', 'updateLink'], setup(props, { emit }) { - const { registerModal } = useModals() + const { dispatchModal } = useModals() const { $gettext, current } = useGettext() const configurationManager = useConfigurationManager() const passwordPolicyService = usePasswordPolicyService() @@ -240,7 +240,7 @@ export default defineComponent({ } const showPasswordModal = () => { - registerModal({ + dispatchModal({ title: props.link.password ? $gettext('Edit password') : $gettext('Add password'), customComponent: SetLinkPasswordModal, customComponentAttrs: () => ({ link: props.link }) @@ -257,7 +257,7 @@ export default defineComponent({ currentLinkRole, isRunningOnEos: computed(() => configurationManager.options.isRunningOnEos), showPasswordModal, - registerModal + dispatchModal } }, data() { @@ -479,7 +479,7 @@ export default defineComponent({ ;(this.$refs.editPublicLinkDropdown as InstanceType).hide() }, showRenameModal() { - this.registerModal({ + this.dispatchModal({ title: this.$gettext('Edit name'), confirmText: this.$gettext('Save'), hasInput: true, @@ -520,7 +520,7 @@ export default defineComponent({ ) }, showNotifyUploadsExtraRecipientsModal() { - this.registerModal({ + this.dispatchModal({ icon: 'mail-add', title: this.$gettext('Notify a third party about uploads'), confirmText: this.$gettext('Apply'), diff --git a/packages/web-app-files/src/components/SideBar/Shares/SpaceMembers.vue b/packages/web-app-files/src/components/SideBar/Shares/SpaceMembers.vue index a286e94839a..bad53766e0c 100644 --- a/packages/web-app-files/src/components/SideBar/Shares/SpaceMembers.vue +++ b/packages/web-app-files/src/components/SideBar/Shares/SpaceMembers.vue @@ -83,13 +83,13 @@ export default defineComponent({ }, setup() { const clientService = useClientService() - const { registerModal } = useModals() + const { dispatchModal } = useModals() return { clientService, configurationManager, resource: inject>('resource'), - registerModal + dispatchModal } }, data: () => { @@ -177,7 +177,7 @@ export default defineComponent({ }, $_ocCollaborators_deleteShare_trigger(share) { - this.registerModal({ + this.dispatchModal({ variation: 'danger', title: this.$gettext('Remove member'), confirmText: this.$gettext('Remove'), diff --git a/packages/web-app-files/tests/unit/components/AppBar/CreateSpace.spec.ts b/packages/web-app-files/tests/unit/components/AppBar/CreateSpace.spec.ts index 4af108a9a82..ca6f7386032 100644 --- a/packages/web-app-files/tests/unit/components/AppBar/CreateSpace.spec.ts +++ b/packages/web-app-files/tests/unit/components/AppBar/CreateSpace.spec.ts @@ -24,9 +24,9 @@ describe('CreateSpace component', () => { }) it('should show a modal when clicking the "New Space" button', async () => { const { wrapper } = getWrapper() - const { registerModal } = useModals() + const { dispatchModal } = useModals() await wrapper.find(selectors.newSpaceBtn).trigger('click') - expect(registerModal).toHaveBeenCalledTimes(1) + expect(dispatchModal).toHaveBeenCalledTimes(1) }) describe('method "addNewSpace"', () => { it('creates the space and updates the readme data after creation', async () => { diff --git a/packages/web-app-importer/src/extensions.ts b/packages/web-app-importer/src/extensions.ts index 9c8b6aa1521..39a0a9d50dc 100644 --- a/packages/web-app-importer/src/extensions.ts +++ b/packages/web-app-importer/src/extensions.ts @@ -20,7 +20,7 @@ export const extensions = ({ applicationConfig }: ApplicationSetupOptions) => { const publicLinkContext = usePublicLinkContext({ store }) const themeStore = useThemeStore() const { currentTheme } = storeToRefs(themeStore) - const { registerModal, removeModal, activeModal } = useModals() + const { dispatchModal, removeModal, activeModal } = useModals() const { companionUrl, webdavCloudType } = applicationConfig let { supportedClouds } = applicationConfig @@ -59,7 +59,7 @@ export const extensions = ({ applicationConfig }: ApplicationSetupOptions) => { const handler = () => { const renderDarkTheme = currentTheme.value.isDark - registerModal({ + dispatchModal({ title: $gettext('Import files'), hideConfirmButton: true, onCancel: () => { diff --git a/packages/web-pkg/src/components/AppTemplates/AppWrapper.vue b/packages/web-pkg/src/components/AppTemplates/AppWrapper.vue index 17796e2f792..179701a03e4 100644 --- a/packages/web-pkg/src/components/AppTemplates/AppWrapper.vue +++ b/packages/web-pkg/src/components/AppTemplates/AppWrapper.vue @@ -109,7 +109,7 @@ export default defineComponent({ const clientService = useClientService() const { getResourceContext } = useGetResourceContext() const { selectedResources } = useSelectedResources({ store }) - const { registerModal } = useModals() + const { dispatchModal } = useModals() const applicationName = ref('') const resource: Ref = ref() @@ -368,15 +368,12 @@ export default defineComponent({ } const editorOptions = store.getters.configuration.options.editor if (editorOptions.autosaveEnabled) { - autosaveIntervalId = setInterval( - async () => { - if (isDirty.value) { - await save() - autosavePopup() - } - }, - (editorOptions.autosaveInterval || 120) * 1000 - ) + autosaveIntervalId = setInterval(async () => { + if (isDirty.value) { + await save() + autosavePopup() + } + }, (editorOptions.autosaveInterval || 120) * 1000) } }) onBeforeUnmount(() => { @@ -414,7 +411,7 @@ export default defineComponent({ onBeforeRouteLeave((_to, _from, next) => { if (unref(isDirty)) { - registerModal({ + dispatchModal({ variation: 'danger', icon: 'warning', title: $gettext('Unsaved changes'), diff --git a/packages/web-pkg/src/composables/actions/files/useFileActionsCreateLink.ts b/packages/web-pkg/src/composables/actions/files/useFileActionsCreateLink.ts index 572b6de281a..81fad06699c 100644 --- a/packages/web-pkg/src/composables/actions/files/useFileActionsCreateLink.ts +++ b/packages/web-pkg/src/composables/actions/files/useFileActionsCreateLink.ts @@ -31,7 +31,7 @@ export const useFileActionsCreateLink = ({ const passwordEnforcedCapabilities = useCapabilityFilesSharingPublicPasswordEnforcedFor() const { defaultLinkPermissions } = useDefaultLinkPermissions() const { createLink } = useCreateLink() - const { registerModal } = useModals() + const { dispatchModal } = useModals() const proceedResult = (result: PromiseSettledResult[]) => { const succeeded = result.filter( @@ -69,7 +69,7 @@ export const useFileActionsCreateLink = ({ enforceModal || (passwordEnforced && unref(defaultLinkPermissions) > SharePermissionBit.Internal) ) { - registerModal({ + dispatchModal({ title: $ngettext( 'Create link for "%{resourceName}"', 'Create links for the selected items', diff --git a/packages/web-pkg/src/composables/actions/files/useFileActionsCreateNewFile.ts b/packages/web-pkg/src/composables/actions/files/useFileActionsCreateNewFile.ts index e0d192ecb5b..bfb25cf19f5 100644 --- a/packages/web-pkg/src/composables/actions/files/useFileActionsCreateNewFile.ts +++ b/packages/web-pkg/src/composables/actions/files/useFileActionsCreateNewFile.ts @@ -38,7 +38,7 @@ export const useFileActionsCreateNewFile = ({ const router = useRouter() const { $gettext } = useGettext() const { makeRequest } = useRequest() - const { registerModal } = useModals() + const { dispatchModal } = useModals() const { openEditor, triggerDefaultAction } = useFileActions() const clientService = useClientService() @@ -186,7 +186,7 @@ export const useFileActionsCreateNewFile = ({ ? null : ([0, defaultName.length - (extension.length + 1)] as [number, number]) - registerModal({ + dispatchModal({ title: $gettext('Create a new file'), confirmText: $gettext('Create'), hasInput: true, diff --git a/packages/web-pkg/src/composables/actions/files/useFileActionsCreateNewFolder.ts b/packages/web-pkg/src/composables/actions/files/useFileActionsCreateNewFolder.ts index 16efa520668..3a74f34c883 100644 --- a/packages/web-pkg/src/composables/actions/files/useFileActionsCreateNewFolder.ts +++ b/packages/web-pkg/src/composables/actions/files/useFileActionsCreateNewFolder.ts @@ -21,7 +21,7 @@ export const useFileActionsCreateNewFolder = ({ }: { store?: Store; space?: SpaceResource } = {}) => { store = store || useStore() const router = useRouter() - const { registerModal } = useModals() + const { dispatchModal } = useModals() const { $gettext } = useGettext() const { scrollToResource } = useScrollTo() @@ -99,7 +99,7 @@ export const useFileActionsCreateNewFolder = ({ defaultName = resolveFileNameDuplicate(defaultName, '', unref(files)) } - registerModal({ + dispatchModal({ title: $gettext('Create a new folder'), confirmText: $gettext('Create'), hasInput: true, diff --git a/packages/web-pkg/src/composables/actions/files/useFileActionsCreateNewShortcut.ts b/packages/web-pkg/src/composables/actions/files/useFileActionsCreateNewShortcut.ts index a1fcdf33f08..03ffd770551 100644 --- a/packages/web-pkg/src/composables/actions/files/useFileActionsCreateNewShortcut.ts +++ b/packages/web-pkg/src/composables/actions/files/useFileActionsCreateNewShortcut.ts @@ -7,7 +7,7 @@ import { useGettext } from 'vue3-gettext' export const useFileActionsCreateNewShortcut = ({ space }: { space: SpaceResource }) => { const store = useStore() - const { registerModal } = useModals() + const { dispatchModal } = useModals() const { $gettext } = useGettext() const currentFolder = computed((): Resource => store.getters['Files/currentFolder']) @@ -17,7 +17,7 @@ export const useFileActionsCreateNewShortcut = ({ space }: { space: SpaceResourc name: 'create-shortcut', icon: 'external-link', handler: () => { - registerModal({ + dispatchModal({ title: $gettext('Create a Shortcut'), confirmText: $gettext('Create'), customComponent: CreateShortcutModal, diff --git a/packages/web-pkg/src/composables/actions/files/useFileActionsCreateSpaceFromResource.ts b/packages/web-pkg/src/composables/actions/files/useFileActionsCreateSpaceFromResource.ts index 42366eafe1e..071aa647d12 100644 --- a/packages/web-pkg/src/composables/actions/files/useFileActionsCreateSpaceFromResource.ts +++ b/packages/web-pkg/src/composables/actions/files/useFileActionsCreateSpaceFromResource.ts @@ -21,7 +21,7 @@ export const useFileActionsCreateSpaceFromResource = ({ store }: { store?: Store const clientService = useClientService() const router = useRouter() const hasCreatePermission = computed(() => can('create-all', 'Drive')) - const { registerModal } = useModals() + const { dispatchModal } = useModals() const confirmAction = async ({ spaceName, resources, space }) => { const { webdav } = clientService @@ -57,7 +57,7 @@ export const useFileActionsCreateSpaceFromResource = ({ store }: { store?: Store } } const handler = ({ resources, space }: FileActionOptions) => { - registerModal({ + dispatchModal({ title: $ngettext( 'Create Space from "%{resourceName}"', 'Create Space from selection', diff --git a/packages/web-pkg/src/composables/actions/files/useFileActionsEmptyTrashBin.ts b/packages/web-pkg/src/composables/actions/files/useFileActionsEmptyTrashBin.ts index a70ec4f568d..c4fa6beedd4 100644 --- a/packages/web-pkg/src/composables/actions/files/useFileActionsEmptyTrashBin.ts +++ b/packages/web-pkg/src/composables/actions/files/useFileActionsEmptyTrashBin.ts @@ -18,7 +18,7 @@ export const useFileActionsEmptyTrashBin = ({ store }: { store?: Store } = const { $gettext, $pgettext } = useGettext() const clientService = useClientService() const hasPermanentDeletion = useCapabilityFilesPermanentDeletion() - const { registerModal } = useModals() + const { dispatchModal } = useModals() const emptyTrashBin = ({ space }: { space: SpaceResource }) => { return clientService.webdav @@ -42,7 +42,7 @@ export const useFileActionsEmptyTrashBin = ({ store }: { store?: Store } = } const handler = ({ space }: FileActionOptions) => { - registerModal({ + dispatchModal({ variation: 'danger', title: $gettext('Empty trash bin'), confirmText: $gettext('Delete'), diff --git a/packages/web-pkg/src/composables/actions/files/useFileActionsRename.ts b/packages/web-pkg/src/composables/actions/files/useFileActionsRename.ts index 64fe29c5611..c6057e97a16 100644 --- a/packages/web-pkg/src/composables/actions/files/useFileActionsRename.ts +++ b/packages/web-pkg/src/composables/actions/files/useFileActionsRename.ts @@ -28,7 +28,7 @@ export const useFileActionsRename = ({ store }: { store?: Store } = {}) => const clientService = useClientService() const canRename = useCapabilityFilesSharingCanRename() const configurationManager = useConfigurationManager() - const { registerModal } = useModals() + const { dispatchModal } = useModals() const getNameErrorMsg = (resource: Resource, newName: string, parentResources = undefined) => { const newPath = @@ -187,7 +187,7 @@ export const useFileActionsRename = ({ store }: { store?: Store } = {}) => ? null : ([0, nameWithoutExtension.length] as [number, number]) - registerModal({ + dispatchModal({ variation: 'passive', title, confirmText: $gettext('Rename'), diff --git a/packages/web-pkg/src/composables/actions/files/useFileActionsRestore.ts b/packages/web-pkg/src/composables/actions/files/useFileActionsRestore.ts index d751a6acbec..1b1524f2783 100644 --- a/packages/web-pkg/src/composables/actions/files/useFileActionsRestore.ts +++ b/packages/web-pkg/src/composables/actions/files/useFileActionsRestore.ts @@ -32,7 +32,7 @@ export const useFileActionsRestore = ({ store }: { store?: Store } = {}) => const { $gettext, $ngettext } = useGettext() const clientService = useClientService() const loadingService = useLoadingService() - const { registerModal } = useModals() + const { dispatchModal } = useModals() const hasSpacesEnabled = useCapabilitySpacesEnabled() diff --git a/packages/web-pkg/src/composables/actions/helpers/useFileActionsDeleteResources.ts b/packages/web-pkg/src/composables/actions/helpers/useFileActionsDeleteResources.ts index 823d8a3f35d..c778ef00ef3 100644 --- a/packages/web-pkg/src/composables/actions/helpers/useFileActionsDeleteResources.ts +++ b/packages/web-pkg/src/composables/actions/helpers/useFileActionsDeleteResources.ts @@ -29,7 +29,7 @@ export const useFileActionsDeleteResources = ({ store }: { store?: Store }) const clientService = useClientService() const loadingService = useLoadingService() const { owncloudSdk } = clientService - const { registerModal } = useModals() + const { dispatchModal } = useModals() const queue = new PQueue({ concurrency: 4 }) const deleteOps = [] @@ -236,7 +236,7 @@ export const useFileActionsDeleteResources = ({ store }: { store?: Store }) const displayDialog = (space: SpaceResource, resources: Resource[]) => { resourcesToDelete.value = [...resources] - registerModal({ + dispatchModal({ variation: 'danger', title: unref(dialogTitle), message: unref(dialogMessage), diff --git a/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsDelete.ts b/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsDelete.ts index ccbd2bafc0c..f2c254f935e 100644 --- a/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsDelete.ts +++ b/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsDelete.ts @@ -17,7 +17,7 @@ export const useSpaceActionsDelete = ({ store }: { store?: Store } = {}) => const ability = useAbility() const clientService = useClientService() const route = useRoute() - const { registerModal } = useModals() + const { dispatchModal } = useModals() const filterResourcesToDelete = (resources: SpaceResource[]) => { return resources.filter( @@ -88,7 +88,7 @@ export const useSpaceActionsDelete = ({ store }: { store?: Store } = {}) => { count: allowedResources.length.toString() } ) - registerModal({ + dispatchModal({ title: $ngettext( 'Delete Space "%{space}"?', 'Delete %{spaceCount} Spaces?', diff --git a/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsDisable.ts b/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsDisable.ts index f5a33449ebb..e7c8c364b9f 100644 --- a/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsDisable.ts +++ b/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsDisable.ts @@ -17,7 +17,7 @@ export const useSpaceActionsDisable = ({ store }: { store?: Store } = {}) = const clientService = useClientService() const route = useRoute() const router = useRouter() - const { registerModal } = useModals() + const { dispatchModal } = useModals() const filterResourcesToDisable = (resources: SpaceResource[]): SpaceResource[] => { return resources.filter( @@ -97,7 +97,7 @@ export const useSpaceActionsDisable = ({ store }: { store?: Store } = {}) = ) const confirmText = $gettext('Disable') - registerModal({ + dispatchModal({ title: $ngettext( 'Disable Space "%{space}"?', 'Disable %{spaceCount} Spaces?', diff --git a/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsEditDescription.ts b/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsEditDescription.ts index f1e06624fcc..153e05e4e6b 100644 --- a/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsEditDescription.ts +++ b/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsEditDescription.ts @@ -16,7 +16,7 @@ export const useSpaceActionsEditDescription = ({ store }: { store?: Store } const ability = useAbility() const clientService = useClientService() const route = useRoute() - const { registerModal } = useModals() + const { dispatchModal } = useModals() const editDescriptionSpace = (space: SpaceResource, description: string) => { const graphClient = clientService.graphAuthenticated @@ -49,7 +49,7 @@ export const useSpaceActionsEditDescription = ({ store }: { store?: Store } return } - registerModal({ + dispatchModal({ title: $gettext('Change subtitle for space') + ' ' + resources[0].name, confirmText: $gettext('Confirm'), hasInput: true, diff --git a/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsEditQuota.ts b/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsEditQuota.ts index b4c6c14dffe..0f330be6785 100644 --- a/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsEditQuota.ts +++ b/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsEditQuota.ts @@ -7,7 +7,7 @@ import { QuotaModal } from '../../../components' import { useModals } from '../../piniaStores' export const useSpaceActionsEditQuota = () => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() const { $gettext } = useGettext() const ability = useAbility() @@ -23,7 +23,7 @@ export const useSpaceActionsEditQuota = () => { } const handler = ({ resources }: SpaceActionOptions) => { - registerModal({ + dispatchModal({ title: getModalTitle({ resources }), customComponent: QuotaModal, customComponentAttrs: () => ({ diff --git a/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsEditReadmeContent.ts b/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsEditReadmeContent.ts index 1e53f2b13e0..340c18aef26 100644 --- a/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsEditReadmeContent.ts +++ b/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsEditReadmeContent.ts @@ -8,11 +8,11 @@ import { useModals } from '../../piniaStores' export const useSpaceActionsEditReadmeContent = ({ store }: { store?: Store } = {}) => { store = store || useStore() - const { registerModal } = useModals() + const { dispatchModal } = useModals() const { $gettext } = useGettext() const handler = ({ resources }: SpaceActionOptions) => { - registerModal({ + dispatchModal({ title: $gettext('Edit description for space %{name}', { name: resources[0].name }), diff --git a/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsRename.ts b/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsRename.ts index 6f6d14ab0c3..bdca004ded7 100644 --- a/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsRename.ts +++ b/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsRename.ts @@ -17,7 +17,7 @@ export const useSpaceActionsRename = ({ store }: { store?: Store } = {}) => const clientService = useClientService() const route = useRoute() const { checkSpaceNameModalInput } = useSpaceHelpers() - const { registerModal } = useModals() + const { dispatchModal } = useModals() const renameSpace = (space: SpaceResource, name: string) => { const graphClient = clientService.graphAuthenticated @@ -50,7 +50,7 @@ export const useSpaceActionsRename = ({ store }: { store?: Store } = {}) => return } - registerModal({ + dispatchModal({ title: $gettext('Rename space') + ' ' + resources[0].name, confirmText: $gettext('Rename'), hasInput: true, diff --git a/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsRestore.ts b/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsRestore.ts index f977d386918..6f8f11386a4 100644 --- a/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsRestore.ts +++ b/packages/web-pkg/src/composables/actions/spaces/useSpaceActionsRestore.ts @@ -18,7 +18,7 @@ export const useSpaceActionsRestore = ({ store }: { store?: Store } = {}) = const clientService = useClientService() const loadingService = useLoadingService() const route = useRoute() - const { registerModal } = useModals() + const { dispatchModal } = useModals() const filterResourcesToRestore = (resources): SpaceResource[] => { return resources.filter( @@ -104,7 +104,7 @@ export const useSpaceActionsRestore = ({ store }: { store?: Store } = {}) = ) const confirmText = $gettext('Enable') - registerModal({ + dispatchModal({ title: $ngettext( 'Enable Space "%{space}"?', 'Enable %{spaceCount} Spaces?', diff --git a/packages/web-pkg/src/composables/piniaStores/modals.ts b/packages/web-pkg/src/composables/piniaStores/modals.ts index 806ffb51968..0e1dec75b82 100644 --- a/packages/web-pkg/src/composables/piniaStores/modals.ts +++ b/packages/web-pkg/src/composables/piniaStores/modals.ts @@ -57,7 +57,7 @@ export const useModals = defineStore('modals', () => { return unref(modals).find((modal) => modal.id === id) } - const registerModal = ( + const dispatchModal = ( data: Omit, { isActive = true }: { isActive?: boolean } = {} ) => { @@ -98,5 +98,5 @@ export const useModals = defineStore('modals', () => { modal.active = true } - return { modals, activeModal, registerModal, updateModal, removeModal, setModalActive } + return { modals, activeModal, dispatchModal, updateModal, removeModal, setModalActive } }) diff --git a/packages/web-pkg/src/helpers/resource/conflictHandling/conflictDialog.ts b/packages/web-pkg/src/helpers/resource/conflictHandling/conflictDialog.ts index 9a938cf3dd4..61f448bd44a 100644 --- a/packages/web-pkg/src/helpers/resource/conflictHandling/conflictDialog.ts +++ b/packages/web-pkg/src/helpers/resource/conflictHandling/conflictDialog.ts @@ -81,10 +81,10 @@ export class ConflictDialog { suggestMerge = false, separateSkipHandling = false // separate skip-handling between files and folders ): Promise { - const { registerModal } = useModals() + const { dispatchModal } = useModals() return new Promise((resolve) => { - registerModal({ + dispatchModal({ variation: 'danger', title: resource.isFolder ? this.$gettext('Folder already exists') @@ -105,10 +105,10 @@ export class ConflictDialog { } resolveDoCopyInsteadOfMoveForSpaces(): Promise { - const { registerModal } = useModals() + const { dispatchModal } = useModals() return new Promise((resolve) => { - registerModal({ + dispatchModal({ variation: 'danger', title: this.$gettext('Copy here?'), customComponent: SpaceMoveInfoModal, diff --git a/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateLink.spec.ts b/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateLink.spec.ts index 12cb69244cb..42949de60b9 100644 --- a/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateLink.spec.ts +++ b/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateLink.spec.ts @@ -87,10 +87,10 @@ describe('useFileActionsCreateLink', () => { getWrapper({ enforceModal: true, setup: ({ actions }, { mocks }) => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() unref(actions)[0].handler({ resources: [mock({ canShare: () => true })] }) expect(mocks.createLinkMock).not.toHaveBeenCalled() - expect(registerModal).toHaveBeenCalledTimes(1) + expect(dispatchModal).toHaveBeenCalledTimes(1) } }) }) @@ -99,10 +99,10 @@ describe('useFileActionsCreateLink', () => { passwordEnforced: true, defaultLinkPermissions: SharePermissionBit.Read, setup: ({ actions }, { mocks }) => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() unref(actions)[0].handler({ resources: [mock({ canShare: () => true })] }) expect(mocks.createLinkMock).not.toHaveBeenCalled() - expect(registerModal).toHaveBeenCalledTimes(1) + expect(dispatchModal).toHaveBeenCalledTimes(1) } }) }) diff --git a/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateNewFile.spec.ts b/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateNewFile.spec.ts index f256699703b..b61f6586c00 100644 --- a/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateNewFile.spec.ts +++ b/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateNewFile.spec.ts @@ -77,11 +77,11 @@ describe('useFileActionsCreateNewFile', () => { getWrapper({ space, setup: async ({ actions }) => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() const fileActionOptions: FileActionOptions = { space, resources: [] } as FileActionOptions unref(actions)[0].handler(fileActionOptions) await nextTick() - expect(registerModal).toHaveBeenCalled() + expect(dispatchModal).toHaveBeenCalled() } }) }) diff --git a/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateNewFolder.spec.ts b/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateNewFolder.spec.ts index c0e99bd25b9..1074a3d1e77 100644 --- a/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateNewFolder.spec.ts +++ b/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateNewFolder.spec.ts @@ -82,10 +82,10 @@ describe('useFileActionsCreateNewFolder', () => { getWrapper({ space, setup: ({ actions }) => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() unref(actions)[0].handler() - expect(registerModal).toHaveBeenCalled() + expect(dispatchModal).toHaveBeenCalled() } }) }) diff --git a/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateNewShortcut.spec.ts b/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateNewShortcut.spec.ts index 39accdf12da..a1b4f4fe781 100644 --- a/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateNewShortcut.spec.ts +++ b/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsCreateNewShortcut.spec.ts @@ -35,9 +35,9 @@ describe('createNewShortcut', () => { it('creates a modal', () => { getWrapper({ setup: async ({ actions }) => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() await unref(actions)[0].handler() - expect(registerModal).toHaveBeenCalled() + expect(dispatchModal).toHaveBeenCalled() } }) }) diff --git a/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsEmptyTrashBin.spec.ts b/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsEmptyTrashBin.spec.ts index 0aa3b789379..9299da763fb 100644 --- a/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsEmptyTrashBin.spec.ts +++ b/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsEmptyTrashBin.spec.ts @@ -45,10 +45,10 @@ describe('emptyTrashBin', () => { it('should trigger the empty trash bin modal window', () => { getWrapper({ setup: async ({ actions }) => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() await unref(actions)[0].handler(mock()) - expect(registerModal).toHaveBeenCalledTimes(1) + expect(dispatchModal).toHaveBeenCalledTimes(1) } }) }) diff --git a/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsRename.spec.ts b/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsRename.spec.ts index 4319dca67e0..715e701cf16 100644 --- a/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsRename.spec.ts +++ b/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsRename.spec.ts @@ -55,10 +55,10 @@ describe('rename', () => { it('should trigger the rename modal window', () => { getWrapper({ setup: async ({ actions }, { space }) => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() const resources = [currentFolder] await unref(actions)[0].handler({ space, resources }) - expect(registerModal).toHaveBeenCalledTimes(1) + expect(dispatchModal).toHaveBeenCalledTimes(1) } }) }) diff --git a/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsDelete.spec.ts b/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsDelete.spec.ts index 8fe6356ff44..dc2fb935b15 100644 --- a/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsDelete.spec.ts +++ b/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsDelete.spec.ts @@ -75,28 +75,28 @@ describe('delete', () => { it('should trigger the delete modal window', () => { getWrapper({ setup: async ({ actions }, { storeOptions }) => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() await unref(actions)[0].handler({ resources: [ mock({ id: '1', canBeDeleted: () => true, driveType: 'project' }) ] }) - expect(registerModal).toHaveBeenCalledTimes(1) + expect(dispatchModal).toHaveBeenCalledTimes(1) } }) }) it('should not trigger the delete modal window without any resource to delete', () => { getWrapper({ setup: async ({ actions }, { storeOptions }) => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() await unref(actions)[0].handler({ resources: [ mock({ id: '1', canBeDeleted: () => false, driveType: 'project' }) ] }) - expect(registerModal).toHaveBeenCalledTimes(0) + expect(dispatchModal).toHaveBeenCalledTimes(0) } }) }) diff --git a/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsDisable.spec.ts b/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsDisable.spec.ts index 114a19845bd..adb1fd88187 100644 --- a/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsDisable.spec.ts +++ b/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsDisable.spec.ts @@ -73,28 +73,28 @@ describe('disable', () => { it('should trigger the disable modal window', () => { getWrapper({ setup: async ({ actions }, { storeOptions }) => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() await unref(actions)[0].handler({ resources: [ mock({ id: '1', canDisable: () => true, driveType: 'project' }) ] }) - expect(registerModal).toHaveBeenCalledTimes(1) + expect(dispatchModal).toHaveBeenCalledTimes(1) } }) }) it('should not trigger the disable modal window without any resource', () => { getWrapper({ setup: async ({ actions }, { storeOptions }) => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() await unref(actions)[0].handler({ resources: [ mock({ id: '1', canDisable: () => false, driveType: 'project' }) ] }) - expect(registerModal).toHaveBeenCalledTimes(0) + expect(dispatchModal).toHaveBeenCalledTimes(0) } }) }) diff --git a/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsEditDescription.spec.ts b/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsEditDescription.spec.ts index f2b41718218..480d7bbed27 100644 --- a/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsEditDescription.spec.ts +++ b/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsEditDescription.spec.ts @@ -17,20 +17,20 @@ describe('editDescription', () => { it('should trigger the editDescription modal window with one resource', () => { getWrapper({ setup: async ({ actions }, { storeOptions }) => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() await unref(actions)[0].handler({ resources: [{ id: '1' } as SpaceResource] }) - expect(registerModal).toHaveBeenCalledTimes(1) + expect(dispatchModal).toHaveBeenCalledTimes(1) } }) }) it('should not trigger the editDescription modal window with no resource', () => { getWrapper({ setup: async ({ actions }, { storeOptions }) => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() await unref(actions)[0].handler({ resources: [] }) - expect(registerModal).toHaveBeenCalledTimes(0) + expect(dispatchModal).toHaveBeenCalledTimes(0) } }) }) diff --git a/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsEditQuota.spec.ts b/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsEditQuota.spec.ts index ff062adf1bd..8af3c8d5950 100644 --- a/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsEditQuota.spec.ts +++ b/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsEditQuota.spec.ts @@ -59,9 +59,9 @@ describe('editQuota', () => { it('should create a modal', () => { getWrapper({ setup: async ({ actions }) => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() await unref(actions)[0].handler({ resources: [] }) - expect(registerModal).toHaveBeenCalled() + expect(dispatchModal).toHaveBeenCalled() } }) }) diff --git a/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsEditReadmeContent.spec.ts b/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsEditReadmeContent.spec.ts index cac9abc2a81..5dacfdd08ca 100644 --- a/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsEditReadmeContent.spec.ts +++ b/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsEditReadmeContent.spec.ts @@ -77,9 +77,9 @@ describe('editReadmeContent', () => { it('creates a modal', () => { getWrapper({ setup: async ({ actions }) => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() await unref(actions)[0].handler({ resources: [mock()] }) - expect(registerModal).toHaveBeenCalled() + expect(dispatchModal).toHaveBeenCalled() } }) }) diff --git a/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsRename.spec.ts b/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsRename.spec.ts index 04191001e24..7c67ecb9232 100644 --- a/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsRename.spec.ts +++ b/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsRename.spec.ts @@ -17,22 +17,22 @@ describe('rename', () => { it('should trigger the rename modal window', () => { getWrapper({ setup: async ({ actions }, { storeOptions }) => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() await unref(actions)[0].handler({ resources: [{ id: '1', name: 'renamed space' } as SpaceResource] }) - expect(registerModal).toHaveBeenCalledTimes(1) + expect(dispatchModal).toHaveBeenCalledTimes(1) } }) }) it('should not trigger the rename modal window without any resource', () => { getWrapper({ setup: async ({ actions }, { storeOptions }) => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() await unref(actions)[0].handler({ resources: [] }) - expect(registerModal).toHaveBeenCalledTimes(0) + expect(dispatchModal).toHaveBeenCalledTimes(0) } }) }) diff --git a/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsRestore.spec.ts b/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsRestore.spec.ts index 28d0ab96fa1..58ac852d40e 100644 --- a/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsRestore.spec.ts +++ b/packages/web-pkg/tests/unit/composables/actions/spaces/useSpaceActionsRestore.spec.ts @@ -75,26 +75,26 @@ describe('restore', () => { it('should trigger the restore modal window', () => { getWrapper({ setup: async ({ actions }, { storeOptions }) => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() await unref(actions)[0].handler({ resources: [ mock({ id: '1', canRestore: () => true, driveType: 'project' }) ] }) - expect(registerModal).toHaveBeenCalledTimes(1) + expect(dispatchModal).toHaveBeenCalledTimes(1) } }) }) it('should not trigger the restore modal window without any resource', () => { getWrapper({ setup: async ({ actions }, { storeOptions }) => { - const { registerModal } = useModals() + const { dispatchModal } = useModals() await unref(actions)[0].handler({ resources: [mock({ id: '1', canRestore: () => false })] }) - expect(registerModal).toHaveBeenCalledTimes(0) + expect(dispatchModal).toHaveBeenCalledTimes(0) } }) }) diff --git a/packages/web-pkg/tests/unit/composables/piniaStores/useModals.spec.ts b/packages/web-pkg/tests/unit/composables/piniaStores/useModals.spec.ts index 902c874e3d9..120f8c15774 100644 --- a/packages/web-pkg/tests/unit/composables/piniaStores/useModals.spec.ts +++ b/packages/web-pkg/tests/unit/composables/piniaStores/useModals.spec.ts @@ -7,12 +7,12 @@ describe('useModals', () => { setActivePinia(createPinia()) }) - describe('method "registerModal"', () => { + describe('method "dispatchModal"', () => { it('registers a modal and adds id and active state', () => { getWrapper({ setup: (instance) => { const data = { title: 'test' } - const modal = instance.registerModal(data) + const modal = instance.dispatchModal(data) expect(modal.id).toBeDefined() expect(modal.active).toBeTruthy() @@ -25,10 +25,10 @@ describe('useModals', () => { it('deactivates existing modal if a new active modal is being registered', () => { getWrapper({ setup: (instance) => { - instance.registerModal({ title: 'test' }) + instance.dispatchModal({ title: 'test' }) expect(instance.modals[0].active).toBeTruthy() - instance.registerModal({ title: 'test2' }) + instance.dispatchModal({ title: 'test2' }) expect(instance.modals[0].active).toBeFalsy() } }) @@ -36,7 +36,7 @@ describe('useModals', () => { it('can register a modal in an inactive state', () => { getWrapper({ setup: (instance) => { - instance.registerModal({ title: 'test' }, { isActive: false }) + instance.dispatchModal({ title: 'test' }, { isActive: false }) expect(instance.modals[0].active).toBeFalsy() } }) @@ -46,7 +46,7 @@ describe('useModals', () => { it('updates a modal with new data', () => { getWrapper({ setup: (instance) => { - const modal = instance.registerModal({ title: 'test' }) + const modal = instance.dispatchModal({ title: 'test' }) const newTitle = 'new title' instance.updateModal(modal.id, 'title', newTitle) expect(instance.modals[0].title).toEqual(newTitle) @@ -58,7 +58,7 @@ describe('useModals', () => { it('removes an existing modal', () => { getWrapper({ setup: (instance) => { - const modal = instance.registerModal({ title: 'test' }) + const modal = instance.dispatchModal({ title: 'test' }) instance.removeModal(modal.id) expect(instance.modals.length).toBe(0) } @@ -68,8 +68,8 @@ describe('useModals', () => { it('activates another inactive modal after removing the current active one', () => { getWrapper({ setup: (instance) => { - const modal = instance.registerModal({ title: 'test' }) - instance.registerModal({ title: 'test2' }) + const modal = instance.dispatchModal({ title: 'test' }) + instance.dispatchModal({ title: 'test2' }) expect(instance.modals[0].active).toBeFalsy() instance.removeModal(modal.id) expect(instance.modals[0].active).toBeTruthy() @@ -80,8 +80,8 @@ describe('useModals', () => { it('activates a modal and deactivates another active modal if present', () => { getWrapper({ setup: (instance) => { - const modal = instance.registerModal({ title: 'test' }) - instance.registerModal({ title: 'test2' }) + const modal = instance.dispatchModal({ title: 'test' }) + instance.dispatchModal({ title: 'test2' }) expect(instance.modals[0].active).toBeFalsy() expect(instance.modals[1].active).toBeTruthy() diff --git a/packages/web-pkg/tests/unit/helpers/resource/conflictHandling/conflictDialog.spec.ts b/packages/web-pkg/tests/unit/helpers/resource/conflictHandling/conflictDialog.spec.ts index 44b7f296406..31e39cee48a 100644 --- a/packages/web-pkg/tests/unit/helpers/resource/conflictHandling/conflictDialog.spec.ts +++ b/packages/web-pkg/tests/unit/helpers/resource/conflictHandling/conflictDialog.spec.ts @@ -35,10 +35,10 @@ describe('conflict dialog', () => { describe('method "resolveFileExists"', () => { it('should create the modal in the end', () => { setActivePinia(createMockStore()) - const { registerModal } = useModals() + const { dispatchModal } = useModals() const conflictDialog = getConflictDialogInstance() conflictDialog.resolveFileExists(mockDeep(), 2, true) - expect(registerModal).toHaveBeenCalledTimes(1) + expect(dispatchModal).toHaveBeenCalledTimes(1) }) }) }) diff --git a/packages/web-runtime/src/pages/account.vue b/packages/web-runtime/src/pages/account.vue index 09e083c85a2..8021433a368 100644 --- a/packages/web-runtime/src/pages/account.vue +++ b/packages/web-runtime/src/pages/account.vue @@ -181,7 +181,7 @@ export default defineComponent({ const disableEmailNotificationsValue = ref() const viewOptionWebDavDetailsValue = ref(store.getters['Files/areWebDavDetailsShown']) const sseEnabled = useCapabilityCoreSSE() - const { registerModal } = useModals() + const { dispatchModal } = useModals() // FIXME: Use settings service capability when we have it const isSettingsServiceSupported = computed( @@ -411,7 +411,7 @@ export default defineComponent({ }) const showEditPasswordModal = () => { - registerModal({ + dispatchModal({ title: $gettext('Change password'), customComponent: EditPasswordModal }) From 0c155835ed4701825b0b6a8b13e216dc2f2764fd Mon Sep 17 00:00:00 2001 From: Jannik Stehle Date: Thu, 21 Dec 2023 14:03:20 +0100 Subject: [PATCH 3/6] refactor: remove explicit active property from modals --- .../src/composables/piniaStores/modals.ts | 28 +++-------- .../composables/piniaStores/useModals.spec.ts | 49 +++---------------- 2 files changed, 15 insertions(+), 62 deletions(-) diff --git a/packages/web-pkg/src/composables/piniaStores/modals.ts b/packages/web-pkg/src/composables/piniaStores/modals.ts index 0e1dec75b82..735391b1d8e 100644 --- a/packages/web-pkg/src/composables/piniaStores/modals.ts +++ b/packages/web-pkg/src/composables/piniaStores/modals.ts @@ -22,7 +22,6 @@ export type CustomModalComponentInstance = ComponentPublicInstance< export type Modal = { id: string - active: boolean title: string variation?: string icon?: string @@ -51,22 +50,14 @@ export type Modal = { export const useModals = defineStore('modals', () => { const modals = ref([]) - const activeModal = computed(() => unref(modals).find(({ active }) => active === true)) + const activeModal = computed(() => unref(modals).at(-1)) const getModal = (id: Modal['id']) => { return unref(modals).find((modal) => modal.id === id) } - const dispatchModal = ( - data: Omit, - { isActive = true }: { isActive?: boolean } = {} - ) => { - const modal = { ...data, id: uuidV4() as string, active: isActive } - - if (isActive && unref(activeModal)) { - unref(activeModal).active = false - } - + const dispatchModal = (data: Omit) => { + const modal = { ...data, id: uuidV4() as string } modals.value.push(modal) return modal } @@ -82,20 +73,17 @@ export const useModals = defineStore('modals', () => { const removeModal = (id: Modal['id']) => { modals.value = unref(modals).filter((modal) => modal.id !== id) - - if (unref(modals).length && !unref(activeModal)) { - // set next modal active - unref(modals)[0].active = true - } } const setModalActive = (id: Modal['id']) => { - if (unref(activeModal)) { - activeModal.value.active = false + const foundIdx = unref(modals).findIndex((modal) => modal.id === id) + if (foundIdx < 0) { + return } const modal = getModal(id) - modal.active = true + unref(modals).splice(foundIdx, 1) + modals.value.push(modal) } return { modals, activeModal, dispatchModal, updateModal, removeModal, setModalActive } diff --git a/packages/web-pkg/tests/unit/composables/piniaStores/useModals.spec.ts b/packages/web-pkg/tests/unit/composables/piniaStores/useModals.spec.ts index 120f8c15774..d5e76129322 100644 --- a/packages/web-pkg/tests/unit/composables/piniaStores/useModals.spec.ts +++ b/packages/web-pkg/tests/unit/composables/piniaStores/useModals.spec.ts @@ -8,36 +8,15 @@ describe('useModals', () => { }) describe('method "dispatchModal"', () => { - it('registers a modal and adds id and active state', () => { + it('adds a modal to the stack of modals', () => { getWrapper({ setup: (instance) => { const data = { title: 'test' } const modal = instance.dispatchModal(data) expect(modal.id).toBeDefined() - expect(modal.active).toBeTruthy() expect(modal.title).toEqual(data.title) - - expect(instance.modals[0]).toEqual(modal) - } - }) - }) - it('deactivates existing modal if a new active modal is being registered', () => { - getWrapper({ - setup: (instance) => { - instance.dispatchModal({ title: 'test' }) - expect(instance.modals[0].active).toBeTruthy() - - instance.dispatchModal({ title: 'test2' }) - expect(instance.modals[0].active).toBeFalsy() - } - }) - }) - it('can register a modal in an inactive state', () => { - getWrapper({ - setup: (instance) => { - instance.dispatchModal({ title: 'test' }, { isActive: false }) - expect(instance.modals[0].active).toBeFalsy() + expect(instance.activeModal).toEqual(modal) } }) }) @@ -49,7 +28,7 @@ describe('useModals', () => { const modal = instance.dispatchModal({ title: 'test' }) const newTitle = 'new title' instance.updateModal(modal.id, 'title', newTitle) - expect(instance.modals[0].title).toEqual(newTitle) + expect(instance.activeModal.title).toEqual(newTitle) } }) }) @@ -65,30 +44,16 @@ describe('useModals', () => { }) }) }) - it('activates another inactive modal after removing the current active one', () => { - getWrapper({ - setup: (instance) => { - const modal = instance.dispatchModal({ title: 'test' }) - instance.dispatchModal({ title: 'test2' }) - expect(instance.modals[0].active).toBeFalsy() - instance.removeModal(modal.id) - expect(instance.modals[0].active).toBeTruthy() - } - }) - }) describe('method "setModalActive"', () => { - it('activates a modal and deactivates another active modal if present', () => { + it('moves a modal to the first position of the modal stack, making it active', () => { getWrapper({ setup: (instance) => { const modal = instance.dispatchModal({ title: 'test' }) - instance.dispatchModal({ title: 'test2' }) - expect(instance.modals[0].active).toBeFalsy() - expect(instance.modals[1].active).toBeTruthy() + const modal2 = instance.dispatchModal({ title: 'test2' }) + expect(instance.activeModal.id).toEqual(modal2.id) instance.setModalActive(modal.id) - - expect(instance.modals[0].active).toBeTruthy() - expect(instance.modals[1].active).toBeFalsy() + expect(instance.activeModal.id).toEqual(modal.id) } }) }) From f03b08cdb07246a41af25307dcbc5f7f86cf5ade Mon Sep 17 00:00:00 2001 From: Jannik Stehle Date: Thu, 21 Dec 2023 14:13:36 +0100 Subject: [PATCH 4/6] style: run linter --- .../SideBar/Shares/Collaborators/ListItem.vue | 4 +++- .../src/components/AppTemplates/AppWrapper.vue | 15 +++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/web-app-files/src/components/SideBar/Shares/Collaborators/ListItem.vue b/packages/web-app-files/src/components/SideBar/Shares/Collaborators/ListItem.vue index 6860251b6bc..cfd91b5a132 100644 --- a/packages/web-app-files/src/components/SideBar/Shares/Collaborators/ListItem.vue +++ b/packages/web-app-files/src/components/SideBar/Shares/Collaborators/ListItem.vue @@ -190,7 +190,9 @@ export default defineComponent({ const { dispatchModal } = useModals() const sharedParentDir = computed(() => { - return queryItemAsString(props.sharedParentRoute?.params?.driveAliasAndItem).split('/').pop() + return queryItemAsString(props.sharedParentRoute?.params?.driveAliasAndItem) + .split('/') + .pop() }) const setDenyShare = (value) => { diff --git a/packages/web-pkg/src/components/AppTemplates/AppWrapper.vue b/packages/web-pkg/src/components/AppTemplates/AppWrapper.vue index 179701a03e4..7919dca8a00 100644 --- a/packages/web-pkg/src/components/AppTemplates/AppWrapper.vue +++ b/packages/web-pkg/src/components/AppTemplates/AppWrapper.vue @@ -368,12 +368,15 @@ export default defineComponent({ } const editorOptions = store.getters.configuration.options.editor if (editorOptions.autosaveEnabled) { - autosaveIntervalId = setInterval(async () => { - if (isDirty.value) { - await save() - autosavePopup() - } - }, (editorOptions.autosaveInterval || 120) * 1000) + autosaveIntervalId = setInterval( + async () => { + if (isDirty.value) { + await save() + autosavePopup() + } + }, + (editorOptions.autosaveInterval || 120) * 1000 + ) } }) onBeforeUnmount(() => { From 79a3f72d7d94fda6103b28f598b77c3bebd1ca5c Mon Sep 17 00:00:00 2001 From: Jannik Stehle Date: Fri, 22 Dec 2023 14:10:38 +0100 Subject: [PATCH 5/6] fix: importer modal now rendering content --- packages/web-app-importer/src/extensions.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/web-app-importer/src/extensions.ts b/packages/web-app-importer/src/extensions.ts index 39a0a9d50dc..ffdd81a2804 100644 --- a/packages/web-app-importer/src/extensions.ts +++ b/packages/web-app-importer/src/extensions.ts @@ -2,7 +2,7 @@ import { storeToRefs } from 'pinia' import { useStore, usePublicLinkContext, useThemeStore, useModals } from '@ownclouders/web-pkg' import { useGettext } from 'vue3-gettext' import { useService } from '@ownclouders/web-pkg' -import { computed, unref } from 'vue' +import { computed, nextTick, unref } from 'vue' import { Resource } from '@ownclouders/web-client/src' import type { UppyService } from '@ownclouders/web-pkg' import '@uppy/dashboard/dist/style.min.css' @@ -56,7 +56,7 @@ export const extensions = ({ applicationConfig }: ApplicationSetupOptions) => { removeUppyPlugins() }) - const handler = () => { + const handler = async () => { const renderDarkTheme = currentTheme.value.isDark dispatchModal({ @@ -67,6 +67,8 @@ export const extensions = ({ applicationConfig }: ApplicationSetupOptions) => { } }) + await nextTick() + uppyService.addPlugin(Dashboard, { uppyService, inline: true, From 9df16e8b3e3bd218598d24cb6cc2db71022081b8 Mon Sep 17 00:00:00 2001 From: Jannik Stehle Date: Fri, 22 Dec 2023 14:11:11 +0100 Subject: [PATCH 6/6] test: align modal.ts test file name with actual file name --- .../composables/piniaStores/{useModals.spec.ts => modals.spec.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/web-pkg/tests/unit/composables/piniaStores/{useModals.spec.ts => modals.spec.ts} (100%) diff --git a/packages/web-pkg/tests/unit/composables/piniaStores/useModals.spec.ts b/packages/web-pkg/tests/unit/composables/piniaStores/modals.spec.ts similarity index 100% rename from packages/web-pkg/tests/unit/composables/piniaStores/useModals.spec.ts rename to packages/web-pkg/tests/unit/composables/piniaStores/modals.spec.ts