Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>tags/v26.0.0beta1
class UserThemeController extends OCSController { | class UserThemeController extends OCSController { | ||||
protected string $userId; | |||||
protected ?string $userId = null; | |||||
private IConfig $config; | private IConfig $config; | ||||
private IUserSession $userSession; | private IUserSession $userSession; | ||||
private ThemesService $themesService; | private ThemesService $themesService; | ||||
private ThemingDefaults $themingDefaults; | private ThemingDefaults $themingDefaults; | ||||
private BackgroundService $backgroundService; | private BackgroundService $backgroundService; | ||||
/** | |||||
* Config constructor. | |||||
*/ | |||||
public function __construct(string $appName, | public function __construct(string $appName, | ||||
IRequest $request, | IRequest $request, | ||||
IConfig $config, | IConfig $config, | ||||
$this->themesService = $themesService; | $this->themesService = $themesService; | ||||
$this->themingDefaults = $themingDefaults; | $this->themingDefaults = $themingDefaults; | ||||
$this->backgroundService = $backgroundService; | $this->backgroundService = $backgroundService; | ||||
$this->userId = $userSession->getUser()->getUID(); | |||||
$user = $userSession->getUser(); | |||||
if ($user !== null) { | |||||
$this->userId = $user->getUID(); | |||||
} | |||||
} | } | ||||
/** | /** |
*/ | */ | ||||
namespace OCA\Theming\Themes; | namespace OCA\Theming\Themes; | ||||
use OCA\Theming\AppInfo\Application; | |||||
use OCA\Theming\Util; | |||||
use OCA\Theming\ImageManager; | use OCA\Theming\ImageManager; | ||||
use OCA\Theming\AppInfo\Application; | |||||
use OCA\Theming\Service\BackgroundService; | use OCA\Theming\Service\BackgroundService; | ||||
use OCA\Theming\Util; | |||||
trait CommonThemeTrait { | trait CommonThemeTrait { | ||||
public Util $util; | public Util $util; | ||||
* Generate admin theming background-related variables | * Generate admin theming background-related variables | ||||
*/ | */ | ||||
protected function generateGlobalBackgroundVariables(): array { | protected function generateGlobalBackgroundVariables(): array { | ||||
$user = $this->userSession->getUser(); | |||||
$backgroundDeleted = $this->config->getAppValue(Application::APP_ID, 'backgroundMime', '') === 'backgroundColor'; | $backgroundDeleted = $this->config->getAppValue(Application::APP_ID, 'backgroundMime', '') === 'backgroundColor'; | ||||
$hasCustomLogoHeader = $this->util->isLogoThemed(); | $hasCustomLogoHeader = $this->util->isLogoThemed(); | ||||
$isDefaultPrimaryBright = $this->util->invertTextColor($this->defaultPrimaryColor); | |||||
$variables = []; | $variables = []; | ||||
// If primary as background has been request or if we have a custom primary colour | // If primary as background has been request or if we have a custom primary colour | ||||
// let's not define the background image | // let's not define the background image | ||||
if ($backgroundDeleted) { | if ($backgroundDeleted) { | ||||
$variables['--color-background-plain'] = $this->themingDefaults->getColorPrimary(); | |||||
$variables['--color-background-plain'] = $this->defaultPrimaryColor; | |||||
$variables['--image-background-plain'] = 'yes'; | $variables['--image-background-plain'] = 'yes'; | ||||
// If no background image is set, we need to check against the shown primary colour | |||||
$variables['--background-image-invert-if-bright'] = $isDefaultPrimaryBright ? 'invert(100%)' : 'no'; | |||||
} | } | ||||
// Register image variables only if custom-defined | // Register image variables only if custom-defined | ||||
&& $this->appManager->isEnabledForUser(Application::APP_ID)) { | && $this->appManager->isEnabledForUser(Application::APP_ID)) { | ||||
$backgroundImage = $this->config->getUserValue($user->getUID(), Application::APP_ID, 'background_image', BackgroundService::BACKGROUND_DEFAULT); | $backgroundImage = $this->config->getUserValue($user->getUID(), Application::APP_ID, 'background_image', BackgroundService::BACKGROUND_DEFAULT); | ||||
$currentVersion = (int)$this->config->getUserValue($user->getUID(), Application::APP_ID, 'userCacheBuster', '0'); | $currentVersion = (int)$this->config->getUserValue($user->getUID(), Application::APP_ID, 'userCacheBuster', '0'); | ||||
$isPrimaryBright = $this->util->invertTextColor($this->primaryColor); | |||||
// The user removed the background | // The user removed the background | ||||
if ($backgroundImage === BackgroundService::BACKGROUND_DISABLED) { | if ($backgroundImage === BackgroundService::BACKGROUND_DISABLED) { | ||||
return [ | return [ | ||||
'--image-background' => 'no', | '--image-background' => 'no', | ||||
'--color-background-plain' => $this->themingDefaults->getColorPrimary(), | |||||
'--color-background-plain' => $this->primaryColor, | |||||
// If no background image is set, we need to check against the shown primary colour | |||||
'--background-image-invert-if-bright' => $isPrimaryBright ? 'invert(100%)' : 'no', | |||||
]; | ]; | ||||
} | } | ||||
describe('Admin theming settings', function() { | describe('Admin theming settings', function() { | ||||
before(function() { | before(function() { | ||||
// Just in case previous test failed | // Just in case previous test failed | ||||
cy.resetTheming() | |||||
cy.resetAdminTheming() | |||||
cy.login(admin) | cy.login(admin) | ||||
}) | }) | ||||
let selectedColor = '' | let selectedColor = '' | ||||
before(function() { | before(function() { | ||||
// Just in case previous test failed | // Just in case previous test failed | ||||
cy.resetTheming() | |||||
cy.resetAdminTheming() | |||||
cy.login(admin) | cy.login(admin) | ||||
}) | }) | ||||
}) | }) | ||||
it('Undo theming settings', function() { | it('Undo theming settings', function() { | ||||
cy.resetTheming() | |||||
cy.resetAdminTheming() | |||||
}) | }) | ||||
it('Screenshot the login page', function() { | it('Screenshot the login page', function() { | ||||
describe('Remove the default background and restore it', function() { | describe('Remove the default background and restore it', function() { | ||||
before(function() { | before(function() { | ||||
// Just in case previous test failed | // Just in case previous test failed | ||||
cy.resetTheming() | |||||
cy.resetAdminTheming() | |||||
cy.login(admin) | cy.login(admin) | ||||
}) | }) | ||||
}) | }) | ||||
it('Undo theming settings', function() { | it('Undo theming settings', function() { | ||||
cy.resetTheming() | |||||
cy.resetAdminTheming() | |||||
}) | }) | ||||
it('Screenshot the login page', function() { | it('Screenshot the login page', function() { | ||||
}) | }) | ||||
}) | }) | ||||
describe.only('Remove the default background with a bright color', function() { | |||||
before(function() { | |||||
// Just in case previous test failed | |||||
cy.resetAdminTheming() | |||||
cy.resetUserTheming(admin) | |||||
cy.login(admin) | |||||
}) | |||||
it('See the admin theming section', function() { | |||||
cy.visit('/settings/admin/theming') | |||||
cy.get('[data-admin-theming-settings]').scrollIntoView().should('be.visible') | |||||
}) | |||||
it('Remove the default background', function() { | |||||
cy.intercept('*/apps/theming/ajax/updateStylesheet').as('removeBackground') | |||||
cy.get('[data-admin-theming-setting-file-remove]').click() | |||||
cy.wait('@removeBackground') | |||||
}) | |||||
it('Change the primary colour', 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() | |||||
cy.wait('@setColor') | |||||
cy.waitUntil(() => validateBodyThemingCss('#ddcb55', '')) | |||||
}) | |||||
it('See the header being inverted', function() { | |||||
cy.waitUntil(() => cy.window().then((win) => { | |||||
const firstEntry = win.document.querySelector('.app-menu-main li') | |||||
if (!firstEntry) { | |||||
return false | |||||
} | |||||
return getComputedStyle(firstEntry).filter === 'invert(1)' | |||||
})) | |||||
}) | |||||
}) | |||||
describe('Change the login fields then reset them', function() { | describe('Change the login fields then reset them', function() { | ||||
const name = 'ABCdef123' | const name = 'ABCdef123' | ||||
const url = 'https://example.com' | const url = 'https://example.com' | ||||
before(function() { | before(function() { | ||||
// Just in case previous test failed | // Just in case previous test failed | ||||
cy.resetTheming() | |||||
cy.resetAdminTheming() | |||||
cy.login(admin) | cy.login(admin) | ||||
}) | }) | ||||
}) | }) | ||||
it('Undo theming settings', function() { | it('Undo theming settings', function() { | ||||
cy.resetTheming() | |||||
cy.resetAdminTheming() | |||||
}) | }) | ||||
it('Check login screen changes', function() { | it('Check login screen changes', function() { | ||||
describe('Disable user theming and enable it back', function() { | describe('Disable user theming and enable it back', function() { | ||||
before(function() { | before(function() { | ||||
// Just in case previous test failed | // Just in case previous test failed | ||||
cy.resetTheming() | |||||
cy.resetAdminTheming() | |||||
cy.login(admin) | cy.login(admin) | ||||
}) | }) | ||||
before(function() { | before(function() { | ||||
// Just in case previous test failed | // Just in case previous test failed | ||||
cy.resetTheming() | |||||
cy.resetAdminTheming() | |||||
cy.login(admin) | cy.login(admin) | ||||
}) | }) | ||||
after(function() { | after(function() { | ||||
cy.resetTheming() | |||||
cy.resetAdminTheming() | |||||
}) | }) | ||||
it('See the admin theming section', function() { | it('See the admin theming section', function() { |
const defaultPrimary = '#006aa3' | const defaultPrimary = '#006aa3' | ||||
const defaultBackground = 'kamil-porembinski-clouds.jpg' | 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 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 colord(backgroundColor).isEqual(expectedPrimary) | |||||
&& backgroundImage.includes(expectedBackground) | |||||
&& invertIfBright === (bright ? 'invert(100%)' : 'no') | |||||
}) | |||||
} | |||||
import { pickRandomColor, validateBodyThemingCss } from './themingUtils' | |||||
describe('User default background settings', function() { | describe('User default background settings', function() { | ||||
before(function() { | before(function() { | ||||
}) | }) | ||||
}) | }) | ||||
describe('User select shipped backgrounds', function() { | |||||
describe('User select shipped backgrounds and remove background', function() { | |||||
before(function() { | before(function() { | ||||
cy.createRandomUser().then((user: User) => { | cy.createRandomUser().then((user: User) => { | ||||
cy.login(user) | cy.login(user) | ||||
// Validate changed background and primary | // Validate changed background and primary | ||||
cy.wait('@setBackground') | cy.wait('@setBackground') | ||||
cy.waitUntil(() => validateThemingCss('#a53c17', background)) | |||||
cy.waitUntil(() => validateBodyThemingCss('#a53c17', background)) | |||||
}) | }) | ||||
it('Select a bright shipped background', function() { | it('Select a bright shipped background', function() { | ||||
// Validate changed background and primary | // Validate changed background and primary | ||||
cy.wait('@setBackground') | cy.wait('@setBackground') | ||||
cy.waitUntil(() => validateThemingCss('#56633d', background, true)) | |||||
cy.waitUntil(() => validateBodyThemingCss('#56633d', background, true)) | |||||
}) | }) | ||||
it('Remove background', function() { | it('Remove background', function() { | ||||
// Validate clear background | // Validate clear background | ||||
cy.wait('@clearBackground') | cy.wait('@clearBackground') | ||||
cy.waitUntil(() => validateThemingCss('#56633d', '')) | |||||
cy.waitUntil(() => validateBodyThemingCss('#56633d', '')) | |||||
}) | }) | ||||
}) | }) | ||||
it('Select a custom color', function() { | it('Select a custom color', function() { | ||||
cy.intercept('*/apps/theming/background/color').as('setColor') | cy.intercept('*/apps/theming/background/color').as('setColor') | ||||
cy.get('[data-user-theming-background-color]').click() | |||||
cy.get('.color-picker__simple-color-circle:eq(3)').click() | |||||
pickRandomColor('[data-user-theming-background-color]') | |||||
// Validate clear background | |||||
// Validate custom colour change | |||||
cy.wait('@setColor') | cy.wait('@setColor') | ||||
cy.waitUntil(() => cy.window().then((win) => { | cy.waitUntil(() => cy.window().then((win) => { | ||||
const primary = getComputedStyle(win.document.body).getPropertyValue('--color-primary') | const primary = getComputedStyle(win.document.body).getPropertyValue('--color-primary') | ||||
}) | }) | ||||
}) | }) | ||||
describe('User select a bright custom color and remove background', function() { | |||||
before(function() { | |||||
cy.createRandomUser().then((user: 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('Remove background', function() { | |||||
cy.intercept('*/apps/theming/background/custom').as('clearBackground') | |||||
// Clear background | |||||
cy.get('[data-user-theming-background-clear]').click() | |||||
// Validate clear background | |||||
cy.wait('@clearBackground') | |||||
cy.waitUntil(() => validateBodyThemingCss(undefined, '')) | |||||
}) | |||||
it('Select a custom color', function() { | |||||
cy.intercept('*/apps/theming/background/color').as('setColor') | |||||
// Pick one of the bright color preset | |||||
cy.get('[data-user-theming-background-color]').click() | |||||
cy.get('.color-picker__simple-color-circle:eq(4)').click() | |||||
// Validate custom colour change | |||||
cy.wait('@setColor') | |||||
}) | |||||
it('See the header being inverted', function() { | |||||
cy.waitUntil(() => cy.window().then((win) => { | |||||
const firstEntry = win.document.querySelector('.app-menu-main li') | |||||
if (!firstEntry) { | |||||
return false | |||||
} | |||||
return getComputedStyle(firstEntry).filter === 'invert(1)' | |||||
})) | |||||
}) | |||||
it('Select a shipped background', function() { | |||||
const background = 'anatoly-mikhaltsov-butterfly-wing-scale.jpg' | |||||
cy.intercept('*/apps/theming/background/shipped').as('setBackground') | |||||
// Select background | |||||
cy.get(`[data-user-theming-background-shipped="${background}"]`).click() | |||||
// Validate changed background and primary | |||||
cy.wait('@setBackground') | |||||
cy.waitUntil(() => validateBodyThemingCss('#a53c17', background)) | |||||
}) | |||||
it('See the header NOT being inverted', 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' | |||||
})) | |||||
}) | |||||
}) | |||||
describe('User select a custom background', function() { | describe('User select a custom background', function() { | ||||
const image = 'image.jpg' | const image = 'image.jpg' | ||||
before(function() { | before(function() { | ||||
// Wait for background to be set | // Wait for background to be set | ||||
cy.wait('@setBackground') | cy.wait('@setBackground') | ||||
cy.waitUntil(() => validateThemingCss('#4c0c04', 'apps/theming/background?v=')) | |||||
cy.waitUntil(() => validateBodyThemingCss('#4c0c04', 'apps/theming/background?v=')) | |||||
}) | }) | ||||
}) | }) | ||||
// Wait for background to be set | // Wait for background to be set | ||||
cy.wait('@setBackground') | cy.wait('@setBackground') | ||||
cy.waitUntil(() => validateThemingCss(primaryFromImage, 'apps/theming/background?v=')) | |||||
cy.waitUntil(() => validateBodyThemingCss(primaryFromImage, 'apps/theming/background?v=')) | |||||
}) | }) | ||||
it('Select a custom color', function() { | it('Select a custom color', function() { | ||||
it('Reload the page and validate persistent changes', function() { | it('Reload the page and validate persistent changes', function() { | ||||
cy.reload() | cy.reload() | ||||
cy.waitUntil(() => validateThemingCss(selectedColor, 'apps/theming/background?v=')) | |||||
cy.waitUntil(() => validateBodyThemingCss(selectedColor, 'apps/theming/background?v=')) | |||||
}) | }) | ||||
}) | }) |
// eslint-disable-next-line @typescript-eslint/no-namespace | // eslint-disable-next-line @typescript-eslint/no-namespace | ||||
namespace Cypress { | namespace Cypress { | ||||
interface Chainable<Subject = any> { | interface Chainable<Subject = any> { | ||||
uploadFile(user: User, fixture?: string, mimeType?: string, target ?: string): Cypress.Chainable<void>, | |||||
resetTheming(): Cypress.Chainable<void>, | |||||
/** | |||||
* Upload a file from the fixtures folder to a given user storage. | |||||
* **Warning**: Using this function will reset the previous session | |||||
*/ | |||||
uploadFile(user: User, fixture?: string, mimeType?: string, target?: string): Cypress.Chainable<void>, | |||||
/** | |||||
* Reset the admin theming entirely. | |||||
* **Warning**: Using this function will reset the previous session | |||||
*/ | |||||
resetAdminTheming(): Cypress.Chainable<void>, | |||||
/** | |||||
* Reset the user theming settings. | |||||
* If provided, will clear session and login as the given user. | |||||
* **Warning**: Providing a user will reset the previous session. | |||||
*/ | |||||
resetUserTheming(user?: User): Cypress.Chainable<void>, | |||||
} | } | ||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* Reset the admin theming entirely | * Reset the admin theming entirely | ||||
*/ | */ | ||||
Cypress.Commands.add('resetTheming', () => { | |||||
Cypress.Commands.add('resetAdminTheming', () => { | |||||
const admin = new User('admin', 'admin') | const admin = new User('admin', 'admin') | ||||
cy.clearCookies() | cy.clearCookies() | ||||
// Clear admin session | // Clear admin session | ||||
cy.clearCookies() | cy.clearCookies() | ||||
}) | }) | ||||
/** | |||||
* Reset the current or provided user theming settings | |||||
* It does not reset the theme config as it is enforced in the | |||||
* server config for cypress testing. | |||||
*/ | |||||
Cypress.Commands.add('resetUserTheming', (user?: User) => { | |||||
if (user) { | |||||
cy.clearCookies() | |||||
cy.login(user) | |||||
} | |||||
// Reset background config | |||||
cy.request('/csrftoken').then(({ body }) => { | |||||
const requestToken = body.token | |||||
cy.request({ | |||||
method: 'POST', | |||||
url: '/apps/theming/background/default', | |||||
headers: { | |||||
'requesttoken': requestToken, | |||||
}, | |||||
}) | |||||
}) | |||||
if (user) { | |||||
// Clear current session | |||||
cy.clearCookies() | |||||
} | |||||
}) |