Commit a85fd642 authored by David O'Regan's avatar David O'Regan

Merge branch '321929-fix-dark-mode-app-header-on-profile-preferences-page' into 'master'

Correctly style Dark Mode application header on profile preferences page

See merge request gitlab-org/gitlab!54575
parents 747a1396 7f3d3186
...@@ -44,6 +44,8 @@ export default { ...@@ -44,6 +44,8 @@ export default {
data() { data() {
return { return {
isSubmitEnabled: true, isSubmitEnabled: true,
darkModeOnCreate: null,
darkModeOnSubmit: null,
}; };
}, },
computed: { computed: {
...@@ -58,6 +60,7 @@ export default { ...@@ -58,6 +60,7 @@ export default {
this.formEl.addEventListener('ajax:beforeSend', this.handleLoading); this.formEl.addEventListener('ajax:beforeSend', this.handleLoading);
this.formEl.addEventListener('ajax:success', this.handleSuccess); this.formEl.addEventListener('ajax:success', this.handleSuccess);
this.formEl.addEventListener('ajax:error', this.handleError); this.formEl.addEventListener('ajax:error', this.handleError);
this.darkModeOnCreate = this.darkModeSelected();
}, },
beforeDestroy() { beforeDestroy() {
this.formEl.removeEventListener('ajax:beforeSend', this.handleLoading); this.formEl.removeEventListener('ajax:beforeSend', this.handleLoading);
...@@ -65,16 +68,27 @@ export default { ...@@ -65,16 +68,27 @@ export default {
this.formEl.removeEventListener('ajax:error', this.handleError); this.formEl.removeEventListener('ajax:error', this.handleError);
}, },
methods: { 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() { handleLoading() {
this.isSubmitEnabled = false; this.isSubmitEnabled = false;
this.darkModeOnSubmit = this.darkModeSelected();
}, },
handleSuccess(customEvent) { handleSuccess(customEvent) {
const formData = new FormData(this.formEl); // Reload the page if the theme has changed from light to dark mode or vice versa
updateClasses( // to correctly load all required styles.
this.bodyClasses, const modeChanged = this.darkModeOnCreate ? !this.darkModeOnSubmit : this.darkModeOnSubmit;
this.applicationThemes[formData.get('user[theme_id]')].css_class, if (modeChanged) {
this.selectedLayout, window.location.reload();
); return;
}
updateClasses(this.bodyClasses, this.getSelectedTheme().css_class, this.selectedLayout);
const { message = this.$options.i18n.defaultSuccess, type = FLASH_TYPES.NOTICE } = const { message = this.$options.i18n.defaultSuccess, type = FLASH_TYPES.NOTICE } =
customEvent?.detail?.[0] || {}; customEvent?.detail?.[0] || {};
createFlash({ message, type }); createFlash({ message, type });
......
...@@ -84,11 +84,11 @@ body { ...@@ -84,11 +84,11 @@ body {
&.gl-dark { &.gl-dark {
.logo-text svg { .logo-text svg {
fill: $gl-text-color; fill: var(--gl-text-color);
} }
.navbar-gitlab { .navbar-gitlab {
background-color: $gray-50; background-color: var(--gray-50);
.navbar-sub-nav, .navbar-sub-nav,
.navbar-nav { .navbar-nav {
...@@ -97,8 +97,8 @@ body { ...@@ -97,8 +97,8 @@ body {
> a:focus, > a:focus,
> button:hover, > button:hover,
> button:focus { > button:focus {
color: $gl-text-color; color: var(--gl-text-color);
background-color: $gray-200; background-color: var(--gray-200);
} }
} }
...@@ -106,21 +106,21 @@ body { ...@@ -106,21 +106,21 @@ body {
li.dropdown.show { li.dropdown.show {
> a, > a,
> button { > button {
color: $gl-text-color; color: var(--gl-text-color);
background-color: $gray-200; background-color: var(--gray-200);
} }
} }
} }
.search { .search {
form { form {
background-color: $gray-100; background-color: var(--gray-100);
box-shadow: inset 0 0 0 1px $border-color; box-shadow: inset 0 0 0 1px var(--border-color);
&:active, &:active,
&:hover { &:hover {
background-color: $gray-100; background-color: var(--gray-100);
box-shadow: inset 0 0 0 1px $blue-200; 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 { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import IntegrationView from '~/profile/preferences/components/integration_view.vue'; import IntegrationView from '~/profile/preferences/components/integration_view.vue';
import ProfilePreferences from '~/profile/preferences/components/profile_preferences.vue'; import ProfilePreferences from '~/profile/preferences/components/profile_preferences.vue';
import { i18n } from '~/profile/preferences/constants'; 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'; const expectedUrl = '/foo';
...@@ -14,7 +23,7 @@ describe('ProfilePreferences component', () => { ...@@ -14,7 +23,7 @@ describe('ProfilePreferences component', () => {
integrationViews: [], integrationViews: [],
userFields, userFields,
bodyClasses, bodyClasses,
themes: [{ id: 1, css_class: 'foo' }], themes,
profilePreferencesPath: '/update-profile', profilePreferencesPath: '/update-profile',
formEl: document.createElement('form'), formEl: document.createElement('form'),
}; };
...@@ -49,6 +58,30 @@ describe('ProfilePreferences component', () => { ...@@ -49,6 +58,30 @@ describe('ProfilePreferences component', () => {
return document.querySelector('.flash-container .flash-text'); 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(() => { beforeEach(() => {
setFixtures('<div class="flash-container"></div>'); setFixtures('<div class="flash-container"></div>');
}); });
...@@ -84,30 +117,15 @@ describe('ProfilePreferences component', () => { ...@@ -84,30 +117,15 @@ describe('ProfilePreferences component', () => {
let form; let form;
beforeEach(() => { beforeEach(() => {
const div = document.createElement('div'); setupBody();
div.classList.add('container-fluid'); form = createForm();
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);
wrapper = createComponent({ provide: { formEl: form }, attachTo: document.body }); wrapper = createComponent({ provide: { formEl: form }, attachTo: document.body });
const beforeSendEvent = new CustomEvent('ajax:beforeSend'); const beforeSendEvent = new CustomEvent('ajax:beforeSend');
form.dispatchEvent(beforeSendEvent); form.dispatchEvent(beforeSendEvent);
}); });
it('disables the submit button', async () => { it('disables the submit button', async () => {
await wrapper.vm.$nextTick(); await nextTick();
const button = findSubmitButton(); const button = findSubmitButton();
expect(button.props('disabled')).toBe(true); expect(button.props('disabled')).toBe(true);
}); });
...@@ -116,7 +134,7 @@ describe('ProfilePreferences component', () => { ...@@ -116,7 +134,7 @@ describe('ProfilePreferences component', () => {
const successEvent = new CustomEvent('ajax:success'); const successEvent = new CustomEvent('ajax:success');
form.dispatchEvent(successEvent); form.dispatchEvent(successEvent);
await wrapper.vm.$nextTick(); await nextTick();
const button = findSubmitButton(); const button = findSubmitButton();
expect(button.props('disabled')).toBe(false); expect(button.props('disabled')).toBe(false);
}); });
...@@ -125,7 +143,7 @@ describe('ProfilePreferences component', () => { ...@@ -125,7 +143,7 @@ describe('ProfilePreferences component', () => {
const errorEvent = new CustomEvent('ajax:error'); const errorEvent = new CustomEvent('ajax:error');
form.dispatchEvent(errorEvent); form.dispatchEvent(errorEvent);
await wrapper.vm.$nextTick(); await nextTick();
const button = findSubmitButton(); const button = findSubmitButton();
expect(button.props('disabled')).toBe(false); expect(button.props('disabled')).toBe(false);
}); });
...@@ -160,4 +178,89 @@ describe('ProfilePreferences component', () => { ...@@ -160,4 +178,89 @@ describe('ProfilePreferences component', () => {
expect(findFlashError().innerText.trim()).toEqual(message); 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 = { ...@@ -18,3 +18,15 @@ export const userFields = {
}; };
export const bodyClasses = 'ui-light-indigo ui-light gl-dark'; 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