Faster theming tests, better colours comparison and properly follow admin theming changes

Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
pull/35525/head
John Molakvoæ 1 year ago
parent 8434259b1b
commit f26ee9c69d
No known key found for this signature in database
GPG Key ID: 60C25B8C072916CF

@ -39,6 +39,11 @@ return [
'url' => '/ajax/undoChanges',
'verb' => 'POST'
],
[
'name' => 'Theming#undoAll',
'url' => '/ajax/undoAllChanges',
'verb' => 'POST'
],
[
'name' => 'Theming#uploadImage',
'url' => '/ajax/uploadImage',

@ -275,6 +275,27 @@ class ThemingController extends Controller {
);
}
/**
* Revert all theming settings to their default values
* @AuthorizedAdminSetting(settings=OCA\Theming\Settings\Admin)
*
* @return DataResponse
* @throws NotPermittedException
*/
public function undoAll(): DataResponse {
$this->themingDefaults->undoAll();
return new DataResponse(
[
'data' =>
[
'message' => $this->l10n->t('Saved'),
],
'status' => 'success'
]
);
}
/**
* @PublicPage
* @NoCSRFRequired

@ -186,6 +186,7 @@ class UserThemeController extends OCSController {
$this->backgroundService->setFileBackground($value);
break;
case BackgroundService::BACKGROUND_DEFAULT:
// Delete both background and color keys
$this->backgroundService->setDefaultBackground();
break;
default:

@ -160,7 +160,7 @@ class BackgroundService {
public function setDefaultBackground(): void {
$this->config->deleteUserValue($this->userId, Application::APP_ID, 'background_image');
$this->config->setUserValue($this->userId, Application::APP_ID, 'background_color', $this->themingDefaults->getDefaultColorPrimary());
$this->config->deleteUserValue($this->userId, Application::APP_ID, 'background_color');
}
/**

@ -439,18 +439,26 @@ class ThemingDefaults extends \OC_Defaults {
* @param string $setting
* @param string $value
*/
public function set($setting, $value) {
public function set($setting, $value): void {
$this->config->setAppValue('theming', $setting, $value);
$this->increaseCacheBuster();
}
/**
* Revert all settings to the default value
*/
public function undoAll(): void {
$this->config->deleteAppValues('theming');
$this->increaseCacheBuster();
}
/**
* Revert settings to the default value
*
* @param string $setting setting which should be reverted
* @return string default value
*/
public function undo($setting) {
public function undo($setting): string {
$this->config->deleteAppValue('theming', $setting);
$this->increaseCacheBuster();

@ -58,15 +58,15 @@
<FileInputField v-for="field in fileInputFields"
:key="field.name"
:aria-label="field.ariaLabel"
:data-admin-theming-setting-file="field.name"
:default-mime-value="field.defaultMimeValue"
:display-name="field.displayName"
:mime-name="field.mimeName"
:mime-value.sync="field.mimeValue"
:name="field.name"
data-admin-theming-setting-background
@update:theming="$emit('update:theming')" />
<div class="admin-theming__preview">
<div class="admin-theming__preview-logo" />
<div class="admin-theming__preview" data-admin-theming-preview>
<div class="admin-theming__preview-logo" data-admin-theming-preview-logo />
</div>
</div>
</NcSettingsSection>

@ -27,7 +27,7 @@
<NcButton type="secondary"
:id="id"
:aria-label="ariaLabel"
data-admin-theming-setting-background-picker
data-admin-theming-setting-file-picker
@click="activateLocalFilePicker">
<template #icon>
<Upload :size="20" />
@ -37,7 +37,7 @@
<NcButton v-if="showReset"
type="tertiary"
:aria-label="t('theming', 'Reset to default')"
data-admin-theming-setting-background-reset
data-admin-theming-setting-file-reset
@click="undo">
<template #icon>
<Undo :size="20" />
@ -46,7 +46,7 @@
<NcButton v-if="showRemove"
type="tertiary"
:aria-label="t('theming', 'Remove background image')"
data-admin-theming-setting-background-remove
data-admin-theming-setting-file-remove
@click="removeBackground">
<template #icon>
<Delete :size="20" />

@ -20,6 +20,9 @@
*
*/
import { User } from '@nextcloud/cypress'
import { colord } from 'colord'
import { pickRandomColor, validateBodyThemingCss, validateUserThemingDefaultCss } from './themingUtils'
const admin = new User('admin', 'admin')
@ -28,6 +31,8 @@ const defaultBackground = 'kamil-porembinski-clouds.jpg'
describe('Admin theming settings', function() {
before(function() {
// Just in case previous test failed
cy.resetTheming()
cy.login(admin)
})
@ -39,13 +44,16 @@ describe('Admin theming settings', function() {
it('See the default settings', function() {
cy.get('[data-admin-theming-setting-primary-color-picker]').should('contain.text', defaultPrimary)
cy.get('[data-admin-theming-setting-primary-color-reset]').should('not.exist')
cy.get('[data-admin-theming-setting-background-reset]').should('not.exist')
cy.get('[data-admin-theming-setting-background-remove]').should('be.visible')
cy.get('[data-admin-theming-setting-file-reset]').should('not.exist')
cy.get('[data-admin-theming-setting-file-remove]').should('be.visible')
})
})
describe('Change the primary colour', function() {
describe('Change the primary colour and reset it', function() {
let selectedColor = ''
before(function() {
// Just in case previous test failed
cy.resetTheming()
cy.login(admin)
})
@ -57,14 +65,11 @@ describe('Change the primary colour', function() {
it('Change the primary colour', function() {
cy.intercept('*/apps/theming/ajax/updateStylesheet').as('setColor')
cy.get('[data-admin-theming-setting-primary-color-picker]').click()
cy.get('.color-picker__simple-color-circle:eq(3)').click()
pickRandomColor('[data-admin-theming-setting-primary-color-picker]')
.then(color => selectedColor = color)
cy.wait('@setColor')
cy.waitUntil(() => cy.window().then((win) => {
const primary = getComputedStyle(win.document.body).getPropertyValue('--color-primary-default')
return primary !== defaultPrimary
}))
cy.waitUntil(() => validateBodyThemingCss(selectedColor, defaultBackground))
})
it('Screenshot the login page', function() {
@ -73,32 +78,21 @@ describe('Change the primary colour', function() {
cy.screenshot()
})
it('Login again and go to the admin theming section', function() {
cy.login(admin)
cy.visit('/settings/admin/theming')
})
it('Reset the primary colour', function() {
cy.intercept('*/apps/theming/ajax/undoChanges').as('undoChanges')
cy.get('[data-admin-theming-setting-primary-color-reset]').click()
cy.wait('@undoChanges')
cy.waitUntil(() => cy.window().then((win) => {
const primary = getComputedStyle(win.document.body).getPropertyValue('--color-primary-default')
return primary === defaultPrimary
}))
it('Undo theming settings', function() {
cy.resetTheming()
})
it('Screenshot the login page', function() {
cy.logout()
cy.visit('/')
cy.waitUntil(validateBodyThemingCss)
cy.screenshot()
})
})
describe('Remove the default background', function() {
describe('Remove the default background and restore it', function() {
before(function() {
// Just in case previous test failed
cy.resetTheming()
cy.login(admin)
})
@ -110,13 +104,13 @@ describe('Remove the default background', function() {
it('Remove the default background', function() {
cy.intercept('*/apps/theming/ajax/updateStylesheet').as('removeBackground')
cy.get('[data-admin-theming-setting-background-remove]').click()
cy.get('[data-admin-theming-setting-file-remove]').click()
cy.wait('@removeBackground')
cy.waitUntil(() => cy.window().then((win) => {
const backgroundDefault = getComputedStyle(win.document.body).getPropertyValue('--image-background-default')
const currentBackgroundDefault = getComputedStyle(win.document.body).getPropertyValue('--image-background-default')
const backgroundPlain = getComputedStyle(win.document.body).getPropertyValue('--image-background-plain')
return !backgroundDefault.includes(defaultBackground)
return !currentBackgroundDefault.includes(defaultBackground)
&& backgroundPlain !== ''
}))
})
@ -127,38 +121,25 @@ describe('Remove the default background', function() {
cy.screenshot()
})
it('Login again and go to the admin theming section', function() {
cy.login(admin)
cy.visit('/settings/admin/theming')
})
it('Restore the default background', function() {
cy.intercept('*/apps/theming/ajax/undoChanges').as('undoChanges')
cy.get('[data-admin-theming-setting-background-reset]').click()
cy.wait('@undoChanges')
cy.waitUntil(() => cy.window().then((win) => {
const backgroundDefault = getComputedStyle(win.document.body).getPropertyValue('--image-background-default')
const backgroundPlain = getComputedStyle(win.document.body).getPropertyValue('--image-background-plain')
return backgroundDefault.includes(defaultBackground)
&& backgroundPlain === ''
}))
it('Undo theming settings', function() {
cy.resetTheming()
})
it('Screenshot the login page', function() {
cy.logout()
cy.visit('/')
cy.waitUntil(validateBodyThemingCss)
cy.screenshot()
})
})
describe('Change the login fields', function() {
describe('Change the login fields then reset them', function() {
const name = 'ABCdef123'
const url = 'https://example.com'
const slogan = 'Testing is fun'
before(function() {
// Just in case previous test failed
cy.resetTheming()
cy.login(admin)
})
@ -214,29 +195,11 @@ describe('Change the login fields', function() {
cy.get('footer p').should('contain.text', ` ${slogan}`)
})
it('Login again and go to the admin theming section', function() {
cy.login(admin)
cy.visit('/settings/admin/theming')
})
it('Undo changes', function() {
cy.intercept('*/apps/theming/ajax/undoChanges').as('undoChanges')
cy.get('[data-admin-theming-setting-field="name"] .input-field__clear-button')
.scrollIntoView().click()
cy.wait('@undoChanges')
cy.get('[data-admin-theming-setting-field="url"] .input-field__clear-button')
.scrollIntoView().click()
cy.wait('@undoChanges')
cy.get('[data-admin-theming-setting-field="slogan"] .input-field__clear-button')
.scrollIntoView().click()
cy.wait('@undoChanges')
it('Undo theming settings', function() {
cy.resetTheming()
})
it('Check login screen changes', function() {
cy.logout()
cy.visit('/')
cy.get('[data-login-form-headline]').should('not.contain.text', name)
@ -246,8 +209,10 @@ describe('Change the login fields', function() {
})
})
describe('Disable user theming', function() {
describe('Disable user theming and enable it back', function() {
before(function() {
// Just in case previous test failed
cy.resetTheming()
cy.login(admin)
})
@ -278,25 +243,74 @@ describe('Disable user theming', function() {
cy.visit('/settings/user/theming')
cy.get('[data-user-theming-background-disabled]').scrollIntoView().should('be.visible')
})
})
it('Login back as admin', function() {
cy.logout()
describe('User default option matches admin theming', function() {
let selectedColor = ''
before(function() {
// Just in case previous test failed
cy.resetTheming()
cy.login(admin)
})
after(function() {
cy.resetTheming()
})
it('See the admin theming section', function() {
cy.visit('/settings/admin/theming')
cy.get('[data-admin-theming-settings]').scrollIntoView().should('be.visible')
})
it('Enable back user theming', function() {
cy.intercept('*/apps/theming/ajax/updateStylesheet').as('enableUserTheming')
it('Change the primary colour', function() {
cy.intercept('*/apps/theming/ajax/updateStylesheet').as('setColor')
cy.get('[data-admin-theming-setting-disable-user-theming]')
.scrollIntoView().should('be.visible')
cy.get('[data-admin-theming-setting-disable-user-theming] input[type="checkbox"]').uncheck({ force: true })
cy.get('[data-admin-theming-setting-disable-user-theming] input[type="checkbox"]').should('not.be.checked')
pickRandomColor('[data-admin-theming-setting-primary-color-picker]')
.then(color => selectedColor = color)
cy.wait('@setColor')
cy.waitUntil(() => cy.window().then((win) => {
const primary = getComputedStyle(win.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.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=')
}))
})
it('Logout and check changes', function() {
cy.logout()
cy.visit('/')
cy.waitUntil(() => validateBodyThemingCss(selectedColor, '/apps/theming/image/background?v='))
})
it('Login as user', function() {
cy.createRandomUser().then((user) => {
cy.login(user)
})
})
it('See the user background settings', function() {
cy.visit('/settings/user/theming')
cy.get('[data-user-theming-background-settings]').scrollIntoView().should('be.visible')
})
it('See the default background option selected', function() {
cy.get('[data-user-theming-background-default]').should('be.visible')
cy.get('[data-user-theming-background-default]').should('have.class', 'background--active')
cy.wait('@enableUserTheming')
cy.waitUntil(() => validateUserThemingDefaultCss(selectedColor, '/apps/theming/image/background?v='))
})
})

@ -0,0 +1,75 @@
/**
* @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/>.
*
*/
import { colord } from 'colord'
/**
* Validate the current page body css variables
*
* @param {string} expectedColor the expected color
* @param {string} expectedBackground the expected background
*/
export const validateBodyThemingCss = function(expectedColor = '#0082c9', expectedBackground = 'kamil-porembinski-clouds.jpg') {
return cy.window().then((win) => {
const guestBackgroundColor = getComputedStyle(win.document.body).backgroundColor
const guestBackgroundImage = getComputedStyle(win.document.body).backgroundImage
return colord(guestBackgroundColor).isEqual(expectedColor)
&& guestBackgroundImage.includes(expectedBackground)
})
}
/**
* Validate the user theming default select option css
*
* @param {string} expectedColor the expected color
* @param {string} expectedBackground the expected background
*/
export const validateUserThemingDefaultCss = function(expectedColor = '#0082c9', expectedBackground = 'kamil-porembinski-clouds.jpg') {
return cy.window().then((win) => {
const defaultSelectButton = win.document.querySelector('[data-user-theming-background-default]')
const customColorSelectButton = win.document.querySelector('[data-user-theming-background-color]')
if (!defaultSelectButton || !customColorSelectButton) {
return false
}
const defaultOptionBackground = getComputedStyle(defaultSelectButton).backgroundImage
const defaultOptionBorderColor = getComputedStyle(defaultSelectButton).borderColor
const colorPickerOptionColor = getComputedStyle(customColorSelectButton).backgroundColor
return defaultOptionBackground.includes(expectedBackground)
&& colord(defaultOptionBorderColor).isEqual(expectedColor)
&& colord(colorPickerOptionColor).isEqual(expectedColor)
})
}
export const pickRandomColor = function(pickerSelector: string): Cypress.Chainable<string> {
// Pick one of the first 8 options
const randColour = Math.floor(Math.random() * 8)
// Open picker
cy.get(pickerSelector).click()
// Return selected colour
return cy.get(pickerSelector).get(`.color-picker__simple-color-circle`).eq(randColour)
.click().then(colorElement => {
const selectedColor = colorElement.css('background-color')
return selectedColor
})
}

@ -23,16 +23,17 @@ import type { User } from '@nextcloud/cypress'
const defaultPrimary = '#006aa3'
const defaultBackground = 'kamil-porembinski-clouds.jpg'
import { colord } from 'colord'
const validateThemingCss = function(expectedPrimary = '#0082c9', expectedBackground = 'kamil-porembinski-clouds.jpg', bright = false) {
return cy.window().then((win) => {
const primary = getComputedStyle(win.document.body).getPropertyValue('--color-primary')
const background = getComputedStyle(win.document.body).getPropertyValue('--image-background')
const backgroundColor = getComputedStyle(win.document.body).backgroundColor
const backgroundImage = getComputedStyle(win.document.body).backgroundImage
const invertIfBright = getComputedStyle(win.document.body).getPropertyValue('--background-image-invert-if-bright')
// Returning boolean for cy.waitUntil usage
return primary === expectedPrimary
&& background.includes(expectedBackground)
return colord(backgroundColor).isEqual(expectedPrimary)
&& backgroundImage.includes(expectedBackground)
&& invertIfBright === (bright ? 'invert(100%)' : 'no')
})
}
@ -163,7 +164,6 @@ describe('User select a custom background', function() {
})
})
describe('User changes settings and reload the page', function() {
const image = 'image.jpg'
const primaryFromImage = '#4c0c04'

@ -21,7 +21,7 @@
*/
/* eslint-disable node/no-unpublished-import */
import axios from '@nextcloud/axios'
import { addCommands, type User} from '@nextcloud/cypress'
import { addCommands, User } from '@nextcloud/cypress'
import { basename } from 'path'
// Add custom commands
@ -33,7 +33,8 @@ declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
interface Chainable<Subject = any> {
uploadFile(user: User, fixture: string, mimeType: string, target ?: string): Cypress.Chainable<void>
uploadFile(user: User, fixture?: string, mimeType?: string, target ?: string): Cypress.Chainable<void>,
resetTheming(): Cypress.Chainable<void>,
}
}
}
@ -50,7 +51,7 @@ Cypress.env('baseUrl', url)
* @param {string} mimeType e.g. image/png
* @param {string} [target] the target of the file relative to the user root
*/
Cypress.Commands.add('uploadFile', (user, fixture, mimeType, target = `/${fixture}`) => {
Cypress.Commands.add('uploadFile', (user, fixture = 'image.jpg', mimeType = 'image/jpeg', target = `/${fixture}`) => {
cy.clearCookies()
const fileName = basename(target)
@ -84,3 +85,29 @@ Cypress.Commands.add('uploadFile', (user, fixture, mimeType, target = `/${fixtur
}
})
})
/**
* Reset the admin theming entirely
*/
Cypress.Commands.add('resetTheming', () => {
const admin = new User('admin', 'admin')
cy.clearCookies()
cy.login(admin)
// Clear all settings
cy.request('/csrftoken').then(({ body }) => {
const requestToken = body.token
axios({
method: 'POST',
url: '/index.php/apps/theming/ajax/undoAllChanges',
headers: {
'requesttoken': requestToken,
},
})
})
// Clear admin session
cy.clearCookies()
})

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

17
package-lock.json generated

@ -39,6 +39,7 @@
"buffer": "^6.0.3",
"camelcase": "^6.3.0",
"clipboard": "^2.0.10",
"colord": "^2.9.3",
"core-js": "^3.24.0",
"davclient.js": "git+https://github.com/owncloud/davclient.js.git#0.2.1",
"debounce": "^1.2.1",
@ -8245,11 +8246,9 @@
}
},
"node_modules/colord": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/colord/-/colord-2.9.2.tgz",
"integrity": "sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ==",
"dev": true,
"peer": true
"version": "2.9.3",
"resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz",
"integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw=="
},
"node_modules/colorette": {
"version": "2.0.16",
@ -30099,11 +30098,9 @@
"peer": true
},
"colord": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/colord/-/colord-2.9.2.tgz",
"integrity": "sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ==",
"dev": true,
"peer": true
"version": "2.9.3",
"resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz",
"integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw=="
},
"colorette": {
"version": "2.0.16",

@ -62,6 +62,7 @@
"buffer": "^6.0.3",
"camelcase": "^6.3.0",
"clipboard": "^2.0.10",
"colord": "^2.9.3",
"core-js": "^3.24.0",
"davclient.js": "git+https://github.com/owncloud/davclient.js.git#0.2.1",
"debounce": "^1.2.1",

Loading…
Cancel
Save