From 11de05bc3a2f854d11d7b9bda59e7752731ca18f Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Sun, 2 Aug 2026 00:22:58 -0700 Subject: [PATCH] fix: show a single duplicate-name error during file creation Fixes #62686 --- .../src/components/NewNodeDialog.spec.ts | 65 +++++++++++++++++++ apps/files/src/components/NewNodeDialog.vue | 1 - 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 apps/files/src/components/NewNodeDialog.spec.ts diff --git a/apps/files/src/components/NewNodeDialog.spec.ts b/apps/files/src/components/NewNodeDialog.spec.ts new file mode 100644 index 0000000000000..77f59a6352f95 --- /dev/null +++ b/apps/files/src/components/NewNodeDialog.spec.ts @@ -0,0 +1,65 @@ +/*! + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { cleanup, fireEvent, render } from '@testing-library/vue' +import { afterEach, describe, expect, it, vi } from 'vitest' +import NewNodeDialog from './NewNodeDialog.vue' + +describe('NewNodeDialog', () => { + afterEach(cleanup) + + it('shows a single inline error for a duplicate name without reporting native validity', async () => { + const component = render(NewNodeDialog, { + props: { + otherNames: ['existing.txt'], + }, + }) + const input = component.getByRole('textbox', { name: 'Folder name' }) as HTMLInputElement + const reportValidity = vi.spyOn(input, 'reportValidity') + const setCustomValidity = vi.spyOn(input, 'setCustomValidity') + + await fireEvent.update(input, 'existing.txt') + + expect(component.getAllByText('This name is already in use.')).toHaveLength(1) + expect(component.getByRole('button', { name: 'Create' })).toBeDisabled() + expect(setCustomValidity).toHaveBeenLastCalledWith('This name is already in use.') + expect(input.validity.valid).toBe(false) + expect(input.validationMessage).toBe('This name is already in use.') + expect(reportValidity).not.toHaveBeenCalled() + }) + + it('clears inline and native validity when the name becomes unique', async () => { + const component = render(NewNodeDialog, { + props: { + otherNames: ['existing.txt'], + }, + }) + const input = component.getByRole('textbox', { name: 'Folder name' }) as HTMLInputElement + + await fireEvent.update(input, 'existing.txt') + expect(input.validationMessage).toBe('This name is already in use.') + + await fireEvent.update(input, 'unique.txt') + + expect(component.queryByText('This name is already in use.')).not.toBeInTheDocument() + expect(input.validity.valid).toBe(true) + expect(input.validationMessage).toBe('') + expect(component.getByRole('button', { name: 'Create' })).toBeEnabled() + }) + + it('shows other filename errors inline without reporting native validity', async () => { + const component = render(NewNodeDialog) + const input = component.getByRole('textbox', { name: 'Folder name' }) as HTMLInputElement + const reportValidity = vi.spyOn(input, 'reportValidity') + + await fireEvent.update(input, '') + + expect(component.getAllByText('Filename must not be empty.')).toHaveLength(1) + expect(component.getByRole('button', { name: 'Create' })).toBeDisabled() + expect(input.validity.valid).toBe(false) + expect(input.validationMessage).toBe('Filename must not be empty.') + expect(reportValidity).not.toHaveBeenCalled() + }) +}) diff --git a/apps/files/src/components/NewNodeDialog.vue b/apps/files/src/components/NewNodeDialog.vue index a0bc4d15ef0c7..8e5380ea96e35 100644 --- a/apps/files/src/components/NewNodeDialog.vue +++ b/apps/files/src/components/NewNodeDialog.vue @@ -147,7 +147,6 @@ watchEffect(() => { const input = nameInput.value?.$el.querySelector('input') if (input) { input.setCustomValidity(validity.value) - input.reportValidity() } })