diff options
Diffstat (limited to 'cypress/e2e/theming')
-rw-r--r-- | cypress/e2e/theming/a11y-color-contrast.cy.ts | 10 | ||||
-rw-r--r-- | cypress/e2e/theming/admin-settings.cy.ts | 360 | ||||
-rw-r--r-- | cypress/e2e/theming/admin-settings_default-app.cy.ts | 91 | ||||
-rw-r--r-- | cypress/e2e/theming/admin-settings_urls.cy.ts | 143 | ||||
-rw-r--r-- | cypress/e2e/theming/themingUtils.ts | 93 | ||||
-rw-r--r-- | cypress/e2e/theming/user-settings_app-order.cy.ts (renamed from cypress/e2e/theming/navigation-bar-settings.cy.ts) | 228 | ||||
-rw-r--r-- | cypress/e2e/theming/user-settings_background.cy.ts (renamed from cypress/e2e/theming/user-background.cy.ts) | 117 |
7 files changed, 668 insertions, 374 deletions
diff --git a/cypress/e2e/theming/a11y-color-contrast.cy.ts b/cypress/e2e/theming/a11y-color-contrast.cy.ts index 03a6814ea1f..bff7df28e8e 100644 --- a/cypress/e2e/theming/a11y-color-contrast.cy.ts +++ b/cypress/e2e/theming/a11y-color-contrast.cy.ts @@ -1,3 +1,7 @@ +/** + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ const themesToTest = ['light', 'dark', 'light-highcontrast', 'dark-highcontrast'] const testCases = { @@ -106,7 +110,7 @@ describe('Accessibility of Nextcloud theming colors', () => { before(() => { cy.createRandomUser().then(($user) => { // set user theme - cy.runOccCommand(`user:setting -- '${$user.userId}' theming enabled-themes '["${theme}"]'`) + cy.runOccCommand(`user:setting -- '${$user.userId}' theming enabled-themes '[\\"${theme}\\"]'`) cy.login($user) cy.visit('/') cy.injectAxe({ axeCorePath: 'node_modules/axe-core/axe.min.js' }) @@ -118,7 +122,7 @@ describe('Accessibility of Nextcloud theming colors', () => { // Unset background image and thus use background-color for testing blur background (images do not work with axe-core) doc.body.style.backgroundImage = 'unset' - const root = doc.querySelector('main') + const root = doc.querySelector('#content') if (root === null) { throw new Error('No test root found') } @@ -133,7 +137,7 @@ describe('Accessibility of Nextcloud theming colors', () => { it(`color contrast of ${foreground} on ${background}`, () => { cy.document().then(doc => { const element = createTestCase(foreground, background) - const root = doc.querySelector('main') + const root = doc.querySelector('#content') // eslint-disable-next-line no-unused-expressions expect(root).not.to.be.undefined // eslint-disable-next-line @typescript-eslint/no-non-null-assertion diff --git a/cypress/e2e/theming/admin-settings.cy.ts b/cypress/e2e/theming/admin-settings.cy.ts index 1c4e3458aae..4207b98f711 100644 --- a/cypress/e2e/theming/admin-settings.cy.ts +++ b/cypress/e2e/theming/admin-settings.cy.ts @@ -1,29 +1,19 @@ /** - * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com> - * - * @author John Molakvoæ <skjnldsv@protonmail.com> - * - * @license AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ /* eslint-disable n/no-unpublished-import */ import { User } from '@nextcloud/cypress' -import { colord } from 'colord' -import { defaultPrimary, defaultBackground, pickRandomColor, validateBodyThemingCss, validateUserThemingDefaultCss } from './themingUtils' +import { + defaultPrimary, + defaultBackground, + pickRandomColor, + validateBodyThemingCss, + validateUserThemingDefaultCss, + expectBackgroundColor, +} from './themingUtils' +import { NavigationHeader } from '../../pages/NavigationHeader' const admin = new User('admin', 'admin') @@ -36,15 +26,24 @@ describe('Admin theming settings visibility check', function() { it('See the admin theming section', function() { cy.visit('/settings/admin/theming') - cy.get('[data-admin-theming-settings]').should('exist').scrollIntoView() + cy.get('[data-admin-theming-settings]') + .should('exist') + .scrollIntoView() cy.get('[data-admin-theming-settings]').should('be.visible') }) it('See the default settings', function() { - cy.get('[data-admin-theming-setting-primary-color-picker]').should('exist') - cy.get('[data-admin-theming-setting-primary-color-reset]').should('not.exist') + cy.get('[data-admin-theming-setting-color-picker]').should('exist') cy.get('[data-admin-theming-setting-file-reset]').should('not.exist') - cy.get('[data-admin-theming-setting-file-remove]').should('be.visible') + cy.get('[data-admin-theming-setting-file-remove]').should('exist') + + cy.get( + '[data-admin-theming-setting-primary-color] [data-admin-theming-setting-color]', + ).then(($el) => expectBackgroundColor($el, defaultPrimary)) + + cy.get( + '[data-admin-theming-setting-background-color] [data-admin-theming-setting-color]', + ).then(($el) => expectBackgroundColor($el, defaultPrimary)) }) }) @@ -59,24 +58,42 @@ describe('Change the primary color and reset it', function() { it('See the admin theming section', function() { cy.visit('/settings/admin/theming') - cy.get('[data-admin-theming-settings]').should('exist').scrollIntoView() + cy.get('[data-admin-theming-settings]') + .should('exist') + .scrollIntoView() cy.get('[data-admin-theming-settings]').should('be.visible') }) it('Change the primary color', function() { cy.intercept('*/apps/theming/ajax/updateStylesheet').as('setColor') - pickRandomColor().then(color => { selectedColor = color }) + pickRandomColor('[data-admin-theming-setting-primary-color]').then( + (color) => { + selectedColor = color + }, + ) cy.wait('@setColor') - cy.waitUntil(() => validateBodyThemingCss(selectedColor, defaultBackground)) + cy.waitUntil(() => + validateBodyThemingCss( + selectedColor, + defaultBackground, + defaultPrimary, + ), + ) }) it('Screenshot the login page and validate login page', function() { cy.logout() cy.visit('/') - cy.waitUntil(() => validateBodyThemingCss(selectedColor, defaultBackground)) + cy.waitUntil(() => + validateBodyThemingCss( + selectedColor, + defaultBackground, + defaultPrimary, + ), + ) cy.screenshot() }) @@ -98,21 +115,29 @@ describe('Remove the default background and restore it', function() { it('See the admin theming section', function() { cy.visit('/settings/admin/theming') - cy.get('[data-admin-theming-settings]').should('exist').scrollIntoView() + cy.get('[data-admin-theming-settings]') + .should('exist') + .scrollIntoView() cy.get('[data-admin-theming-settings]').should('be.visible') }) it('Remove the default background', function() { - cy.intercept('*/apps/theming/ajax/updateStylesheet').as('removeBackground') + cy.intercept('*/apps/theming/ajax/updateStylesheet').as( + 'removeBackground', + ) cy.get('[data-admin-theming-setting-file-remove]').click() cy.wait('@removeBackground') cy.waitUntil(() => validateBodyThemingCss(defaultPrimary, null)) - cy.waitUntil(() => cy.window().then((win) => { - const backgroundPlain = getComputedStyle(win.document.body).getPropertyValue('--image-background-plain') - return backgroundPlain !== '' - })) + cy.waitUntil(() => + cy.window().then((win) => { + const backgroundPlain = getComputedStyle( + win.document.body, + ).getPropertyValue('--image-background') + return backgroundPlain !== '' + }), + ) }) it('Screenshot the login page and validate login page', function() { @@ -132,7 +157,7 @@ describe('Remove the default background and restore it', function() { }) }) -describe('Remove the default background with a custom primary color', function() { +describe('Remove the default background with a custom background color', function() { let selectedColor = '' before(function() { @@ -143,23 +168,40 @@ describe('Remove the default background with a custom primary color', function() it('See the admin theming section', function() { cy.visit('/settings/admin/theming') - cy.get('[data-admin-theming-settings]').should('exist').scrollIntoView() + cy.get('[data-admin-theming-settings]') + .should('exist') + .scrollIntoView() cy.get('[data-admin-theming-settings]').should('be.visible') }) - it('Change the primary color', function() { + it('Change the background color', function() { cy.intercept('*/apps/theming/ajax/updateStylesheet').as('setColor') - pickRandomColor().then(color => { selectedColor = color }) + pickRandomColor('[data-admin-theming-setting-background-color]').then( + (color) => { + selectedColor = color + }, + ) cy.wait('@setColor') - cy.waitUntil(() => validateBodyThemingCss(selectedColor, defaultBackground)) + cy.waitUntil(() => + validateBodyThemingCss( + defaultPrimary, + defaultBackground, + selectedColor, + ), + ) }) it('Remove the default background', function() { - cy.intercept('*/apps/theming/ajax/updateStylesheet').as('removeBackground') + cy.intercept('*/apps/theming/ajax/updateStylesheet').as( + 'removeBackground', + ) - cy.get('[data-admin-theming-setting-file-remove]').click() + cy.get('[data-admin-theming-setting-file-remove]').scrollIntoView() + cy.get('[data-admin-theming-setting-file-remove]').click({ + force: true, + }) cy.wait('@removeBackground') }) @@ -168,7 +210,9 @@ describe('Remove the default background with a custom primary color', function() cy.logout() cy.visit('/') - cy.waitUntil(() => validateBodyThemingCss(selectedColor, null)) + cy.waitUntil(() => + validateBodyThemingCss(defaultPrimary, null, selectedColor), + ) cy.screenshot() }) @@ -182,6 +226,9 @@ describe('Remove the default background with a custom primary color', function() }) describe('Remove the default background with a bright color', function() { + const navigationHeader = new NavigationHeader() + let selectedColor = '' + before(function() { // Just in case previous test failed cy.resetAdminTheming() @@ -191,37 +238,52 @@ describe('Remove the default background with a bright color', function() { it('See the admin theming section', function() { cy.visit('/settings/admin/theming') - cy.get('[data-admin-theming-settings]').should('exist').scrollIntoView() + cy.get('[data-admin-theming-settings]') + .should('exist') + .scrollIntoView() cy.get('[data-admin-theming-settings]').should('be.visible') }) it('Remove the default background', function() { - cy.intercept('*/apps/theming/ajax/updateStylesheet').as('removeBackground') + cy.intercept('*/apps/theming/ajax/updateStylesheet').as( + 'removeBackground', + ) cy.get('[data-admin-theming-setting-file-remove]').click() cy.wait('@removeBackground') }) - it('Change the primary color', function() { + it('Change the background color', function() { cy.intercept('*/apps/theming/ajax/updateStylesheet').as('setColor') // Pick one of the bright color preset - cy.get('[data-admin-theming-setting-primary-color-picker]').click() - cy.get('.color-picker__simple-color-circle:eq(4)').click() + pickRandomColor( + '[data-admin-theming-setting-background-color]', + 4, + ).then((color) => { + selectedColor = color + }) cy.wait('@setColor') - cy.waitUntil(() => validateBodyThemingCss('#ddcb55', null)) + cy.waitUntil(() => + validateBodyThemingCss(defaultPrimary, null, selectedColor), + ) }) it('See the header being inverted', function() { - cy.waitUntil(() => cy.window().then((win) => { - const firstEntry = win.document.querySelector('.app-menu-main li img') - if (!firstEntry) { - return false - } - return getComputedStyle(firstEntry).filter === 'invert(1)' - })) + cy.waitUntil(() => + navigationHeader + .getNavigationEntries() + .find('img') + .then((el) => { + let ret = true + el.each(function() { + ret = ret && window.getComputedStyle(this).filter === 'invert(1)' + }) + return ret + }) + ) }) }) @@ -238,7 +300,9 @@ describe('Change the login fields then reset them', function() { it('See the admin theming section', function() { cy.visit('/settings/admin/theming') - cy.get('[data-admin-theming-settings]').should('exist').scrollIntoView() + cy.get('[data-admin-theming-settings]') + .should('exist') + .scrollIntoView() cy.get('[data-admin-theming-settings]').should('be.visible') }) @@ -246,42 +310,54 @@ describe('Change the login fields then reset them', function() { cy.intercept('*/apps/theming/ajax/updateStylesheet').as('updateFields') // Name - cy.get('[data-admin-theming-setting-field="name"] input[type="text"]') - .scrollIntoView() - cy.get('[data-admin-theming-setting-field="name"] input[type="text"]') - .type(`{selectall}${name}{enter}`) + cy.get( + '[data-admin-theming-setting-field="name"] input[type="text"]', + ).scrollIntoView() + cy.get( + '[data-admin-theming-setting-field="name"] input[type="text"]', + ).type(`{selectall}${name}{enter}`) cy.wait('@updateFields') // Url - cy.get('[data-admin-theming-setting-field="url"] input[type="url"]') - .scrollIntoView() - cy.get('[data-admin-theming-setting-field="url"] input[type="url"]') - .type(`{selectall}${url}{enter}`) + cy.get( + '[data-admin-theming-setting-field="url"] input[type="url"]', + ).scrollIntoView() + cy.get( + '[data-admin-theming-setting-field="url"] input[type="url"]', + ).type(`{selectall}${url}{enter}`) cy.wait('@updateFields') // Slogan - cy.get('[data-admin-theming-setting-field="slogan"] input[type="text"]') - .scrollIntoView() - cy.get('[data-admin-theming-setting-field="slogan"] input[type="text"]') - .type(`{selectall}${slogan}{enter}`) + cy.get( + '[data-admin-theming-setting-field="slogan"] input[type="text"]', + ).scrollIntoView() + cy.get( + '[data-admin-theming-setting-field="slogan"] input[type="text"]', + ).type(`{selectall}${slogan}{enter}`) cy.wait('@updateFields') }) it('Ensure undo button presence', function() { - cy.get('[data-admin-theming-setting-field="name"] .input-field__trailing-button') - .scrollIntoView() - cy.get('[data-admin-theming-setting-field="name"] .input-field__trailing-button') - .should('be.visible') - - cy.get('[data-admin-theming-setting-field="url"] .input-field__trailing-button') - .scrollIntoView() - cy.get('[data-admin-theming-setting-field="url"] .input-field__trailing-button') - .should('be.visible') - - cy.get('[data-admin-theming-setting-field="slogan"] .input-field__trailing-button') - .scrollIntoView() - cy.get('[data-admin-theming-setting-field="slogan"] .input-field__trailing-button') - .should('be.visible') + cy.get( + '[data-admin-theming-setting-field="name"] .input-field__trailing-button', + ).scrollIntoView() + cy.get( + '[data-admin-theming-setting-field="name"] .input-field__trailing-button', + ).should('be.visible') + + cy.get( + '[data-admin-theming-setting-field="url"] .input-field__trailing-button', + ).scrollIntoView() + cy.get( + '[data-admin-theming-setting-field="url"] .input-field__trailing-button', + ).should('be.visible') + + cy.get( + '[data-admin-theming-setting-field="slogan"] .input-field__trailing-button', + ).scrollIntoView() + cy.get( + '[data-admin-theming-setting-field="slogan"] .input-field__trailing-button', + ).should('be.visible') }) it('Validate login screen changes', function() { @@ -317,19 +393,29 @@ describe('Disable user theming and enable it back', function() { it('See the admin theming section', function() { cy.visit('/settings/admin/theming') - cy.get('[data-admin-theming-settings]').should('exist').scrollIntoView() + cy.get('[data-admin-theming-settings]') + .should('exist') + .scrollIntoView() cy.get('[data-admin-theming-settings]').should('be.visible') }) it('Disable user background theming', function() { - cy.intercept('*/apps/theming/ajax/updateStylesheet').as('disableUserTheming') - - cy.get('[data-admin-theming-setting-disable-user-theming]') - .scrollIntoView() - cy.get('[data-admin-theming-setting-disable-user-theming]') - .should('be.visible') - cy.get('[data-admin-theming-setting-disable-user-theming] input[type="checkbox"]').check({ force: true }) - cy.get('[data-admin-theming-setting-disable-user-theming] input[type="checkbox"]').should('be.checked') + cy.intercept('*/apps/theming/ajax/updateStylesheet').as( + 'disableUserTheming', + ) + + cy.get( + '[data-admin-theming-setting-disable-user-theming]', + ).scrollIntoView() + cy.get('[data-admin-theming-setting-disable-user-theming]').should( + 'be.visible', + ) + cy.get( + '[data-admin-theming-setting-disable-user-theming] input[type="checkbox"]', + ).check({ force: true }) + cy.get( + '[data-admin-theming-setting-disable-user-theming] input[type="checkbox"]', + ).should('be.checked') cy.wait('@disableUserTheming') }) @@ -343,8 +429,9 @@ describe('Disable user theming and enable it back', function() { it('User cannot not change background settings', function() { cy.visit('/settings/user/theming') - cy.get('[data-user-theming-background-disabled]').scrollIntoView() - cy.get('[data-user-theming-background-disabled]').should('be.visible') + cy.contains( + 'Customization has been disabled by your administrator', + ).should('exist') }) }) @@ -363,40 +450,60 @@ describe('The user default background settings reflect the admin theming setting it('See the admin theming section', function() { cy.visit('/settings/admin/theming') - cy.get('[data-admin-theming-settings]').should('exist').scrollIntoView() + cy.get('[data-admin-theming-settings]') + .should('exist') + .scrollIntoView() cy.get('[data-admin-theming-settings]').should('be.visible') }) - it('Change the primary color', function() { - cy.intercept('*/apps/theming/ajax/updateStylesheet').as('setColor') - - pickRandomColor().then(color => { selectedColor = color }) - - cy.wait('@setColor') - cy.waitUntil(() => cy.window().then(($window) => { - const primary = $window.getComputedStyle($window.document.body).getPropertyValue('--color-primary-default') - return colord(primary).isEqual(selectedColor) - })) - }) - it('Change the default background', function() { cy.intercept('*/apps/theming/ajax/uploadImage').as('setBackground') cy.fixture('image.jpg', null).as('background') - cy.get('[data-admin-theming-setting-file="background"] input[type="file"]').selectFile('@background', { force: true }) + cy.get( + '[data-admin-theming-setting-file="background"] input[type="file"]', + ).selectFile('@background', { force: true }) cy.wait('@setBackground') - cy.waitUntil(() => cy.window().then((win) => { - const currentBackgroundDefault = getComputedStyle(win.document.body).getPropertyValue('--image-background-default') - return currentBackgroundDefault.includes('/apps/theming/image/background?v=') - })) + cy.waitUntil(() => + validateBodyThemingCss( + defaultPrimary, + '/apps/theming/image/background?v=', + null, + ), + ) + }) + + it('Change the background color', function() { + cy.intercept('*/apps/theming/ajax/updateStylesheet').as('setColor') + + pickRandomColor('[data-admin-theming-setting-background-color]').then( + (color) => { + selectedColor = color + }, + ) + + cy.wait('@setColor') + cy.waitUntil(() => + validateBodyThemingCss( + defaultPrimary, + '/apps/theming/image/background?v=', + selectedColor, + ), + ) }) it('Login page should match admin theming settings', function() { cy.logout() cy.visit('/') - cy.waitUntil(() => validateBodyThemingCss(selectedColor, '/apps/theming/image/background?v=')) + cy.waitUntil(() => + validateBodyThemingCss( + defaultPrimary, + '/apps/theming/image/background?v=', + selectedColor, + ), + ) }) it('Login as user', function() { @@ -413,9 +520,17 @@ describe('The user default background settings reflect the admin theming setting it('Default user background settings should match admin theming settings', function() { cy.get('[data-user-theming-background-default]').should('be.visible') - cy.get('[data-user-theming-background-default]').should('have.class', 'background--active') - - cy.waitUntil(() => validateUserThemingDefaultCss(selectedColor, '/apps/theming/image/background?v=')) + cy.get('[data-user-theming-background-default]').should( + 'have.class', + 'background--active', + ) + + cy.waitUntil(() => + validateUserThemingDefaultCss( + selectedColor, + '/apps/theming/image/background?v=', + ), + ) }) }) @@ -432,12 +547,16 @@ describe('The user default background settings reflect the admin theming setting it('See the admin theming section', function() { cy.visit('/settings/admin/theming') - cy.get('[data-admin-theming-settings]').should('exist').scrollIntoView() + cy.get('[data-admin-theming-settings]') + .should('exist') + .scrollIntoView() cy.get('[data-admin-theming-settings]').should('be.visible') }) it('Remove the default background', function() { - cy.intercept('*/apps/theming/ajax/updateStylesheet').as('removeBackground') + cy.intercept('*/apps/theming/ajax/updateStylesheet').as( + 'removeBackground', + ) cy.get('[data-admin-theming-setting-file-remove]').click() @@ -466,7 +585,10 @@ describe('The user default background settings reflect the admin theming setting it('Default user background settings should match admin theming settings', function() { cy.get('[data-user-theming-background-default]').should('be.visible') - cy.get('[data-user-theming-background-default]').should('have.class', 'background--active') + cy.get('[data-user-theming-background-default]').should( + 'have.class', + 'background--active', + ) cy.waitUntil(() => validateUserThemingDefaultCss(defaultPrimary, null)) }) diff --git a/cypress/e2e/theming/admin-settings_default-app.cy.ts b/cypress/e2e/theming/admin-settings_default-app.cy.ts new file mode 100644 index 00000000000..702f737bc15 --- /dev/null +++ b/cypress/e2e/theming/admin-settings_default-app.cy.ts @@ -0,0 +1,91 @@ +/** + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { User } from '@nextcloud/cypress' +import { NavigationHeader } from '../../pages/NavigationHeader' + +const admin = new User('admin', 'admin') + +describe('Admin theming set default apps', () => { + const navigationHeader = new NavigationHeader() + + before(function() { + // Just in case previous test failed + cy.resetAdminTheming() + cy.login(admin) + }) + + it('See the current default app is the dashboard', () => { + // check default route + cy.visit('/') + cy.url().should('match', /apps\/dashboard/) + + // Also check the top logo link + navigationHeader.logo().click() + cy.url().should('match', /apps\/dashboard/) + }) + + it('See the default app settings', () => { + cy.visit('/settings/admin/theming') + + cy.get('.settings-section').contains('Navigation bar settings').should('exist') + cy.get('[data-cy-switch-default-app]').should('exist') + cy.get('[data-cy-switch-default-app]').scrollIntoView() + }) + + it('Toggle the "use custom default app" switch', () => { + cy.get('[data-cy-switch-default-app] input').should('not.be.checked') + cy.get('[data-cy-switch-default-app] .checkbox-content').click() + cy.get('[data-cy-switch-default-app] input').should('be.checked') + }) + + it('See the default app order selector', () => { + cy.get('[data-cy-app-order] [data-cy-app-order-element]').then(elements => { + const appIDs = elements.map((idx, el) => el.getAttribute('data-cy-app-order-element')).get() + expect(appIDs).to.deep.eq(['dashboard', 'files']) + }) + }) + + it('Change the default app', () => { + cy.get('[data-cy-app-order] [data-cy-app-order-element="files"]').scrollIntoView() + + cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="up"]').should('be.visible') + cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="up"]').click() + cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="up"]').should('not.be.visible') + + }) + + it('See the default app is changed', () => { + cy.get('[data-cy-app-order] [data-cy-app-order-element]').then(elements => { + const appIDs = elements.map((idx, el) => el.getAttribute('data-cy-app-order-element')).get() + expect(appIDs).to.deep.eq(['files', 'dashboard']) + }) + + // Check the redirect to the default app works + cy.request({ url: '/', followRedirect: false }).then((response) => { + expect(response.status).to.eq(302) + expect(response).to.have.property('headers') + expect(response.headers.location).to.contain('/apps/files') + }) + }) + + it('Toggle the "use custom default app" switch back to reset the default apps', () => { + cy.visit('/settings/admin/theming') + cy.get('[data-cy-switch-default-app]').scrollIntoView() + + cy.get('[data-cy-switch-default-app] input').should('be.checked') + cy.get('[data-cy-switch-default-app] .checkbox-content').click() + cy.get('[data-cy-switch-default-app] input').should('be.not.checked') + }) + + it('See the default app is changed back to default', () => { + // Check the redirect to the default app works + cy.request({ url: '/', followRedirect: false }).then((response) => { + expect(response.status).to.eq(302) + expect(response).to.have.property('headers') + expect(response.headers.location).to.contain('/apps/dashboard') + }) + }) +}) diff --git a/cypress/e2e/theming/admin-settings_urls.cy.ts b/cypress/e2e/theming/admin-settings_urls.cy.ts new file mode 100644 index 00000000000..46bae7901c4 --- /dev/null +++ b/cypress/e2e/theming/admin-settings_urls.cy.ts @@ -0,0 +1,143 @@ +/*! + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import { User } from '@nextcloud/cypress' + +const admin = new User('admin', 'admin') + +describe('Admin theming: Setting custom project URLs', function() { + this.beforeEach(() => { + // Just in case previous test failed + cy.resetAdminTheming() + cy.login(admin) + cy.visit('/settings/admin/theming') + cy.intercept('POST', '**/apps/theming/ajax/updateStylesheet').as('updateTheming') + }) + + it('Setting the web link', () => { + cy.findByRole('textbox', { name: /web link/i }) + .and('have.attr', 'type', 'url') + .as('input') + .scrollIntoView() + cy.get('@input') + .should('be.visible') + .type('{selectAll}http://example.com/path?query#fragment{enter}') + + cy.wait('@updateTheming') + + cy.logout() + + cy.visit('/') + cy.contains('a', 'Nextcloud') + .should('be.visible') + .and('have.attr', 'href', 'http://example.com/path?query#fragment') + }) + + it('Setting the legal notice link', () => { + cy.findByRole('textbox', { name: /legal notice link/i }) + .should('exist') + .and('have.attr', 'type', 'url') + .as('input') + .scrollIntoView() + cy.get('@input') + .type('http://example.com/path?query#fragment{enter}') + + cy.wait('@updateTheming') + + cy.logout() + + cy.visit('/') + cy.contains('a', /legal notice/i) + .should('be.visible') + .and('have.attr', 'href', 'http://example.com/path?query#fragment') + }) + + it('Setting the privacy policy link', () => { + cy.findByRole('textbox', { name: /privacy policy link/i }) + .should('exist') + .as('input') + .scrollIntoView() + cy.get('@input') + .should('have.attr', 'type', 'url') + .type('http://privacy.local/path?query#fragment{enter}') + + cy.wait('@updateTheming') + + cy.logout() + + cy.visit('/') + cy.contains('a', /privacy policy/i) + .should('be.visible') + .and('have.attr', 'href', 'http://privacy.local/path?query#fragment') + }) + +}) + +describe('Admin theming: Web link corner cases', function() { + this.beforeEach(() => { + // Just in case previous test failed + cy.resetAdminTheming() + cy.login(admin) + cy.visit('/settings/admin/theming') + cy.intercept('POST', '**/apps/theming/ajax/updateStylesheet').as('updateTheming') + }) + + it('Already URL encoded', () => { + cy.findByRole('textbox', { name: /web link/i }) + .and('have.attr', 'type', 'url') + .as('input') + .scrollIntoView() + cy.get('@input') + .should('be.visible') + .type('{selectAll}http://example.com/%22path%20with%20space%22{enter}') + + cy.wait('@updateTheming') + + cy.logout() + + cy.visit('/') + cy.contains('a', 'Nextcloud') + .should('be.visible') + .and('have.attr', 'href', 'http://example.com/%22path%20with%20space%22') + }) + + it('URL with double quotes', () => { + cy.findByRole('textbox', { name: /web link/i }) + .and('have.attr', 'type', 'url') + .as('input') + .scrollIntoView() + cy.get('@input') + .should('be.visible') + .type('{selectAll}http://example.com/"path"{enter}') + + cy.wait('@updateTheming') + + cy.logout() + + cy.visit('/') + cy.contains('a', 'Nextcloud') + .should('be.visible') + .and('have.attr', 'href', 'http://example.com/%22path%22') + }) + + it('URL with double quotes and already encoded', () => { + cy.findByRole('textbox', { name: /web link/i }) + .and('have.attr', 'type', 'url') + .as('input') + .scrollIntoView() + cy.get('@input') + .should('be.visible') + .type('{selectAll}http://example.com/"the%20path"{enter}') + + cy.wait('@updateTheming') + + cy.logout() + + cy.visit('/') + cy.contains('a', 'Nextcloud') + .should('be.visible') + .and('have.attr', 'href', 'http://example.com/%22the%20path%22') + }) + +}) diff --git a/cypress/e2e/theming/themingUtils.ts b/cypress/e2e/theming/themingUtils.ts index 2965886c656..b4740beda1c 100644 --- a/cypress/e2e/theming/themingUtils.ts +++ b/cypress/e2e/theming/themingUtils.ts @@ -1,49 +1,57 @@ /** - * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com> - * - * @author John Molakvoæ <skjnldsv@protonmail.com> - * - * @license AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ import { colord } from 'colord' -const defaultNextcloudBlue = '#0082c9' export const defaultPrimary = '#00679e' -export const defaultBackground = 'kamil-porembinski-clouds.jpg' +export const defaultBackground = 'jenna-kim-the-globe.webp' + +/** + * Check if a CSS variable is set to a specific color + * @param variable Variable to check + * @param expectedColor Color that is expected + */ +export function validateCSSVariable(variable: string, expectedColor: string) { + const value = window.getComputedStyle(Cypress.$('body').get(0)).getPropertyValue(variable) + console.debug(`${variable}, is: ${colord(value).toHex()} expected: ${expectedColor}`) + return colord(value).isEqual(expectedColor) +} /** * Validate the current page body css variables * - * @param {string} expectedColor the expected color + * @param {string} expectedColor the expected primary color * @param {string|null} expectedBackground the expected background + * @param {string|null} expectedBackgroundColor the expected background color (null to ignore) */ -export const validateBodyThemingCss = function(expectedColor = defaultPrimary, expectedBackground: string|null = defaultBackground) { +export function validateBodyThemingCss(expectedColor = defaultPrimary, expectedBackground: string|null = defaultBackground, expectedBackgroundColor: string|null = defaultPrimary) { // We must use `Cypress.$` here as any assertions (get is an assertion) is not allowed in wait-until's check function, see documentation const guestBackgroundColor = Cypress.$('body').css('background-color') const guestBackgroundImage = Cypress.$('body').css('background-image') - const isValidBackgroundColor = colord(guestBackgroundColor).isEqual(expectedColor) + const isValidBackgroundColor = expectedBackgroundColor === null || colord(guestBackgroundColor).isEqual(expectedBackgroundColor) const isValidBackgroundImage = !expectedBackground ? guestBackgroundImage === 'none' : guestBackgroundImage.includes(expectedBackground) - console.debug({ guestBackgroundColor: colord(guestBackgroundColor).toHex(), guestBackgroundImage, expectedColor, expectedBackground, isValidBackgroundColor, isValidBackgroundImage }) + console.debug({ + isValidBackgroundColor, + isValidBackgroundImage, + guestBackgroundColor: colord(guestBackgroundColor).toHex(), + guestBackgroundImage, + }) - return isValidBackgroundColor && isValidBackgroundImage + return isValidBackgroundColor && isValidBackgroundImage && validateCSSVariable('--color-primary', expectedColor) +} + +/** + * Check background color of element + * @param element JQuery element to check + * @param color expected color + */ +export function expectBackgroundColor(element: JQuery<HTMLElement>, color: string) { + expect(colord(element.css('background-color')).toHex()).equal(colord(color).toHex()) } /** @@ -58,28 +66,28 @@ export const validateUserThemingDefaultCss = function(expectedColor = defaultPri return false } - const defaultOptionBackground = defaultSelectButton.css('background-image') - const colorPickerOptionColor = defaultSelectButton.css('background-color') - const isNextcloudBlue = colord(colorPickerOptionColor).isEqual('#0082c9') + const backgroundImage = defaultSelectButton.css('background-image') + const backgroundColor = defaultSelectButton.css('background-color') const isValidBackgroundImage = !expectedBackground - ? defaultOptionBackground === 'none' - : defaultOptionBackground.includes(expectedBackground) - - console.debug({ colorPickerOptionColor: colord(colorPickerOptionColor).toHex(), expectedColor, isValidBackgroundImage, isNextcloudBlue }) + ? (backgroundImage === 'none' || Cypress.$('body').css('background-image') === 'none') + : backgroundImage.includes(expectedBackground) + + console.debug({ + colorPickerOptionColor: colord(backgroundColor).toHex(), + expectedColor, + isValidBackgroundImage, + backgroundImage, + }) - return isValidBackgroundImage && ( - colord(colorPickerOptionColor).isEqual(expectedColor) - // we replace nextcloud blue with the the default rpimary (apps/theming/lib/Themes/DefaultTheme.php line 76) - || (isNextcloudBlue && colord(expectedColor).isEqual(defaultPrimary)) - ) + return isValidBackgroundImage && colord(backgroundColor).isEqual(expectedColor) } -export const pickRandomColor = function(): Cypress.Chainable<string> { +export const pickRandomColor = function(context: string, index?: number): Cypress.Chainable<string> { // Pick one of the first 8 options - const randColour = Math.floor(Math.random() * 8) + const randColour = index ?? Math.floor(Math.random() * 8) - const colorPreviewSelector = '[data-user-theming-background-color],[data-admin-theming-setting-primary-color]' + const colorPreviewSelector = `${context} [data-admin-theming-setting-color]` let oldColor = '' cy.get(colorPreviewSelector).then(($el) => { @@ -87,7 +95,8 @@ export const pickRandomColor = function(): Cypress.Chainable<string> { }) // Open picker - cy.contains('button', 'Change color').click() + cy.get(`${context} [data-admin-theming-setting-color-picker]`).scrollIntoView() + cy.get(`${context} [data-admin-theming-setting-color-picker]`).click({ force: true }) // Click on random color cy.get('.color-picker__simple-color-circle').eq(randColour).click() diff --git a/cypress/e2e/theming/navigation-bar-settings.cy.ts b/cypress/e2e/theming/user-settings_app-order.cy.ts index 4bea6225f76..11ef2f45382 100644 --- a/cypress/e2e/theming/navigation-bar-settings.cy.ts +++ b/cypress/e2e/theming/user-settings_app-order.cy.ts @@ -1,110 +1,23 @@ /** - * @copyright Copyright (c) 2023 Ferdinand Thiessen <opensource@fthiessen.de> - * - * @author Ferdinand Thiessen <opensource@fthiessen.de> - * - * @license AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ import { User } from '@nextcloud/cypress' import { installTestApp, uninstallTestApp } from '../../support/commonUtils' +import { NavigationHeader } from '../../pages/NavigationHeader' -const admin = new User('admin', 'admin') - -describe('Admin theming set default apps', () => { - before(function() { - // Just in case previous test failed - cy.resetAdminTheming() - cy.login(admin) - }) - - it('See the current default app is the dashboard', () => { - cy.visit('/') - cy.url().should('match', /apps\/dashboard/) - - // Also check the top logo link - cy.get('#nextcloud').click() - cy.url().should('match', /apps\/dashboard/) - }) - - it('See the default app settings', () => { - cy.visit('/settings/admin/theming') - - cy.get('.settings-section').contains('Navigation bar settings').should('exist') - cy.get('[data-cy-switch-default-app]').should('exist') - cy.get('[data-cy-switch-default-app]').scrollIntoView() - }) - - it('Toggle the "use custom default app" switch', () => { - cy.get('[data-cy-switch-default-app] input').should('not.be.checked') - cy.get('[data-cy-switch-default-app] .checkbox-content').click() - cy.get('[data-cy-switch-default-app] input').should('be.checked') - }) - - it('See the default app order selector', () => { - cy.get('[data-cy-app-order] [data-cy-app-order-element]').then(elements => { - const appIDs = elements.map((idx, el) => el.getAttribute('data-cy-app-order-element')).get() - expect(appIDs).to.deep.eq(['dashboard', 'files']) - }) - }) - - it('Change the default app', () => { - cy.get('[data-cy-app-order] [data-cy-app-order-element="files"]').scrollIntoView() - - cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="up"]').should('be.visible') - cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="up"]').click() - cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="up"]').should('not.be.visible') - - }) - - it('See the default app is changed', () => { - cy.get('[data-cy-app-order] [data-cy-app-order-element]').then(elements => { - const appIDs = elements.map((idx, el) => el.getAttribute('data-cy-app-order-element')).get() - expect(appIDs).to.deep.eq(['files', 'dashboard']) - }) - - // Check the redirect to the default app works - cy.request({ url: '/', followRedirect: false }).then((response) => { - expect(response.status).to.eq(302) - expect(response).to.have.property('headers') - expect(response.headers.location).to.contain('/apps/files') - }) - }) - - it('Toggle the "use custom default app" switch back to reset the default apps', () => { - cy.visit('/settings/admin/theming') - cy.get('[data-cy-switch-default-app]').scrollIntoView() - - cy.get('[data-cy-switch-default-app] input').should('be.checked') - cy.get('[data-cy-switch-default-app] .checkbox-content').click() - cy.get('[data-cy-switch-default-app] input').should('be.not.checked') - }) +/** + * Intercept setting the app order as `updateAppOrder` + */ +function interceptAppOrder() { + cy.intercept('POST', '/ocs/v2.php/apps/provisioning_api/api/v1/config/users/core/apporder').as('updateAppOrder') +} - it('See the default app is changed back to default', () => { - // Check the redirect to the default app works - cy.request({ url: '/', followRedirect: false }).then((response) => { - expect(response.status).to.eq(302) - expect(response).to.have.property('headers') - expect(response.headers.location).to.contain('/apps/dashboard') - }) - }) -}) +before(() => uninstallTestApp()) describe('User theming set app order', () => { + const navigationHeader = new NavigationHeader() let user: User before(() => { @@ -126,40 +39,43 @@ describe('User theming set app order', () => { }) it('See that the dashboard app is the first one', () => { + const appOrder = ['Dashboard', 'Files'] // Check the app order settings UI - cy.get('[data-cy-app-order] [data-cy-app-order-element]').then(elements => { - const appIDs = elements.map((idx, el) => el.getAttribute('data-cy-app-order-element')).get() - expect(appIDs).to.deep.eq(['dashboard', 'files']) - }) + cy.get('[data-cy-app-order] [data-cy-app-order-element]') + .each((element, index) => expect(element).to.contain.text(appOrder[index])) // Check the top app menu order - cy.get('.app-menu-main .app-menu-entry').then(elements => { - const appIDs = elements.map((idx, el) => el.getAttribute('data-app-id')).get() - expect(appIDs).to.deep.eq(['dashboard', 'files']) - }) + navigationHeader.getNavigationEntries() + .each((entry, index) => expect(entry).contain.text(appOrder[index])) }) it('Change the app order', () => { + interceptAppOrder() cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="up"]').should('be.visible') cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="up"]').click() cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="up"]').should('not.be.visible') + cy.wait('@updateAppOrder') - cy.get('[data-cy-app-order] [data-cy-app-order-element]').then(elements => { - const appIDs = elements.map((idx, el) => el.getAttribute('data-cy-app-order-element')).get() - expect(appIDs).to.deep.eq(['files', 'dashboard']) - }) + const appOrder = ['Files', 'Dashboard'] + cy.get('[data-cy-app-order] [data-cy-app-order-element]') + .each((element, index) => expect(element).to.contain.text(appOrder[index])) }) it('See the app menu order is changed', () => { cy.reload() - cy.get('.app-menu-main .app-menu-entry').then(elements => { - const appIDs = elements.map((idx, el) => el.getAttribute('data-app-id')).get() - expect(appIDs).to.deep.eq(['files', 'dashboard']) - }) + const appOrder = ['Files', 'Dashboard'] + // Check the app order settings UI + cy.get('[data-cy-app-order] [data-cy-app-order-element]') + .each((element, index) => expect(element).to.contain.text(appOrder[index])) + + // Check the top app menu order + navigationHeader.getNavigationEntries() + .each((entry, index) => expect(entry).contain.text(appOrder[index])) }) }) describe('User theming set app order with default app', () => { + const navigationHeader = new NavigationHeader() let user: User before(() => { @@ -167,7 +83,7 @@ describe('User theming set app order with default app', () => { // install a third app installTestApp() // set files as default app - cy.runOccCommand('config:system:set --value "files" defaultapp') + cy.runOccCommand('config:system:set --value \'files\' defaultapp') // Create random user for this test cy.createRandomUser().then(($user) => { @@ -193,11 +109,11 @@ describe('User theming set app order with default app', () => { it('See the app order settings: files is the first one', () => { cy.visit('/settings/user/theming') cy.get('[data-cy-app-order]').scrollIntoView() - cy.get('[data-cy-app-order] [data-cy-app-order-element]').then(elements => { - expect(elements).to.have.length(4) - const appIDs = elements.map((idx, el) => el.getAttribute('data-cy-app-order-element')).get() - expect(appIDs).to.deep.eq(['files', 'dashboard', 'testapp1', 'testapp']) - }) + + const appOrder = ['Files', 'Dashboard', 'Test App 2', 'Test App'] + // Check the app order settings UI + cy.get('[data-cy-app-order] [data-cy-app-order-element]') + .each((element, index) => expect(element).to.contain.text(appOrder[index])) }) it('Can not change the default app', () => { @@ -212,32 +128,31 @@ describe('User theming set app order with default app', () => { }) it('Change the order of the other apps', () => { - cy.intercept('POST', '**/apps/provisioning_api/api/v1/config/users/core/apporder').as('setAppOrder') + interceptAppOrder() // Move the testapp up twice, it should be the first one after files cy.get('[data-cy-app-order] [data-cy-app-order-element="testapp"] [data-cy-app-order-button="up"]').click() - cy.wait('@setAppOrder') + cy.wait('@updateAppOrder') cy.get('[data-cy-app-order] [data-cy-app-order-element="testapp"] [data-cy-app-order-button="up"]').click() - cy.wait('@setAppOrder') + cy.wait('@updateAppOrder') // Can't get up anymore, files is enforced as default app cy.get('[data-cy-app-order] [data-cy-app-order-element="testapp"] [data-cy-app-order-button="up"]').should('not.be.visible') // Check the final list order - cy.get('[data-cy-app-order] [data-cy-app-order-element]').then(elements => { - expect(elements).to.have.length(4) - const appIDs = elements.map((idx, el) => el.getAttribute('data-cy-app-order-element')).get() - expect(appIDs).to.deep.eq(['files', 'testapp', 'dashboard', 'testapp1']) - }) + const appOrder = ['Files', 'Test App', 'Dashboard', 'Test App 2'] + // Check the app order settings UI + cy.get('[data-cy-app-order] [data-cy-app-order-element]') + .each((element, index) => expect(element).to.contain.text(appOrder[index])) }) it('See the app menu order is changed', () => { cy.reload() - cy.get('.app-menu-main .app-menu-entry').then(elements => { - expect(elements).to.have.length(4) - const appIDs = elements.map((idx, el) => el.getAttribute('data-app-id')).get() - expect(appIDs).to.deep.eq(['files', 'testapp', 'dashboard', 'testapp1']) - }) + + const appOrder = ['Files', 'Test App', 'Dashboard', 'Test App 2'] + // Check the top app menu order + navigationHeader.getNavigationEntries() + .each((entry, index) => expect(entry).contain.text(appOrder[index])) }) }) @@ -264,8 +179,10 @@ describe('User theming app order list accessibility', () => { }) it('click the first button', () => { + interceptAppOrder() cy.get('[data-cy-app-order] [data-cy-app-order-element="dashboard"] [data-cy-app-order-button="down"]').should('be.visible').focus() cy.get('[data-cy-app-order] [data-cy-app-order-element="dashboard"] [data-cy-app-order-button="down"]').click() + cy.wait('@updateAppOrder') }) it('see the same app kept the focus', () => { @@ -276,8 +193,10 @@ describe('User theming app order list accessibility', () => { }) it('click the last button', () => { + interceptAppOrder() cy.get('[data-cy-app-order] [data-cy-app-order-element="dashboard"] [data-cy-app-order-button="up"]').should('be.visible').focus() cy.get('[data-cy-app-order] [data-cy-app-order-element="dashboard"] [data-cy-app-order-button="up"]').click() + cy.wait('@updateAppOrder') }) it('see the same app kept the focus', () => { @@ -289,6 +208,7 @@ describe('User theming app order list accessibility', () => { }) describe('User theming reset app order', () => { + const navigationHeader = new NavigationHeader() let user: User before(() => { @@ -310,17 +230,14 @@ describe('User theming reset app order', () => { }) it('See that the dashboard app is the first one', () => { + const appOrder = ['Dashboard', 'Files'] // Check the app order settings UI - cy.get('[data-cy-app-order] [data-cy-app-order-element]').then(elements => { - const appIDs = elements.map((idx, el) => el.getAttribute('data-cy-app-order-element')).get() - expect(appIDs).to.deep.eq(['dashboard', 'files']) - }) + cy.get('[data-cy-app-order] [data-cy-app-order-element]') + .each((element, index) => expect(element).to.contain.text(appOrder[index])) // Check the top app menu order - cy.get('.app-menu-main .app-menu-entry').then(elements => { - const appIDs = elements.map((idx, el) => el.getAttribute('data-app-id')).get() - expect(appIDs).to.deep.eq(['dashboard', 'files']) - }) + navigationHeader.getNavigationEntries() + .each((entry, index) => expect(entry).contain.text(appOrder[index])) }) it('See the reset button is disabled', () => { @@ -329,15 +246,17 @@ describe('User theming reset app order', () => { }) it('Change the app order', () => { + interceptAppOrder() cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="up"]').should('be.visible') cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="up"]').click() cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="up"]').should('not.be.visible') + cy.wait('@updateAppOrder') // Check the app order settings UI - cy.get('[data-cy-app-order] [data-cy-app-order-element]').then(elements => { - const appIDs = elements.map((idx, el) => el.getAttribute('data-cy-app-order-element')).get() - expect(appIDs).to.deep.eq(['files', 'dashboard']) - }) + const appOrder = ['Files', 'Dashboard'] + // Check the app order settings UI + cy.get('[data-cy-app-order] [data-cy-app-order-element]') + .each((element, index) => expect(element).to.contain.text(appOrder[index])) }) it('See the reset button is no longer disabled', () => { @@ -346,14 +265,25 @@ describe('User theming reset app order', () => { }) it('Reset the app order', () => { + cy.intercept('GET', '/ocs/v2.php/core/navigation/apps').as('loadApps') + interceptAppOrder() cy.get('[data-test-id="btn-apporder-reset"]').click({ force: true }) + + cy.wait('@updateAppOrder') + .its('request.body') + .should('have.property', 'configValue', '[]') + cy.wait('@loadApps') }) it('See the app order is restored', () => { - cy.get('[data-cy-app-order] [data-cy-app-order-element]').then(elements => { - const appIDs = elements.map((idx, el) => el.getAttribute('data-cy-app-order-element')).get() - expect(appIDs).to.deep.eq(['dashboard', 'files']) - }) + const appOrder = ['Dashboard', 'Files'] + // Check the app order settings UI + cy.get('[data-cy-app-order] [data-cy-app-order-element]') + .each((element, index) => expect(element).to.contain.text(appOrder[index])) + + // Check the top app menu order + navigationHeader.getNavigationEntries() + .each((entry, index) => expect(entry).contain.text(appOrder[index])) }) it('See the reset button is disabled again', () => { diff --git a/cypress/e2e/theming/user-background.cy.ts b/cypress/e2e/theming/user-settings_background.cy.ts index cdf3ef59f4d..8abcb5bace1 100644 --- a/cypress/e2e/theming/user-background.cy.ts +++ b/cypress/e2e/theming/user-settings_background.cy.ts @@ -1,27 +1,11 @@ /** - * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com> - * - * @author John Molakvoæ <skjnldsv@protonmail.com> - * - * @license AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ import { User } from '@nextcloud/cypress' -import { defaultPrimary, defaultBackground, pickRandomColor, validateBodyThemingCss } from './themingUtils' +import { defaultPrimary, defaultBackground, validateBodyThemingCss } from './themingUtils' +import { NavigationHeader } from '../../pages/NavigationHeader' const admin = new User('admin', 'admin') @@ -80,7 +64,7 @@ describe('User select shipped backgrounds and remove background', function() { // Validate changed background and primary cy.wait('@setBackground') - cy.waitUntil(() => validateBodyThemingCss('#a53c17', background)) + cy.waitUntil(() => validateBodyThemingCss('#a53c17', background, '#652e11')) }) it('Select a bright shipped background', function() { @@ -95,21 +79,21 @@ describe('User select shipped backgrounds and remove background', function() { // Validate changed background and primary cy.wait('@setBackground') - cy.waitUntil(() => validateBodyThemingCss('#869171', background)) + cy.waitUntil(() => validateBodyThemingCss('#56633d', background, '#dee0d3')) }) it('Remove background', function() { - cy.intercept('*/apps/theming/background/custom').as('clearBackground') + cy.intercept('*/apps/theming/background/color').as('clearBackground') // Clear background - cy.get('[data-user-theming-background-clear]').click() + cy.get('[data-user-theming-background-color]').click() // Set the accessibility state - cy.get('[data-user-theming-background-clear]').should('have.attr', 'aria-pressed', 'true') + cy.get('[data-user-theming-background-color]').should('have.attr', 'aria-pressed', 'true') // Validate clear background cy.wait('@clearBackground') - cy.waitUntil(() => validateBodyThemingCss('#869171', null)) + cy.waitUntil(() => validateBodyThemingCss('#56633d', null, '#dee0d3')) }) }) @@ -129,18 +113,18 @@ describe('User select a custom color', function() { it('Select a custom color', function() { cy.intercept('*/apps/theming/background/color').as('setColor') - pickRandomColor() + cy.get('[data-user-theming-background-color]').click() + cy.get('.color-picker__simple-color-circle').eq(5).click() // Validate custom colour change cy.wait('@setColor') - cy.waitUntil(() => cy.window().then((win) => { - const primary = getComputedStyle(win.document.body).getPropertyValue('--color-primary') - return primary !== defaultPrimary && primary !== defaultPrimary - })) + cy.waitUntil(() => validateBodyThemingCss(defaultPrimary, null, '#a5b872')) }) }) describe('User select a bright custom color and remove background', function() { + const navigationHeader = new NavigationHeader() + before(function() { cy.createRandomUser().then((user: User) => { cy.login(user) @@ -154,10 +138,11 @@ describe('User select a bright custom color and remove background', function() { }) it('Remove background', function() { - cy.intercept('*/apps/theming/background/custom').as('clearBackground') + cy.intercept('*/apps/theming/background/color').as('clearBackground') // Clear background - cy.get('[data-user-theming-background-clear]').click() + cy.get('[data-user-theming-background-color]').click() + cy.get('[data-user-theming-background-color]').click() // Validate clear background cy.wait('@clearBackground') @@ -168,7 +153,8 @@ describe('User select a bright custom color and remove background', function() { cy.intercept('*/apps/theming/background/color').as('setColor') // Pick one of the bright color preset - cy.contains('button', 'Change color').click() + cy.get('[data-user-theming-background-color]').scrollIntoView() + cy.get('[data-user-theming-background-color]').click() cy.get('.color-picker__simple-color-circle:eq(4)').click() // Validate custom colour change @@ -176,12 +162,12 @@ describe('User select a bright custom color and remove background', function() { }) it('See the header being inverted', function() { - cy.waitUntil(() => cy.window().then((win) => { - const firstEntry = win.document.querySelector('.app-menu-main li img') - if (!firstEntry) { - return false - } - return getComputedStyle(firstEntry).filter === 'invert(1)' + cy.waitUntil(() => navigationHeader.getNavigationEntries().find('img').then((el) => { + let ret = true + el.each(function() { + ret = ret && window.getComputedStyle(this).filter === 'invert(1)' + }) + return ret })) }) @@ -194,16 +180,16 @@ describe('User select a bright custom color and remove background', function() { // Validate changed background and primary cy.wait('@setBackground') - cy.waitUntil(() => validateBodyThemingCss('#a53c17', background)) + cy.waitUntil(() => validateBodyThemingCss('#a53c17', background, '#652e11')) }) it('See the header NOT being inverted this time', function() { - cy.waitUntil(() => cy.window().then((win) => { - const firstEntry = win.document.querySelector('.app-menu-main li') - if (!firstEntry) { - return false - } - return getComputedStyle(firstEntry).filter === 'none' + cy.waitUntil(() => navigationHeader.getNavigationEntries().find('img').then((el) => { + let ret = true + el.each(function() { + ret = ret && window.getComputedStyle(this).filter === 'none' + }) + return ret })) }) }) @@ -240,15 +226,13 @@ describe('User select a custom background', function() { // Wait for background to be set cy.wait('@setBackground') - cy.waitUntil(() => validateBodyThemingCss('#4c0c04', 'apps/theming/background?v=')) + cy.waitUntil(() => validateBodyThemingCss(defaultPrimary, 'apps/theming/background?v=', '#2f2221')) }) }) describe('User changes settings and reload the page', function() { const image = 'image.jpg' - const primaryFromImage = '#4c0c04' - - let selectedColor = '' + const colorFromImage = '#2f2221' before(function() { cy.createRandomUser().then((user: User) => { @@ -280,28 +264,39 @@ describe('User changes settings and reload the page', function() { // Wait for background to be set cy.wait('@setBackground') - cy.waitUntil(() => validateBodyThemingCss(primaryFromImage, 'apps/theming/background?v=')) + cy.waitUntil(() => validateBodyThemingCss(defaultPrimary, 'apps/theming/background?v=', colorFromImage)) }) it('Select a custom color', function() { cy.intercept('*/apps/theming/background/color').as('setColor') - cy.contains('button', 'Change color').click() + cy.get('[data-user-theming-background-color]').click() cy.get('.color-picker__simple-color-circle:eq(5)').click() + cy.get('[data-user-theming-background-color]').click() // Validate clear background cy.wait('@setColor') - cy.waitUntil(() => cy.window().then((win) => { - selectedColor = getComputedStyle(win.document.body).getPropertyValue('--color-primary') - return selectedColor !== primaryFromImage - })) + cy.waitUntil(() => validateBodyThemingCss(defaultPrimary, null, '#a5b872')) + }) + + it('Select a custom primary color', function() { + cy.intercept('/ocs/v2.php/apps/provisioning_api/api/v1/config/users/theming/primary_color').as('setPrimaryColor') + + cy.get('[data-user-theming-primary-color-trigger]').scrollIntoView() + cy.get('[data-user-theming-primary-color-trigger]').click() + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(500) + cy.get('.color-picker__simple-color-circle').should('be.visible') + cy.get('.color-picker__simple-color-circle:eq(2)').click() + cy.get('[data-user-theming-primary-color-trigger]').click() + + // Validate clear background + cy.wait('@setPrimaryColor') + cy.waitUntil(() => validateBodyThemingCss('#c98879', null, '#a5b872')) }) it('Reload the page and validate persistent changes', function() { cy.reload() - cy.waitUntil(() => validateBodyThemingCss(selectedColor, 'apps/theming/background?v=')) - - // validate accessibility state - cy.get('[data-user-theming-background-custom]').should('have.attr', 'aria-pressed', 'true') + cy.waitUntil(() => validateBodyThemingCss('#c98879', null, '#a5b872')) }) }) |