Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions apps/files/src/components/NewNodeDialog.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*!
* 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'

vi.mock('@nextcloud/capabilities')

describe('NewNodeDialog', () => {
afterEach(cleanup)

it('reports a duplicate name using the native validation only', async () => {
const reportValidity = vi.spyOn(HTMLInputElement.prototype, 'reportValidity')
const setCustomValidity = vi.spyOn(HTMLInputElement.prototype, 'setCustomValidity')
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(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).toHaveBeenCalled()
// the message is only shown by the platform, not duplicated as helper text
expect(component.queryByText('This name is already in use.')).not.toBeInTheDocument()
expect(component.getByRole('button', { name: 'Create' })).toBeDisabled()
})

it('clears the 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(input.validity.valid).toBe(true)
expect(input.validationMessage).toBe('')
expect(component.getByRole('button', { name: 'Create' })).toBeEnabled()
})

it('reports other filename errors using the native validation only', async () => {
const reportValidity = vi.spyOn(HTMLInputElement.prototype, 'reportValidity')
const component = render(NewNodeDialog)
const input = component.getByRole('textbox', { name: 'Folder name' }) as HTMLInputElement

await fireEvent.update(input, '')

expect(input.validity.valid).toBe(false)
expect(input.validationMessage).toBe('Filename must not be empty.')
expect(reportValidity).toHaveBeenCalled()
expect(component.queryByText('Filename must not be empty.')).not.toBeInTheDocument()
expect(component.getByRole('button', { name: 'Create' })).toBeDisabled()
})

it('does not submit a duplicate name', async () => {
const component = render(NewNodeDialog, {
props: {
otherNames: ['existing.txt'],
},
})
const input = component.getByRole('textbox', { name: 'Folder name' }) as HTMLInputElement
const form = input.closest('form') as HTMLFormElement

await fireEvent.update(input, 'existing.txt')
// same code path as pressing enter within the form
form.requestSubmit()

expect(form.checkValidity()).toBe(false)
expect(component.emitted().close).toBeUndefined()
})
})
4 changes: 1 addition & 3 deletions apps/files/src/components/NewNodeDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@
ref="nameInput"
v-model="localDefaultName"
data-cy-files-new-node-dialog-input
:error="validity !== ''"
:helper-text="validity"
:label="label" />

<!-- Hidden file warning -->
Expand Down Expand Up @@ -167,7 +165,7 @@ onMounted(() => {

<style scoped>
.new-node-dialog__form {
/* Ensure the dialog does not jump when there is a validity error */
/* Keep room below the input so the native validation popup is not shown over the dialog actions */
min-height: calc(2 * var(--default-clickable-area));
}
</style>