Public Access
feat(settings): reduce animations switch
This commit is contained in:
1 parent
fa0857f55d
commit
2ec5833f29
9 files changed
+88
-12
No files matched your search
@@ -9,6 +9,7 @@ export const CORNERS = ['sharp', 'default', 'round'];
|
||||
// bannerAppid null: the dashboard banner shows the game played most in the last two weeks.
|
||||
export const DEFAULT_APPEARANCE = {
|
||||
accent: 'violet', corners: 'default', wideLayout: false, backgroundAppid: null, bannerAppid: null, showBanner: true,
|
||||
reduceMotion: false,
|
||||
};
|
||||
|
||||
export const appid = (value) => (Number.isInteger(value) && value > 0 ? value : null);
|
||||
@@ -22,6 +23,7 @@ export const normalizeAppearance = (value) => ({
|
||||
backgroundAppid: appid(value?.backgroundAppid),
|
||||
bannerAppid: appid(value?.bannerAppid),
|
||||
showBanner: value?.showBanner !== false,
|
||||
reduceMotion: value?.reduceMotion === true,
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -41,8 +43,14 @@ export const cacheAppearance = (appearance) => storage.set(STORAGE_KEY, JSON.str
|
||||
export const applyAppearance = (appearance, accentOverride = null, root = document.documentElement) => {
|
||||
root.dataset.accent = accentOverride ?? appearance.accent;
|
||||
root.dataset.corners = appearance.corners;
|
||||
root.toggleAttribute('data-reduce-motion', appearance.reduceMotion === true);
|
||||
};
|
||||
|
||||
/** True when the system asks for reduced motion or the "Reduce animations" setting is on. */
|
||||
export const reduceMotion = () =>
|
||||
(window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false)
|
||||
|| document.documentElement.hasAttribute('data-reduce-motion');
|
||||
|
||||
/**
|
||||
* PUT /api/users/settings/appearance on Auth. The caller handles the response status.
|
||||
*/
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
// @vitest-environment jsdom
|
||||
import {afterEach, describe, expect, it} from 'vitest';
|
||||
import {afterEach, describe, expect, it, vi} from 'vitest';
|
||||
import {
|
||||
applyAppearance,
|
||||
cacheAppearance,
|
||||
DEFAULT_APPEARANCE,
|
||||
normalizeAppearance,
|
||||
readCachedAppearance,
|
||||
reduceMotion,
|
||||
} from './appearance.js';
|
||||
|
||||
describe('normalizeAppearance', () => {
|
||||
it('keeps known values', () => {
|
||||
const appearance = {accent: 'blue', corners: 'round', wideLayout: true, backgroundAppid: 620, bannerAppid: 1086940, showBanner: false};
|
||||
const appearance = {accent: 'blue', corners: 'round', wideLayout: true, backgroundAppid: 620, bannerAppid: 1086940, showBanner: false, reduceMotion: true};
|
||||
|
||||
expect(normalizeAppearance(appearance)).toEqual(appearance);
|
||||
});
|
||||
|
||||
it('replaces unknown or missing values with the defaults', () => {
|
||||
expect(normalizeAppearance({accent: 'red;x', corners: 'pointy', wideLayout: 'yes', backgroundAppid: -1, bannerAppid: '620', showBanner: 'no'}))
|
||||
expect(normalizeAppearance({accent: 'red;x', corners: 'pointy', wideLayout: 'yes', backgroundAppid: -1, bannerAppid: '620', showBanner: 'no', reduceMotion: 'yes'}))
|
||||
.toEqual(DEFAULT_APPEARANCE);
|
||||
expect(normalizeAppearance(null)).toEqual(DEFAULT_APPEARANCE);
|
||||
expect(normalizeAppearance({backgroundAppid: 1.5})).toEqual(DEFAULT_APPEARANCE);
|
||||
@@ -27,9 +28,9 @@ describe('appearance cache', () => {
|
||||
afterEach(() => localStorage.clear());
|
||||
|
||||
it('reads back what was cached', () => {
|
||||
cacheAppearance({accent: 'green', corners: 'sharp', wideLayout: false, backgroundAppid: null, bannerAppid: 620, showBanner: true});
|
||||
cacheAppearance({accent: 'green', corners: 'sharp', wideLayout: false, backgroundAppid: null, bannerAppid: 620, showBanner: true, reduceMotion: false});
|
||||
|
||||
expect(readCachedAppearance()).toEqual({accent: 'green', corners: 'sharp', wideLayout: false, backgroundAppid: null, bannerAppid: 620, showBanner: true});
|
||||
expect(readCachedAppearance()).toEqual({accent: 'green', corners: 'sharp', wideLayout: false, backgroundAppid: null, bannerAppid: 620, showBanner: true, reduceMotion: false});
|
||||
});
|
||||
|
||||
it('falls back to the defaults for a broken or empty cache', () => {
|
||||
@@ -58,3 +59,41 @@ describe('applyAppearance', () => {
|
||||
expect(root.dataset.accent).toBe('gold');
|
||||
});
|
||||
});
|
||||
|
||||
describe('reduce motion', () => {
|
||||
afterEach(() => {
|
||||
document.documentElement.removeAttribute('data-reduce-motion');
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
const mediaQuery = (matches) => vi.stubGlobal('matchMedia', () => ({matches}));
|
||||
|
||||
it('is set on the root by applyAppearance and removed again', () => {
|
||||
const root = document.createElement('html');
|
||||
|
||||
applyAppearance({accent: 'blue', corners: 'default', reduceMotion: true}, null, root);
|
||||
expect(root.hasAttribute('data-reduce-motion')).toBe(true);
|
||||
|
||||
applyAppearance({accent: 'blue', corners: 'default', reduceMotion: false}, null, root);
|
||||
expect(root.hasAttribute('data-reduce-motion')).toBe(false);
|
||||
});
|
||||
|
||||
it('is on for the system media query', () => {
|
||||
mediaQuery(true);
|
||||
|
||||
expect(reduceMotion()).toBe(true);
|
||||
});
|
||||
|
||||
it('is on for the setting', () => {
|
||||
mediaQuery(false);
|
||||
document.documentElement.setAttribute('data-reduce-motion', '');
|
||||
|
||||
expect(reduceMotion()).toBe(true);
|
||||
});
|
||||
|
||||
it('is off for neither', () => {
|
||||
mediaQuery(false);
|
||||
|
||||
expect(reduceMotion()).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user