refactor(settings): switch wipe dialog to confirm

Signed-off-by: Peter Ringelmann <peter.ringelmann@nextcloud.com>
This commit is contained in:
Peter Ringelmann 2026-05-26 11:30:05 +02:00 committed by Peter R.
parent 1d035b4cfc
commit bbdb7d01b8
3 changed files with 27 additions and 83 deletions

View file

@ -16,10 +16,16 @@ vi.hoisted(() => {
import type { IToken } from '../store/authtoken.ts'
// Mock @nextcloud/dialogs so the wipe action's showConfirmation call resolves
// synchronously in tests. Hoisted so it's installed before AuthToken.vue imports.
const showConfirmationMock = vi.hoisted(() => vi.fn())
vi.mock('@nextcloud/dialogs', () => ({
showConfirmation: showConfirmationMock,
}))
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
import AuthToken from './AuthToken.vue'
import AuthTokenDeleteDialog from './AuthTokenDeleteDialog.vue'
import AuthTokenWipeDialog from './AuthTokenWipeDialog.vue'
import { TokenType, useAuthTokenStore } from '../store/authtoken.ts'
import { detect } from '../utils/userAgentDetect.ts'
@ -143,33 +149,27 @@ describe('AuthToken wipe flow', () => {
vi.clearAllMocks()
})
it('does not call wipeToken when the wipe action is triggered (dialog opens first)', async () => {
it('does not call wipeToken when the user rejects the confirmation', async () => {
showConfirmationMock.mockResolvedValueOnce(false)
const token = makeToken()
const wrapper = mountAuthToken(token)
const store = useAuthTokenStore()
;(wrapper.vm as unknown as { wipe: () => void }).wipe()
await wrapper.vm.$nextTick()
await (wrapper.vm as unknown as { wipe: () => Promise<void> }).wipe()
const dialog = wrapper.findComponent(AuthTokenWipeDialog)
expect(dialog.exists()).toBe(true)
expect(dialog.props('open')).toBe(true)
expect(showConfirmationMock).toHaveBeenCalledTimes(1)
expect(store.wipeToken).not.toHaveBeenCalled()
})
it('calls wipeToken only after the dialog emits confirm', async () => {
it('calls wipeToken when the user accepts the confirmation', async () => {
showConfirmationMock.mockResolvedValueOnce(true)
const token = makeToken()
const wrapper = mountAuthToken(token)
const store = useAuthTokenStore()
;(wrapper.vm as unknown as { wipe: () => void }).wipe()
await wrapper.vm.$nextTick()
const dialog = wrapper.findComponent(AuthTokenWipeDialog)
dialog.vm.$emit('confirm')
dialog.vm.$emit('update:open', false)
await wrapper.vm.$nextTick()
await (wrapper.vm as unknown as { wipe: () => Promise<void> }).wipe()
expect(showConfirmationMock).toHaveBeenCalledTimes(1)
expect(store.wipeToken).toHaveBeenCalledTimes(1)
expect(store.wipeToken).toHaveBeenCalledWith(token)
})

View file

@ -87,11 +87,6 @@
:token="token"
:open.sync="deleteDialogOpen"
@confirm="confirmDelete" />
<AuthTokenWipeDialog
v-if="wipeDialogOpen"
:token="token"
:open.sync="wipeDialogOpen"
@confirm="confirmWipe" />
</tr>
</template>
@ -100,6 +95,7 @@ import type { PropType } from 'vue'
import type { IToken } from '../store/authtoken.ts'
import { mdiAndroid, mdiAppleIos, mdiAppleSafari, mdiCellphone, mdiCheck, mdiFirefox, mdiGoogleChrome, mdiKeyOutline, mdiMicrosoftEdge, mdiMonitor, mdiTablet, mdiWeb } from '@mdi/js'
import { showConfirmation } from '@nextcloud/dialogs'
import { translate as t } from '@nextcloud/l10n'
import { defineComponent } from 'vue'
import NcActionButton from '@nextcloud/vue/components/NcActionButton'
@ -110,7 +106,6 @@ import NcDateTime from '@nextcloud/vue/components/NcDateTime'
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
import NcTextField from '@nextcloud/vue/components/NcTextField'
import AuthTokenDeleteDialog from './AuthTokenDeleteDialog.vue'
import AuthTokenWipeDialog from './AuthTokenWipeDialog.vue'
import { TokenType, useAuthTokenStore } from '../store/authtoken.ts'
import { detect } from '../utils/userAgentDetect.ts'
@ -137,7 +132,6 @@ export default defineComponent({
name: 'AuthToken',
components: {
AuthTokenDeleteDialog,
AuthTokenWipeDialog,
NcActions,
NcActionButton,
NcActionCheckbox,
@ -166,7 +160,6 @@ export default defineComponent({
newName: '',
oldName: '',
deleteDialogOpen: false,
wipeDialogOpen: false,
mdiCheck,
TokenType,
}
@ -306,13 +299,18 @@ export default defineComponent({
this.authTokenStore.renameToken(this.token, this.newName)
},
wipe() {
async wipe() {
this.actionOpen = false
this.wipeDialogOpen = true
},
confirmWipe() {
this.authTokenStore.wipeToken(this.token)
const confirmed = await showConfirmation({
name: t('settings', 'Confirm wipe'),
text: t('settings', 'Do you really want to wipe your data from this device?'),
labelConfirm: t('settings', 'Wipe device'),
labelReject: t('settings', 'Cancel'),
severity: 'warning',
})
if (confirmed) {
this.authTokenStore.wipeToken(this.token)
}
},
},
})

View file

@ -1,54 +0,0 @@
<!--
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<script setup lang="ts">
import type { IDialogButton } from '@nextcloud/dialogs'
import type { IToken } from '../store/authtoken.ts'
import { translate as t } from '@nextcloud/l10n'
import { computed } from 'vue'
import NcDialog from '@nextcloud/vue/components/NcDialog'
defineProps<{
/** The token to wipe. Kept for prop-shape parity with AuthTokenDeleteDialog. */
token: IToken
/** Whether the dialog is open */
open: boolean
}>()
const emit = defineEmits<{
'update:open': [open: boolean]
confirm: []
}>()
const buttons = computed<IDialogButton[]>(() => [
{
label: t('settings', 'Cancel'),
variant: 'tertiary',
callback: () => emit('update:open', false),
},
{
label: t('settings', 'Wipe device'),
variant: 'error',
callback: () => {
emit('confirm')
emit('update:open', false)
},
},
])
</script>
<template>
<NcDialog
:open="open"
:name="t('settings', 'Confirm wipe')"
:buttons="buttons"
size="normal"
@update:open="emit('update:open', $event)">
<p>
{{ t('settings', 'Do you really want to wipe your data from this device?') }}
</p>
</NcDialog>
</template>