Skip to content

Commit 590647a

Browse files
committed
test(theme): add spec test for theme utils
1 parent 7316404 commit 590647a

File tree

2 files changed

+280
-9
lines changed

2 files changed

+280
-9
lines changed

core/src/utils/theme.spec.ts

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
import { generateComponentThemeCSS, generateCSSVars, generateGlobalThemeCSS, injectCSS } from './theme';
2+
3+
describe('generateCSSVars', () => {
4+
it('should not generate CSS variables for an empty theme', () => {
5+
const theme = {
6+
palette: {
7+
light: {},
8+
dark: {},
9+
},
10+
};
11+
12+
const css = generateCSSVars(theme);
13+
expect(css).toBe('');
14+
});
15+
16+
it('should generate CSS variables for a given theme', () => {
17+
const theme = {
18+
palette: {
19+
light: {},
20+
dark: {
21+
enabled: 'system',
22+
},
23+
},
24+
borderWidth: {
25+
sm: '4px',
26+
},
27+
spacing: {
28+
md: '12px',
29+
},
30+
scaling: {
31+
0: '0',
32+
},
33+
radii: {
34+
lg: '8px',
35+
},
36+
dynamicFont: '-apple-system-body',
37+
fontFamily: 'Roboto, "Helvetica Neue", sans-serif',
38+
fontWeights: {
39+
semiBold: '600',
40+
},
41+
fontSizes: {
42+
sm: '14px',
43+
md: '16px',
44+
},
45+
lineHeights: {
46+
sm: '1.2',
47+
},
48+
components: {},
49+
};
50+
51+
const css = generateCSSVars(theme);
52+
53+
expect(css).toContain('--ion-palette-dark-enabled: system;');
54+
expect(css).toContain('--ion-border-width-sm: 4px;');
55+
expect(css).toContain('--ion-spacing-md: 12px;');
56+
expect(css).toContain('--ion-scaling-0: 0;');
57+
expect(css).toContain('--ion-radii-lg: 8px;');
58+
expect(css).toContain('--ion-dynamic-font: -apple-system-body;');
59+
expect(css).toContain('--ion-font-family: Roboto, "Helvetica Neue", sans-serif;');
60+
expect(css).toContain('--ion-font-weights-semi-bold: 600;');
61+
expect(css).toContain('--ion-font-sizes-sm: 14px;');
62+
expect(css).toContain('--ion-font-sizes-sm-rem: 0.875rem;');
63+
expect(css).toContain('--ion-font-sizes-md: 16px;');
64+
expect(css).toContain('--ion-font-sizes-md-rem: 1rem;');
65+
expect(css).toContain('--ion-line-heights-sm: 1.2;');
66+
});
67+
});
68+
69+
describe('injectCSS', () => {
70+
it('should inject CSS into the head', () => {
71+
const css = 'body { background-color: red; }';
72+
injectCSS(css);
73+
expect(document.head.innerHTML).toContain(`<style>${css}</style>`);
74+
});
75+
});
76+
77+
describe('generateGlobalThemeCSS', () => {
78+
it('should generate global CSS for a given theme', () => {
79+
const theme = {
80+
palette: {
81+
light: {},
82+
dark: {
83+
enabled: 'never',
84+
},
85+
},
86+
borderWidth: {
87+
sm: '4px',
88+
},
89+
spacing: {
90+
md: '12px',
91+
},
92+
dynamicFont: '-apple-system-body',
93+
};
94+
95+
const css = generateGlobalThemeCSS(theme).replace(/\s/g, '');
96+
97+
const expectedCSS = `
98+
:root {
99+
--ion-border-width-sm: 4px;
100+
--ion-spacing-md: 12px;
101+
--ion-dynamic-font: -apple-system-body;
102+
}
103+
`.replace(/\s/g, '');
104+
105+
expect(css).toBe(expectedCSS);
106+
});
107+
108+
it('should generate global CSS for a given theme with light palette', () => {
109+
const theme = {
110+
palette: {
111+
light: {
112+
color: {
113+
primary: {
114+
bold: {
115+
base: '#0054e9',
116+
contrast: '#ffffff',
117+
shade: '#0041c4',
118+
tint: '#0065ff',
119+
},
120+
subtle: {
121+
base: '#0054e9',
122+
contrast: '#ffffff',
123+
shade: '#0041c4',
124+
tint: '#0065ff',
125+
},
126+
},
127+
red: {
128+
50: '#ffebee',
129+
100: '#ffcdd2',
130+
200: '#ef9a9a',
131+
},
132+
},
133+
},
134+
dark: {
135+
enabled: 'never',
136+
},
137+
},
138+
borderWidth: {
139+
sm: '4px',
140+
},
141+
spacing: {
142+
md: '12px',
143+
},
144+
dynamicFont: '-apple-system-body',
145+
};
146+
147+
const css = generateGlobalThemeCSS(theme).replace(/\s/g, '');
148+
149+
const expectedCSS = `
150+
:root {
151+
--ion-border-width-sm: 4px;
152+
--ion-spacing-md: 12px;
153+
--ion-dynamic-font: -apple-system-body;
154+
155+
--ion-color-primary-bold: #0054e9;
156+
--ion-color-primary-bold-contrast: #ffffff;
157+
--ion-color-primary-bold-shade: #0041c4;
158+
--ion-color-primary-bold-tint: #0065ff;
159+
--ion-color-primary-subtle: #0054e9;
160+
--ion-color-primary-subtle-contrast: #ffffff;
161+
--ion-color-primary-subtle-shade: #0041c4;
162+
--ion-color-primary-subtle-tint: #0065ff;
163+
--ion-color-red-50: #ffebee;
164+
--ion-color-red-100: #ffcdd2;
165+
--ion-color-red-200: #ef9a9a;
166+
}
167+
`.replace(/\s/g, '');
168+
169+
expect(css).toBe(expectedCSS);
170+
});
171+
172+
it('should generate global CSS for a given theme with dark palette enabled for system preference', () => {
173+
const theme = {
174+
palette: {
175+
light: {},
176+
dark: {
177+
enabled: 'system',
178+
color: {
179+
primary: {
180+
bold: {
181+
base: '#0054e9',
182+
contrast: '#ffffff',
183+
shade: '#0041c4',
184+
tint: '#0065ff',
185+
},
186+
subtle: {
187+
base: '#0054e9',
188+
contrast: '#ffffff',
189+
shade: '#0041c4',
190+
tint: '#0065ff',
191+
},
192+
},
193+
red: {
194+
50: '#ffebee',
195+
100: '#ffcdd2',
196+
200: '#ef9a9a',
197+
},
198+
},
199+
},
200+
},
201+
borderWidth: {
202+
sm: '4px',
203+
},
204+
spacing: {
205+
md: '12px',
206+
},
207+
dynamicFont: '-apple-system-body',
208+
};
209+
210+
const css = generateGlobalThemeCSS(theme).replace(/\s/g, '');
211+
212+
const expectedCSS = `
213+
:root {
214+
--ion-border-width-sm: 4px;
215+
--ion-spacing-md: 12px;
216+
--ion-dynamic-font: -apple-system-body;
217+
}
218+
219+
@media(prefers-color-scheme: dark) {
220+
:root {
221+
--ion-enabled: system;
222+
--ion-color-primary-bold: #0054e9;
223+
--ion-color-primary-bold-contrast: #ffffff;
224+
--ion-color-primary-bold-shade: #0041c4;
225+
--ion-color-primary-bold-tint: #0065ff;
226+
--ion-color-primary-subtle: #0054e9;
227+
--ion-color-primary-subtle-contrast: #ffffff;
228+
--ion-color-primary-subtle-shade: #0041c4;
229+
--ion-color-primary-subtle-tint: #0065ff;
230+
--ion-color-red-50: #ffebee;
231+
--ion-color-red-100: #ffcdd2;
232+
--ion-color-red-200: #ef9a9a;
233+
}
234+
}
235+
`.replace(/\s/g, '');
236+
237+
expect(css).toBe(expectedCSS);
238+
});
239+
});
240+
241+
describe('generateComponentThemeCSS', () => {
242+
it('should generate component theme CSS for a given theme', () => {
243+
const IonChip = {
244+
hue: {
245+
subtle: {
246+
bg: 'red',
247+
color: 'white',
248+
borderColor: 'black',
249+
},
250+
bold: {
251+
bg: 'blue',
252+
color: 'white',
253+
borderColor: 'black',
254+
},
255+
},
256+
};
257+
258+
const css = generateComponentThemeCSS(IonChip, 'chip').replace(/\s/g, '');
259+
260+
const expectedCSS = `
261+
:host(.chip-themed) {
262+
--ion-chip-hue-subtle-bg: red;
263+
--ion-chip-hue-subtle-color: white;
264+
--ion-chip-hue-subtle-border-color: black;
265+
--ion-chip-hue-bold-bg: blue;
266+
--ion-chip-hue-bold-color: white;
267+
--ion-chip-hue-bold-border-color: black;
268+
}
269+
`.replace(/\s/g, '');
270+
271+
expect(css).toBe(expectedCSS);
272+
});
273+
});

