diff --git a/core/src/components/login/LoginForm.cy.ts b/core/src/components/login/LoginForm.cy.ts deleted file mode 100644 index 1b1aeda6306..00000000000 --- a/core/src/components/login/LoginForm.cy.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -import LoginForm from './LoginForm.vue' - -describe('core: LoginForm', { testIsolation: true }, () => { - beforeEach(() => { - // Mock the required global state - cy.window().then(($window) => { - $window.OC = { - theme: { - name: 'J\'s cloud', - }, - requestToken: 'request-token', - } - }) - }) - - /** - * Ensure that characters like ' are not double HTML escaped. - * This was a bug in https://github.com/nextcloud/server/issues/34990 - */ - it('does not double escape special characters in product name', () => { - cy.mount(LoginForm, { - propsData: { - username: 'test-user', - }, - }) - - cy.get('h2').contains('J\'s cloud') - }) - - it('fills username from props into form', () => { - cy.mount(LoginForm, { - propsData: { - username: 'test-user', - }, - }) - - cy.get('input[name="user"]') - .should('exist') - .and('have.attr', 'id', 'user') - - cy.get('input[name="user"]') - .should('have.value', 'test-user') - }) - - it('clears password after timeout', () => { - // mock timeout of 5 seconds - cy.window().then(($window) => { - const state = $window.document.createElement('input') - state.type = 'hidden' - state.id = 'initial-state-core-loginTimeout' - state.value = btoa(JSON.stringify(5)) - $window.document.body.appendChild(state) - }) - - // mount forms - cy.mount(LoginForm) - - cy.get('input[name="password"]') - .should('exist') - .type('MyPassword') - - cy.get('input[name="password"]') - .should('have.value', 'MyPassword') - - // Wait for timeout - // eslint-disable-next-line cypress/no-unnecessary-waiting - cy.wait(5100) - - cy.get('input[name="password"]') - .should('have.value', '') - }) -}) diff --git a/core/src/components/login/LoginForm.vue b/core/src/components/login/LoginForm.vue index 962e7d5a8e2..546190b8c7d 100644 --- a/core/src/components/login/LoginForm.vue +++ b/core/src/components/login/LoginForm.vue @@ -194,10 +194,10 @@ export default { } }, - data() { + data(props) { return { loading: false, - user: '', + user: props.username, password: '', } }, @@ -262,7 +262,7 @@ export default { }, emailEnabled() { - return this.emailStates ? this.emailStates.every((state) => state === '1') : 1 + return this.emailStates.every((state) => state === '1') }, loginText() { @@ -286,7 +286,6 @@ export default { if (this.username === '') { this.$refs.user.$refs.inputField.$refs.input.focus() } else { - this.user = this.username this.$refs.password.$refs.inputField.$refs.input.focus() } }, diff --git a/core/src/tests/components/Login/LoginForm.spec.ts b/core/src/tests/components/Login/LoginForm.spec.ts new file mode 100644 index 00000000000..105bda60634 --- /dev/null +++ b/core/src/tests/components/Login/LoginForm.spec.ts @@ -0,0 +1,102 @@ +/*! + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { cleanup, render } from '@testing-library/vue' +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' +import LoginForm from '../../../components/login/LoginForm.vue' + +describe('core: LoginForm', () => { + afterEach(cleanup) + + beforeEach(() => { + // Mock the required global state + window.OC = { + theme: { + name: 'J\'s cloud', + }, + requestToken: 'request-token', + } + }) + + /** + * Ensure that characters like ' are not double HTML escaped. + * This was a bug in https://github.com/nextcloud/server/issues/34990 + */ + it('does not double escape special characters in product name', () => { + const page = render(LoginForm, { + props: { + username: 'test-user', + }, + }) + + const heading = page.getByRole('heading', { level: 2 }) + expect(heading.textContent).toContain('J\'s cloud') + }) + + it('offers email as login name by default', async () => { + const page = render(LoginForm) + + const input = await page.findByRole('textbox', { name: /Account name or email/ }) + expect(input).toBeInstanceOf(HTMLInputElement) + }) + + it('offers only account name if email is not enabled', async () => { + const page = render(LoginForm, { + propsData: { + emailStates: ['0', '1'], + }, + }) + + await expect(async () => page.findByRole('textbox', { name: /Account name or email/ })).rejects.toThrow() + await expect(page.findByRole('textbox', { name: /Account name/ })).resolves.not.toThrow() + }) + + it('fills username from props into form', () => { + const page = render(LoginForm, { + props: { + username: 'test-user', + }, + }) + + page.debug() + + const input: HTMLInputElement = page.getByRole('textbox', { name: /Account name or email/ }) + expect(input.id).toBe('user') + expect(input.name).toBe('user') + expect(input.value).toBe('test-user') + }) + + describe('', () => { + beforeAll(() => { + vi.useFakeTimers() + // mock timeout of 5 seconds + const state = document.createElement('input') + state.type = 'hidden' + state.id = 'initial-state-core-loginTimeout' + state.value = btoa(JSON.stringify(5)) + document.body.appendChild(state) + }) + + afterAll(() => { + vi.useRealTimers() + document.querySelector('#initial-state-core-loginTimeout')?.remove() + }) + + it('clears password after timeout', () => { + // mount forms + const page = render(LoginForm) + const input: HTMLInputElement = page.getByLabelText('Password', { selector: 'input' }) + input.dispatchEvent(new InputEvent('input', { data: 'MyPassword' })) + + vi.advanceTimersByTime(2500) + // see its still the value + expect(input.value).toBe('') + + // Wait for timeout + vi.advanceTimersByTime(2600) + expect(input.value).toBe('') + }) + }) +}) diff --git a/eslint.config.mjs b/eslint.config.mjs index 95f309bb331..1261a9738a7 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -63,6 +63,8 @@ export default defineConfig([ rules: { 'no-console': 'off', 'jsdoc/require-jsdoc': 'off', + 'jsdoc/require-param-type': 'off', + 'jsdoc/require-param-description': 'off', '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-unused-expressions': 'off', }, diff --git a/package-lock.json b/package-lock.json index e92359f2ec8..3b05eb9487c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -104,7 +104,7 @@ "@testing-library/cypress": "^10.1.0", "@testing-library/jest-dom": "^6.6.4", "@testing-library/user-event": "^14.6.1", - "@testing-library/vue": "^5.8.3", + "@testing-library/vue": "^5.9.0", "@types/dockerode": "^3.3.44", "@types/wait-on": "^5.3.4", "@vitejs/plugin-vue2": "^2.3.3", diff --git a/package.json b/package.json index f738844cd0b..0c8230fc25b 100644 --- a/package.json +++ b/package.json @@ -141,7 +141,7 @@ "@testing-library/cypress": "^10.1.0", "@testing-library/jest-dom": "^6.6.4", "@testing-library/user-event": "^14.6.1", - "@testing-library/vue": "^5.8.3", + "@testing-library/vue": "^5.9.0", "@types/dockerode": "^3.3.44", "@types/wait-on": "^5.3.4", "@vitejs/plugin-vue2": "^2.3.3",