@micha.bigler/ui-core-micha 3.9.1 → 3.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,127 @@
1
+ import { darken, lighten } from '@mui/material/styles';
2
+ import { describe, expect, it } from 'vitest';
3
+
4
+ import {
5
+ assertThemeComplete,
6
+ calculateContrastRatio,
7
+ createAppTheme,
8
+ } from '../src/theme';
9
+ import {
10
+ BASELINE_PALETTE_DARK,
11
+ SERIES_COLOURS,
12
+ DARK_OVERLAY_SHADOW,
13
+ DARK_OVERLAY_SURFACE,
14
+ DARK_SCRIM,
15
+ } from '../src/theme/tokens';
16
+
17
+ const dark = (primary = '#468AB2', extra = {}) => createAppTheme({
18
+ palette: { mode: 'dark', primary: { main: primary }, ...extra },
19
+ });
20
+
21
+ describe('createAppTheme dark baseline', () => {
22
+ it('is complete, including a primary that must be lightened', () => {
23
+ expect(assertThemeComplete(dark()).findings).toEqual([]);
24
+ expect(assertThemeComplete(dark('#2F4F96')).findings).toEqual([]);
25
+ });
26
+
27
+ it('derives focus contrast against page and paper in both modes', () => {
28
+ for (const theme of [
29
+ createAppTheme({ palette: { primary: { main: '#0F62FE' } } }),
30
+ dark(),
31
+ ]) {
32
+ expect(calculateContrastRatio(theme.palette.controlBorder.focus, theme.palette.background.default)).toBeGreaterThanOrEqual(3);
33
+ expect(calculateContrastRatio(theme.palette.controlBorder.focus, theme.palette.background.paper)).toBeGreaterThanOrEqual(3);
34
+ }
35
+ });
36
+
37
+ it('derives dark control borders and focus from the rules', () => {
38
+ const theme = dark();
39
+ expect(theme.palette.controlBorder.main).toBe('rgba(230,235,239,0.38)');
40
+ expect(theme.palette.controlBorder.hover).toBe('rgba(230,235,239,0.53)');
41
+ expect(theme.palette.controlBorder.error).toBe('#E58B80');
42
+ expect(theme.palette.controlBorder.focus).toBe('#468AB2');
43
+ expect(dark('#2F4F96').palette.controlBorder.focus).toBe('#6D84B6');
44
+ const rgbInk = dark('#468AB2', { ink: { primary: 'rgb(230, 235, 239)' } });
45
+ expect(rgbInk.palette.controlBorder.main).toBe('rgba(230,235,239,0.38)');
46
+ const ownFocus = dark('#468AB2', { controlBorder: { focus: '#FFFFFF' } });
47
+ expect(ownFocus.palette.controlBorder.focus).toBe('#FFFFFF');
48
+ });
49
+
50
+ it('recomputes primary shades for a lightened primary', () => {
51
+ const theme = dark('#2F4F96');
52
+ expect(theme.palette.primary.light).toBe(lighten('#6D84B6', 0.2));
53
+ expect(theme.palette.primary.dark).toBe(darken('#6D84B6', 0.3));
54
+ });
55
+
56
+ it('uses the complete dark status family', () => {
57
+ for (const status of ['success', 'warning', 'error', 'info']) {
58
+ expect(dark().palette[status]).toMatchObject(BASELINE_PALETTE_DARK[status]);
59
+ }
60
+ expect(dark().palette.stale).toMatchObject(BASELINE_PALETTE_DARK.stale);
61
+ });
62
+
63
+ it.each([
64
+ ['#468AB2', '#468AB2'],
65
+ ['#2F4F96', '#6D84B6'],
66
+ ['#432CA1', '#8576C2'],
67
+ ['#D49040', '#D49040'],
68
+ ])('applies the dark primary rule to %s', (input, expected) => {
69
+ const theme = dark(input);
70
+ expect(theme.palette.primary.main).toBe(expected);
71
+ expect(theme.palette.primary.contrastText).toBe('#14181B');
72
+ });
73
+
74
+ it('keeps baseline statuses on webshop-guenter page and protects failing baseline values', () => {
75
+ const webshop = dark('#D49040', { background: { default: '#0E0B08', paper: '#171209' } });
76
+ for (const status of ['success', 'warning', 'error', 'info']) {
77
+ expect(webshop.palette[status].main).toBe(BASELINE_PALETTE_DARK[status].main);
78
+ }
79
+ const failing = dark('#468AB2', { background: { default: '#707070', paper: '#787878' } });
80
+ expect(failing.palette.success.main).not.toBe(BASELINE_PALETTE_DARK.success.main);
81
+ expect(calculateContrastRatio(failing.palette.success.main, failing.palette.background.default)).toBeGreaterThanOrEqual(4.5);
82
+ for (const status of ['success', 'warning', 'error', 'info']) {
83
+ const net = dark('#468AB2', { background: { default: '#707070', paper: '#787878' } }).palette[status];
84
+ expect(calculateContrastRatio(net.main, '#707070')).toBeGreaterThanOrEqual(4.5);
85
+ expect(calculateContrastRatio(net.main, net.bg)).toBeGreaterThanOrEqual(4.5);
86
+ }
87
+ const supplied = dark('#468AB2', {
88
+ background: { default: '#707070' },
89
+ success: { main: '#123456' },
90
+ });
91
+ expect(supplied.palette.success.main).toBe('#123456');
92
+ });
93
+
94
+ it('uses the dark overlay surface, shadow, scrim, and shadow token', () => {
95
+ const theme = dark();
96
+ for (const component of ['MuiDialog', 'MuiMenu', 'MuiPopover', 'MuiDrawer']) {
97
+ expect(theme.components[component].styleOverrides.paper).toMatchObject({
98
+ backgroundColor: DARK_OVERLAY_SURFACE,
99
+ boxShadow: DARK_OVERLAY_SHADOW,
100
+ });
101
+ }
102
+ expect(theme.components.MuiBackdrop.styleOverrides.root.backgroundColor).toBe(DARK_SCRIM);
103
+ expect(theme.shadow.overlay).toBe(DARK_OVERLAY_SHADOW);
104
+ });
105
+
106
+ it('lightens baseline series for baseline paper and adapts to app paper', () => {
107
+ const baseline = dark().palette.dataSeries.categorical;
108
+ expect(baseline).toEqual(['#506BA3', '#3E80B8', '#2E8F8A', '#7A5FA8', '#A1588C', '#8A7355']);
109
+ const adapted = dark('#D49040', { background: { paper: '#171209' } }).palette.dataSeries.categorical;
110
+ expect(adapted).toHaveLength(SERIES_COLOURS.length);
111
+ adapted.forEach((colour) => {
112
+ expect(calculateContrastRatio(colour, '#171209')).toBeGreaterThanOrEqual(3);
113
+ });
114
+ });
115
+
116
+ it('leaves app-supplied series untouched', () => {
117
+ const series = ['#111111', '#222222', '#333333'];
118
+ expect(dark('#468AB2', { dataSeries: { categorical: series } }).palette.dataSeries.categorical).toEqual(series);
119
+ });
120
+
121
+ it('resolves dark autofill from paper and primary ink', () => {
122
+ const input = dark().components.MuiOutlinedInput.styleOverrides.input['&:-webkit-autofill'];
123
+ expect(input.WebkitBoxShadow).toBe('0 0 0 100px #1B2126 inset');
124
+ expect(input.WebkitTextFillColor).toBe('#E6EBEF');
125
+ expect(input.caretColor).toBe('#E6EBEF');
126
+ });
127
+ });
@@ -0,0 +1,40 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { describe, expect, it } from 'vitest';
3
+
4
+ import { createAppTheme } from '../src/theme';
5
+
6
+ const sortValue = (value) => {
7
+ if (typeof value === 'function') return String(value);
8
+ if (Array.isArray(value)) return value.map(sortValue);
9
+ if (value && typeof value === 'object') {
10
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [key, sortValue(value[key])]));
11
+ }
12
+ return value;
13
+ };
14
+
15
+ const fixture = JSON.parse(readFileSync(new URL('./fixtures/light-theme-baseline.json', import.meta.url)));
16
+
17
+ function snapshot(baselinePalette = {}) {
18
+ return sortValue({
19
+ baseline: createAppTheme({ palette: { primary: { main: '#0F62FE' }, ...baselinePalette } }),
20
+ override: createAppTheme({
21
+ palette: {
22
+ primary: { main: '#0F62FE' },
23
+ background: { default: '#F0F2F4' },
24
+ success: { main: '#126B36' },
25
+ },
26
+ typography: { fontFamily: 'Fixture Sans' },
27
+ }),
28
+ });
29
+ }
30
+
31
+ describe('createAppTheme light regression guard', () => {
32
+ it('matches the pre-change serialized light themes exactly', () => {
33
+ expect(snapshot()).toEqual(fixture);
34
+ });
35
+
36
+ it('detects a changed light value', () => {
37
+ // Produced by the real factory: one different light value must differ from the fixture.
38
+ expect(snapshot({ background: { default: '#FAFBFC' } })).not.toEqual(fixture);
39
+ });
40
+ });