Commit 7f3d3186 authored by Simon Stieger's avatar Simon Stieger

Correctly style dark mode application header in profile preferences

Fix application header not being dark on the profile preferences
page with active dark mode, reload the page when switching from
light to dark mode (or vice versa) to correctly load all styles.
parent 4a1faf19
......@@ -44,6 +44,8 @@ export default {
data() {
return {
isSubmitEnabled: true,
darkModeOnCreate: null,
darkModeOnSubmit: null,
};
},
computed: {
......@@ -58,6 +60,7 @@ export default {
this.formEl.addEventListener('ajax:beforeSend', this.handleLoading);
this.formEl.addEventListener('ajax:success', this.handleSuccess);
this.formEl.addEventListener('ajax:error', this.handleError);
this.darkModeOnCreate = this.darkModeSelected();
},
beforeDestroy() {
this.formEl.removeEventListener('ajax:beforeSend', this.handleLoading);
......@@ -65,16 +68,27 @@ export default {
this.formEl.removeEventListener('ajax:error', this.handleError);
},
methods: {
darkModeSelected() {
const theme = this.getSelectedTheme();
return theme ? theme.css_class === 'gl-dark' : null;
},
getSelectedTheme() {
const themeId = new FormData(this.formEl).get('user[theme_id]');
return this.applicationThemes[themeId] ?? null;
},
handleLoading() {
this.isSubmitEnabled = false;
this.darkModeOnSubmit = this.darkModeSelected();
},
handleSuccess(customEvent) {
const formData = new FormData(this.formEl);
updateClasses(
this.bodyClasses,
this.applicationThemes[formData.get('user[theme_id]')].css_class,
this.selectedLayout,
);
// Reload the page if the theme has changed from light to dark mode or vice versa
// to correctly load all required styles.
const modeChanged = this.darkModeOnCreate ? !this.darkModeOnSubmit : this.darkModeOnSubmit;
if (modeChanged) {
window.location.reload();
return;
}
updateClasses(this.bodyClasses, this.getSelectedTheme().css_class, this.selectedLayout);
const { message = this.$options.i18n.defaultSuccess, type = FLASH_TYPES.NOTICE } =
customEvent?.detail?.[0] || {};
createFlash({ message, type });
......
......@@ -84,11 +84,11 @@ body {
&.gl-dark {
.logo-text svg {
fill: $gl-text-color;
fill: var(--gl-text-color);
}
.navbar-gitlab {
background-color: $gray-50;
background-color: var(--gray-50);
.navbar-sub-nav,
.navbar-nav {
......@@ -97,8 +97,8 @@ body {
> a:focus,
> button:hover,
> button:focus {
color: $gl-text-color;
background-color: $gray-200;
color: var(--gl-text-color);
background-color: var(--gray-200);
}
}
......@@ -106,21 +106,21 @@ body {
li.dropdown.show {
> a,
> button {
color: $gl-text-color;
background-color: $gray-200;
color: var(--gl-text-color);
background-color: var(--gray-200);
}
}
}
.search {
form {
background-color: $gray-100;
box-shadow: inset 0 0 0 1px $border-color;
background-color: var(--gray-100);
box-shadow: inset 0 0 0 1px var(--border-color);
&:active,
&:hover {
background-color: $gray-100;
box-shadow: inset 0 0 0 1px $blue-200;
background-color: var(--gray-100);
box-shadow: inset 0 0 0 1px var(--blue-200);
}
}
}
......
---
title: Correctly style Dark Mode application header in profile preferences
merge_request: 54575
author: Simon Stieger @sim0
type: fixed
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import IntegrationView from '~/profile/preferences/components/integration_view.vue';
import ProfilePreferences from '~/profile/preferences/components/profile_preferences.vue';
import { i18n } from '~/profile/preferences/constants';
import { integrationViews, userFields, bodyClasses } from '../mock_data';
import {
integrationViews,
userFields,
bodyClasses,
themes,
lightModeThemeId1,
darkModeThemeId,
lightModeThemeId2,
} from '../mock_data';
const expectedUrl = '/foo';
......@@ -14,7 +23,7 @@ describe('ProfilePreferences component', () => {
integrationViews: [],
userFields,
bodyClasses,
themes: [{ id: 1, css_class: 'foo' }],
themes,
profilePreferencesPath: '/update-profile',
formEl: document.createElement('form'),
};
......@@ -49,6 +58,30 @@ describe('ProfilePreferences component', () => {
return document.querySelector('.flash-container .flash-text');
}
function createThemeInput(themeId = lightModeThemeId1) {
const input = document.createElement('input');
input.setAttribute('name', 'user[theme_id]');
input.setAttribute('type', 'radio');
input.setAttribute('value', themeId.toString());
input.setAttribute('checked', 'checked');
return input;
}
function createForm(themeInput = createThemeInput()) {
const form = document.createElement('form');
form.setAttribute('url', expectedUrl);
form.setAttribute('method', 'put');
form.appendChild(themeInput);
return form;
}
function setupBody() {
const div = document.createElement('div');
div.classList.add('container-fluid');
document.body.appendChild(div);
document.body.classList.add('content-wrapper');
}
beforeEach(() => {
setFixtures('<div class="flash-container"></div>');
});
......@@ -84,30 +117,15 @@ describe('ProfilePreferences component', () => {
let form;
beforeEach(() => {
const div = document.createElement('div');
div.classList.add('container-fluid');
document.body.appendChild(div);
document.body.classList.add('content-wrapper');
form = document.createElement('form');
form.setAttribute('url', expectedUrl);
form.setAttribute('method', 'put');
const input = document.createElement('input');
input.setAttribute('name', 'user[theme_id]');
input.setAttribute('type', 'radio');
input.setAttribute('value', '1');
input.setAttribute('checked', 'checked');
form.appendChild(input);
setupBody();
form = createForm();
wrapper = createComponent({ provide: { formEl: form }, attachTo: document.body });
const beforeSendEvent = new CustomEvent('ajax:beforeSend');
form.dispatchEvent(beforeSendEvent);
});
it('disables the submit button', async () => {
await wrapper.vm.$nextTick();
await nextTick();
const button = findSubmitButton();
expect(button.props('disabled')).toBe(true);
});
......@@ -116,7 +134,7 @@ describe('ProfilePreferences component', () => {
const successEvent = new CustomEvent('ajax:success');
form.dispatchEvent(successEvent);
await wrapper.vm.$nextTick();
await nextTick();
const button = findSubmitButton();
expect(button.props('disabled')).toBe(false);
});
......@@ -125,7 +143,7 @@ describe('ProfilePreferences component', () => {
const errorEvent = new CustomEvent('ajax:error');
form.dispatchEvent(errorEvent);
await wrapper.vm.$nextTick();
await nextTick();
const button = findSubmitButton();
expect(button.props('disabled')).toBe(false);
});
......@@ -160,4 +178,89 @@ describe('ProfilePreferences component', () => {
expect(findFlashError().innerText.trim()).toEqual(message);
});
});
describe('theme changes', () => {
const { location } = window;
let themeInput;
let form;
function setupWrapper() {
wrapper = createComponent({ provide: { formEl: form }, attachTo: document.body });
}
function selectThemeId(themeId) {
themeInput.setAttribute('value', themeId.toString());
}
function dispatchBeforeSendEvent() {
const beforeSendEvent = new CustomEvent('ajax:beforeSend');
form.dispatchEvent(beforeSendEvent);
}
function dispatchSuccessEvent() {
const successEvent = new CustomEvent('ajax:success');
form.dispatchEvent(successEvent);
}
beforeAll(() => {
delete window.location;
window.location = {
...location,
reload: jest.fn(),
};
});
afterAll(() => {
window.location = location;
});
beforeEach(() => {
setupBody();
themeInput = createThemeInput();
form = createForm(themeInput);
});
it('reloads the page when switching from light to dark mode', async () => {
selectThemeId(lightModeThemeId1);
setupWrapper();
selectThemeId(darkModeThemeId);
dispatchBeforeSendEvent();
await nextTick();
dispatchSuccessEvent();
await nextTick();
expect(window.location.reload).toHaveBeenCalledTimes(1);
});
it('reloads the page when switching from dark to light mode', async () => {
selectThemeId(darkModeThemeId);
setupWrapper();
selectThemeId(lightModeThemeId1);
dispatchBeforeSendEvent();
await nextTick();
dispatchSuccessEvent();
await nextTick();
expect(window.location.reload).toHaveBeenCalledTimes(1);
});
it('does not reload the page when switching between light mode themes', async () => {
selectThemeId(lightModeThemeId1);
setupWrapper();
selectThemeId(lightModeThemeId2);
dispatchBeforeSendEvent();
await nextTick();
dispatchSuccessEvent();
await nextTick();
expect(window.location.reload).not.toHaveBeenCalled();
});
});
});
......@@ -18,3 +18,15 @@ export const userFields = {
};
export const bodyClasses = 'ui-light-indigo ui-light gl-dark';
export const themes = [
{ id: 1, css_class: 'foo' },
{ id: 2, css_class: 'bar' },
{ id: 3, css_class: 'gl-dark' },
];
export const lightModeThemeId1 = 1;
export const lightModeThemeId2 = 2;
export const darkModeThemeId = 3;
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment