305 lines
10 KiB
TypeScript
305 lines
10 KiB
TypeScript
|
|
import {parse} from 'postcss';
|
||
|
|
import {compileString} from 'sass';
|
||
|
|
import {runfiles} from '@bazel/runfiles';
|
||
|
|
import * as path from 'path';
|
||
|
|
|
||
|
|
import {createLocalAngularPackageImporter} from '../../../../../tools/sass/local-sass-importer';
|
||
|
|
|
||
|
|
// Note: For Windows compatibility, we need to resolve the directory paths through runfiles
|
||
|
|
// which are guaranteed to reside in the source tree.
|
||
|
|
const testDir = path.join(runfiles.resolvePackageRelative('../_all-theme.scss'), '../tests');
|
||
|
|
const packagesDir = path.join(runfiles.resolveWorkspaceRelative('src/cdk/_index.scss'), '../..');
|
||
|
|
|
||
|
|
const localPackageSassImporter = createLocalAngularPackageImporter(packagesDir);
|
||
|
|
|
||
|
|
/** Transpiles given Sass content into CSS. */
|
||
|
|
function transpile(content: string) {
|
||
|
|
return compileString(
|
||
|
|
`
|
||
|
|
@use 'sass:list';
|
||
|
|
@use 'sass:map';
|
||
|
|
@use '../../../index' as mat;
|
||
|
|
|
||
|
|
$internals: _mat-theming-internals-do-not-access;
|
||
|
|
|
||
|
|
${content}
|
||
|
|
`,
|
||
|
|
{
|
||
|
|
loadPaths: [testDir],
|
||
|
|
importers: [localPackageSassImporter],
|
||
|
|
},
|
||
|
|
).css.toString();
|
||
|
|
}
|
||
|
|
|
||
|
|
function getRootVars(css: string) {
|
||
|
|
const result: {[key: string]: string} = {};
|
||
|
|
parse(css).each(node => {
|
||
|
|
if (node.type === 'rule' && node.selector === ':root') {
|
||
|
|
node.walk(child => {
|
||
|
|
if (child.type === 'decl') {
|
||
|
|
if (child.prop.startsWith('--')) {
|
||
|
|
result[child.prop.substring(2)] = child.value;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('theming definition api', () => {
|
||
|
|
describe('define-theme', () => {
|
||
|
|
it('should fill in defaults', () => {
|
||
|
|
const css = transpile(`
|
||
|
|
$theme: mat.define-theme();
|
||
|
|
$data: map.get($theme, $internals);
|
||
|
|
:root {
|
||
|
|
--keys: #{map.keys($data)};
|
||
|
|
--version: #{map.get($data, theme-version)};
|
||
|
|
--type: #{map.get($data, theme-type)};
|
||
|
|
--palettes: #{map.keys(map.get($data, palettes))};
|
||
|
|
--density: #{map.get($data, density-scale)};
|
||
|
|
--base-tokens: #{list.length(map.get($data, base-tokens)) > 0};
|
||
|
|
--color-tokens: #{list.length(map.get($data, color-tokens)) > 0};
|
||
|
|
--typography-tokens: #{list.length(map.get($data, typography-tokens)) > 0};
|
||
|
|
--density-tokens: #{list.length(map.get($data, density-tokens)) > 0};
|
||
|
|
--color-system-variables-prefix: #{map.get($data, color-system-variables-prefix)};
|
||
|
|
--typography-system-variables-prefix: #{map.get($data, typography-system-variables-prefix)};
|
||
|
|
}
|
||
|
|
`);
|
||
|
|
const vars = getRootVars(css);
|
||
|
|
expect(vars['keys'].split(', ')).toEqual([
|
||
|
|
'theme-version',
|
||
|
|
'theme-type',
|
||
|
|
'palettes',
|
||
|
|
'color-system-variables-prefix',
|
||
|
|
'color-tokens',
|
||
|
|
'font-definition',
|
||
|
|
'typography-system-variables-prefix',
|
||
|
|
'typography-tokens',
|
||
|
|
'density-scale',
|
||
|
|
'density-tokens',
|
||
|
|
'base-tokens',
|
||
|
|
]);
|
||
|
|
expect(vars['version']).toBe('1');
|
||
|
|
expect(vars['type']).toBe('light');
|
||
|
|
expect(vars['palettes'].split(', ')).toEqual([
|
||
|
|
'primary',
|
||
|
|
'secondary',
|
||
|
|
'tertiary',
|
||
|
|
'neutral',
|
||
|
|
'neutral-variant',
|
||
|
|
'error',
|
||
|
|
]);
|
||
|
|
expect(vars['density']).toBe('0');
|
||
|
|
expect(vars['base-tokens']).toBe('true');
|
||
|
|
expect(vars['color-tokens']).toBe('true');
|
||
|
|
expect(vars['typography-tokens']).toBe('true');
|
||
|
|
expect(vars['density-tokens']).toBe('true');
|
||
|
|
expect(vars['typography-system-variables-prefix']).toBe('mat-sys');
|
||
|
|
expect(vars['color-system-variables-prefix']).toBe('mat-sys');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should customize colors', () => {
|
||
|
|
const css = transpile(`
|
||
|
|
$theme: mat.define-theme((
|
||
|
|
color: (
|
||
|
|
theme-type: dark,
|
||
|
|
primary: mat.$yellow-palette,
|
||
|
|
tertiary: mat.$red-palette,
|
||
|
|
)
|
||
|
|
));
|
||
|
|
$data: map.get($theme, $internals);
|
||
|
|
:root {
|
||
|
|
--token-surface: #{map.get($data, color-tokens, (mdc, theme), surface)};
|
||
|
|
--token-primary: #{map.get($data, color-tokens, (mdc, theme), primary)};
|
||
|
|
--token-secondary: #{map.get($data, color-tokens, (mdc, theme), secondary)};
|
||
|
|
--token-tertiary: #{map.get($data, color-tokens, (mdc, theme), tertiary)};
|
||
|
|
--palette-primary: #{map.get($data, palettes, primary, 50)};
|
||
|
|
--palette-secondary: #{map.get($data, palettes, secondary, 50)};
|
||
|
|
--palette-tertiary: #{map.get($data, palettes, tertiary, 50)};
|
||
|
|
--type: #{map.get($data, theme-type)};
|
||
|
|
}
|
||
|
|
`);
|
||
|
|
const vars = getRootVars(css);
|
||
|
|
expect(vars['token-surface']).toBe('#14140f');
|
||
|
|
expect(vars['token-primary']).toBe('#cdcd00');
|
||
|
|
expect(vars['token-secondary']).toBe('#cac8a5');
|
||
|
|
expect(vars['token-tertiary']).toBe('#ffb4a8');
|
||
|
|
expect(vars['palette-primary']).toBe('#7b7b00');
|
||
|
|
expect(vars['palette-secondary']).toBe('#7a795a');
|
||
|
|
expect(vars['palette-tertiary']).toBe('#ef0000');
|
||
|
|
expect(vars['type']).toBe('dark');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should customize typography', () => {
|
||
|
|
const css = transpile(`
|
||
|
|
$theme: mat.define-theme((
|
||
|
|
typography: (
|
||
|
|
brand-family: Comic Sans,
|
||
|
|
plain-family: Wingdings,
|
||
|
|
bold-weight: 300,
|
||
|
|
medium-weight: 200,
|
||
|
|
regular-weight: 100,
|
||
|
|
)
|
||
|
|
));
|
||
|
|
$data: map.get($theme, $internals);
|
||
|
|
:root {
|
||
|
|
--display-font:
|
||
|
|
#{map.get($data, typography-tokens, (mdc, typography), display-large-font)};
|
||
|
|
--display-weight:
|
||
|
|
#{map.get($data, typography-tokens, (mdc, typography), display-large-weight)};
|
||
|
|
--title-font:
|
||
|
|
#{map.get($data, typography-tokens, (mdc, typography), title-small-font)};
|
||
|
|
--title-weight:
|
||
|
|
#{map.get($data, typography-tokens, (mdc, typography), title-small-weight)};
|
||
|
|
}
|
||
|
|
`);
|
||
|
|
const vars = getRootVars(css);
|
||
|
|
expect(vars['display-font']).toBe('Comic Sans');
|
||
|
|
expect(vars['display-weight']).toBe('100');
|
||
|
|
expect(vars['title-font']).toBe('Wingdings');
|
||
|
|
expect(vars['title-weight']).toBe('200');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should customize density', () => {
|
||
|
|
const css = transpile(`
|
||
|
|
$theme: mat.define-theme((
|
||
|
|
density: (
|
||
|
|
scale: -2
|
||
|
|
)
|
||
|
|
));
|
||
|
|
$data: map.get($theme, $internals);
|
||
|
|
:root {
|
||
|
|
--size: #{map.get($data, density-tokens, (mdc, checkbox), state-layer-size)};
|
||
|
|
}
|
||
|
|
`);
|
||
|
|
const vars = getRootVars(css);
|
||
|
|
expect(vars['size']).toBe('32px');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should throw for invalid system config', () => {
|
||
|
|
expect(() => transpile(`$theme: mat.define-theme(5)`)).toThrowError(
|
||
|
|
/\$config should be a configuration object\. Got: 5/,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should throw for invalid color config', () => {
|
||
|
|
expect(() => transpile(`$theme: mat.define-theme((color: 5))`)).toThrowError(
|
||
|
|
/\$config\.color should be a color configuration object\. Got: 5/,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should throw for invalid typography config', () => {
|
||
|
|
expect(() => transpile(`$theme: mat.define-theme((typography: 5))`)).toThrowError(
|
||
|
|
/\$config\.typography should be a typography configuration object\. Got: 5/,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should throw for invalid density config', () => {
|
||
|
|
expect(() => transpile(`$theme: mat.define-theme((density: 5))`)).toThrowError(
|
||
|
|
/\$config\.density should be a density configuration object\. Got: 5/,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should throw for invalid config property', () => {
|
||
|
|
expect(() => transpile(`$theme: mat.define-theme((fake: 5))`)).toThrowError(
|
||
|
|
/\$config has unexpected properties.*Found: fake/,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should throw for invalid color property', () => {
|
||
|
|
expect(() => transpile(`$theme: mat.define-theme((color: (fake: 5)))`)).toThrowError(
|
||
|
|
/\$config\.color has unexpected properties.*Found: fake/,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should throw for invalid typography property', () => {
|
||
|
|
expect(() => transpile(`$theme: mat.define-theme((typography: (fake: 5)))`)).toThrowError(
|
||
|
|
/\$config\.typography has unexpected properties.*Found: fake/,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should throw for invalid density property', () => {
|
||
|
|
expect(() => transpile(`$theme: mat.define-theme((density: (fake: 5)))`)).toThrowError(
|
||
|
|
/\$config\.density has unexpected properties.*Found: fake/,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should throw for invalid theme type', () => {
|
||
|
|
expect(() =>
|
||
|
|
transpile(`$theme: mat.define-theme((color: (theme-type: black)))`),
|
||
|
|
).toThrowError(/Expected \$config\.color.theme-type to be one of:.*Got: black/);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should throw for invalid palette', () => {
|
||
|
|
expect(() =>
|
||
|
|
transpile(`$theme: mat.define-theme((color: (tertiary: mat.$m2-red-palette)))`),
|
||
|
|
).toThrowError(/Expected \$config\.color\.tertiary to be a valid M3 palette\. Got:/);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should throw for invalid density scale', () => {
|
||
|
|
expect(() => transpile(`$theme: mat.define-theme((density: (scale: 10)))`)).toThrowError(
|
||
|
|
/Expected \$config\.density\.scale to be one of:.*Got: 10/,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('define-colors', () => {
|
||
|
|
it('should omit non-color info', () => {
|
||
|
|
const css = transpile(`
|
||
|
|
$theme: mat.define-colors();
|
||
|
|
$data: map.get($theme, $internals);
|
||
|
|
:root {
|
||
|
|
--keys: #{map.keys($data)};
|
||
|
|
}
|
||
|
|
`);
|
||
|
|
const vars = getRootVars(css);
|
||
|
|
expect(vars['keys'].split(', ')).toEqual([
|
||
|
|
'theme-version',
|
||
|
|
'theme-type',
|
||
|
|
'palettes',
|
||
|
|
'color-system-variables-prefix',
|
||
|
|
'color-tokens',
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('define-typography', () => {
|
||
|
|
it('should omit non-typography info', () => {
|
||
|
|
const css = transpile(`
|
||
|
|
$theme: mat.define-typography();
|
||
|
|
$data: map.get($theme, $internals);
|
||
|
|
:root {
|
||
|
|
--keys: #{map.keys($data)};
|
||
|
|
}
|
||
|
|
`);
|
||
|
|
const vars = getRootVars(css);
|
||
|
|
expect(vars['keys'].split(', ')).toEqual([
|
||
|
|
'theme-version',
|
||
|
|
'font-definition',
|
||
|
|
'typography-system-variables-prefix',
|
||
|
|
'typography-tokens',
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('define-density', () => {
|
||
|
|
it('should omit non-density info', () => {
|
||
|
|
const css = transpile(`
|
||
|
|
$theme: mat.define-density();
|
||
|
|
$data: map.get($theme, $internals);
|
||
|
|
:root {
|
||
|
|
--keys: #{map.keys($data)};
|
||
|
|
}
|
||
|
|
`);
|
||
|
|
const vars = getRootVars(css);
|
||
|
|
expect(vars['keys'].split(', ')).toEqual([
|
||
|
|
'theme-version',
|
||
|
|
'density-scale',
|
||
|
|
'density-tokens',
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|