core/src/utils/theme.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import type { Color, CssClassMap } from '../interface';
2-
import type { Theme } from '../themes/base/default.tokens';
32

43
import { deepMerge } from './helpers';
54

6-
// Global constants
75
export const CSS_PROPS_PREFIX = '--ion-';
86
export const CSS_ROOT_SELECTOR = ':root';
97

@@ -47,7 +45,7 @@ export const getClassMap = (classes: string | string[] | undefined): CssClassMap
4745
* @param prefix The CSS prefix to use (e.g., '--ion-')
4846
* @returns CSS string with custom properties
4947
*/
50-
const generateCSSVars = (theme: any, prefix: string = CSS_PROPS_PREFIX): string => {
48+
export const generateCSSVars = (theme: any, prefix: string = CSS_PROPS_PREFIX): string => {
5149
const cssProps = Object.entries(theme)
5250
.flatMap(([key, val]) => {
5351
// Skip invalid keys or values
@@ -95,7 +93,7 @@ const generateCSSVars = (theme: any, prefix: string = CSS_PROPS_PREFIX): string
9593
* @param css The CSS string to inject
9694
* @param target The target element to inject into
9795
*/
98-
const injectCSS = (css: string, target: Element | ShadowRoot = document.head) => {
96+
export const injectCSS = (css: string, target: Element | ShadowRoot = document.head) => {
9997
const style = document.createElement('style');
10098
style.innerHTML = css;
10199
target.appendChild(style);
@@ -106,7 +104,7 @@ const injectCSS = (css: string, target: Element | ShadowRoot = document.head) =>
106104
* @param theme The theme object to generate CSS for
107105
* @returns The generated CSS string
108106
*/
109-
const generateGlobalThemeCSS = (theme: Theme): string => {
107+
export const generateGlobalThemeCSS = (theme: any): string => {
110108
if (typeof theme !== 'object' || Array.isArray(theme)) {
111109
console.warn('generateGlobalThemeCSS: Invalid theme object provided', theme);
112110
return '';
@@ -156,7 +154,7 @@ const generateGlobalThemeCSS = (theme: Theme): string => {
156154
* @param userTheme The user's custom theme (optional)
157155
* @returns The combined theme object (or base theme if no user theme was provided)
158156
*/
159-
export const applyGlobalTheme = (baseTheme: Theme, userTheme?: any): any => {
157+
export const applyGlobalTheme = (baseTheme: any, userTheme?: any): any => {
160158
// If no base theme provided, error
161159
if (typeof baseTheme !== 'object' || Array.isArray(baseTheme)) {
162160
console.error('applyGlobalTheme: Valid base theme object is required', baseTheme);
@@ -181,12 +179,12 @@ export const applyGlobalTheme = (baseTheme: Theme, userTheme?: any): any => {
181179
/**
182180
* Generates component's themed CSS class with CSS variables
183181
* from its theme object
184-
* @param theme The theme object to generate CSS for
182+
* @param componentTheme The component's object to generate CSS for (e.g., IonChip { })
185183
* @param componentName The component name without any prefixes (e.g., 'chip')
186184
* @returns string containing the component's themed CSS variables
187185
*/
188-
const generateComponentThemeCSS = (theme: Theme, componentName: string): string => {
189-
const cssProps = generateCSSVars(theme, `${CSS_PROPS_PREFIX}${componentName}-`);
186+
export const generateComponentThemeCSS = (componentTheme: any, componentName: string): string => {
187+
const cssProps = generateCSSVars(componentTheme, `${CSS_PROPS_PREFIX}${componentName}-`);
190188

191189
return `
192190
:host(.${componentName}-themed) {

0 commit comments

Comments
 (0)