sass-references/angular-material/material/core/theming/tests/theming-definition-api.spec.ts

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',
]);
});
});
});