mirror of
https://github.com/material-components/material-web.git
synced 2026-01-09 07:21:09 +08:00
chore: add errors and type assertions to sass-ext
PiperOrigin-RevId: 826549583
This commit is contained in:
parent
0a1f511ed8
commit
be012462c8
82
sass/ext/_assert.scss
Normal file
82
sass/ext/_assert.scss
Normal file
@ -0,0 +1,82 @@
|
||||
//
|
||||
// Copyright 2025 Google LLC
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
// Utility assert functions that throw errors.
|
||||
|
||||
// go/keep-sorted start by_regex='(.+) prefix_order=sass:
|
||||
@use 'sass:meta';
|
||||
@use 'throw';
|
||||
@use 'type';
|
||||
// go/keep-sorted end
|
||||
|
||||
/// Asserts that the argument is a specific type. If it is, the argument is
|
||||
/// returned, otherwise an error is thrown.
|
||||
///
|
||||
/// @example scss
|
||||
/// @mixin multiply($a, $b) {
|
||||
/// $a: assert.is-type($a, 'number');
|
||||
/// $b: assert.is-type($b, 'number');
|
||||
/// @return $a * $b;
|
||||
/// }
|
||||
///
|
||||
/// @function is-empty($value) {
|
||||
/// $value: assert.is-type(
|
||||
/// $value,
|
||||
/// 'list|map|null',
|
||||
/// $message: '$value must be a list, map, or null',
|
||||
/// $source: 'is-empty'
|
||||
/// );
|
||||
/// @return $value and list.length($value) == 0;
|
||||
/// }
|
||||
///
|
||||
/// @param {*} $arg - The argument to check.
|
||||
/// @param {string} $type - The string type to assert the argument matches.
|
||||
/// Multiple types may be separated by '|'.
|
||||
/// @param {string} $message - Optional custom error message.
|
||||
/// @param {string} $source - Optional source of the error message.
|
||||
/// @return {*} The argument if it matches the type string.
|
||||
/// @throw Error if the argument does not match the type string.
|
||||
@function is-type(
|
||||
$arg,
|
||||
$type,
|
||||
$message: 'Argument must be type #{meta.inspect($type)}. $arg: #{meta.inspect($arg)}',
|
||||
$source: 'assert.is-type'
|
||||
) {
|
||||
@if type.matches($arg, $type) {
|
||||
@return $arg;
|
||||
}
|
||||
@return throw.error($message, $source);
|
||||
}
|
||||
|
||||
/// Asserts that the argument is a specific type. If it is, the argument is
|
||||
/// returned, otherwise an error is thrown.
|
||||
///
|
||||
/// @example scss
|
||||
/// @function get-or-throw($map, $key) {
|
||||
/// @return assert.not-type(
|
||||
/// map.get($map, $key),
|
||||
/// 'null',
|
||||
/// $message: 'Key must be in the map'
|
||||
/// );
|
||||
/// }
|
||||
///
|
||||
/// @param {*} $arg - The argument to check.
|
||||
/// @param {string} $type - The string type to assert the argument does not
|
||||
/// match. Multiple types may be separated by '|'.
|
||||
/// @param {string} $message - Optional custom error message.
|
||||
/// @param {string} $source - Optional source of the error message.
|
||||
/// @return {*} The argument if it does not match the type string.
|
||||
/// @throw Error if the argument matches the type string.
|
||||
@function not-type(
|
||||
$arg,
|
||||
$type,
|
||||
$message: 'Argument may not be type #{meta.inspect($type)}. $arg: #{meta.inspect($arg)}',
|
||||
$source: 'assert.not-type'
|
||||
) {
|
||||
@if type.matches($arg, $type) {
|
||||
@return throw.error($message, $source);
|
||||
}
|
||||
@return $arg;
|
||||
}
|
||||
121
sass/ext/_assert_test.scss
Normal file
121
sass/ext/_assert_test.scss
Normal file
@ -0,0 +1,121 @@
|
||||
//
|
||||
// Copyright 2025 Google LLC
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
@use 'true' as test;
|
||||
|
||||
// go/keep-sorted start by_regex='(.+) prefix_order=sass:
|
||||
@use 'sass:string';
|
||||
@use 'assert';
|
||||
@use 'throw';
|
||||
// go/keep-sorted end
|
||||
|
||||
@include test.describe('assert') {
|
||||
// Value types
|
||||
$number: 1;
|
||||
$string: 'a-string';
|
||||
$color: red;
|
||||
$bool: true;
|
||||
$null: null;
|
||||
$list: ('list', 'of', 'values');
|
||||
$map: (
|
||||
'map': 'value',
|
||||
);
|
||||
|
||||
@include test.describe('is-type()') {
|
||||
@include test.it('returns the argument when it matches a single type') {
|
||||
@include test.assert-equal(assert.is-type($number, 'number'), $number);
|
||||
@include test.assert-equal(assert.is-type($string, 'string'), $string);
|
||||
@include test.assert-equal(assert.is-type($bool, 'bool'), $bool);
|
||||
@include test.assert-equal(assert.is-type($null, 'null'), $null);
|
||||
@include test.assert-equal(assert.is-type($list, 'list'), $list);
|
||||
@include test.assert-equal(assert.is-type($map, 'map'), $map);
|
||||
}
|
||||
|
||||
@include test.it(
|
||||
'returns the argument when it matches one of multiple types'
|
||||
) {
|
||||
@include test.assert-equal(
|
||||
assert.is-type($number, 'number|string'),
|
||||
$number
|
||||
);
|
||||
@include test.assert-equal(
|
||||
assert.is-type($string, 'number|string'),
|
||||
$string
|
||||
);
|
||||
@include test.assert-equal(assert.is-type($null, 'list|map|null'), $null);
|
||||
@include test.assert-equal(assert.is-type($list, 'list|map|null'), $list);
|
||||
@include test.assert-equal(assert.is-type($map, 'list|map|null'), $map);
|
||||
}
|
||||
|
||||
@include test.it('throws an error when it does not match the type') {
|
||||
@include test.assert-true(
|
||||
throw.get-error(assert.is-type($number, 'string')),
|
||||
'number should not match "string" type'
|
||||
);
|
||||
@include test.assert-true(
|
||||
throw.get-error(assert.is-type($string, 'number')),
|
||||
'string should not match "number" type'
|
||||
);
|
||||
@include test.assert-true(
|
||||
throw.get-error(assert.is-type($null, 'list|map')),
|
||||
'null should not match "list|map" type'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@include test.describe('not-type()') {
|
||||
@include test.it(
|
||||
'returns the argument when it does not match a single type'
|
||||
) {
|
||||
@include test.assert-equal(assert.not-type($number, 'string'), $number);
|
||||
@include test.assert-equal(assert.not-type($string, 'number'), $string);
|
||||
@include test.assert-equal(assert.not-type($bool, 'string'), $bool);
|
||||
@include test.assert-equal(assert.not-type($null, 'string'), $null);
|
||||
@include test.assert-equal(assert.not-type($list, 'string'), $list);
|
||||
@include test.assert-equal(assert.not-type($map, 'string'), $map);
|
||||
}
|
||||
|
||||
@include test.it(
|
||||
'returns the argument when it does not match one of multiple types'
|
||||
) {
|
||||
@include test.assert-equal(
|
||||
assert.not-type($number, 'string|map'),
|
||||
$number
|
||||
);
|
||||
@include test.assert-equal(
|
||||
assert.not-type($string, 'number|map'),
|
||||
$string
|
||||
);
|
||||
@include test.assert-equal(assert.not-type($null, 'list|map'), $null);
|
||||
}
|
||||
|
||||
@include test.it('throws an error when it matches the type') {
|
||||
@include test.assert-true(
|
||||
throw.get-error(assert.not-type($number, 'number')),
|
||||
'number should match "number" type and throw'
|
||||
);
|
||||
@include test.assert-true(
|
||||
throw.get-error(assert.not-type($string, 'string')),
|
||||
'string should match "string" type and throw'
|
||||
);
|
||||
@include test.assert-true(
|
||||
throw.get-error(assert.not-type($null, 'null')),
|
||||
'null should match "null" type and throw'
|
||||
);
|
||||
@include test.assert-true(
|
||||
throw.get-error(assert.not-type($number, 'number|string')),
|
||||
'number should match "number|string" type and throw'
|
||||
);
|
||||
@include test.assert-true(
|
||||
throw.get-error(assert.not-type($string, 'number|string')),
|
||||
'string should match "number|string" type and throw'
|
||||
);
|
||||
@include test.assert-true(
|
||||
throw.get-error(assert.not-type($null, 'list|map|null')),
|
||||
'null should match "list|map|null" type and throw'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
83
sass/ext/_throw.scss
Normal file
83
sass/ext/_throw.scss
Normal file
@ -0,0 +1,83 @@
|
||||
//
|
||||
// Copyright 2025 Google LLC
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
// Utilities for `sass-true` errors, to support testing error behavior.
|
||||
|
||||
// go/keep-sorted start by_regex='(.+) prefix_order=sass:
|
||||
@use 'sass:meta';
|
||||
@use 'sass:string';
|
||||
// go/keep-sorted end
|
||||
|
||||
@forward 'true' show error;
|
||||
|
||||
/// Returns false if none of the given values are error strings, or returns an
|
||||
/// error string if any value has an error.
|
||||
///
|
||||
/// This is used to support testing error behavior with `sass-true`, since
|
||||
/// `@error` messages cannot be caught at build time.
|
||||
///
|
||||
/// @example scss
|
||||
/// // A function that may return an "ERROR:" string in a test.
|
||||
/// @function get-value($map, $key) {
|
||||
/// @if meta.type-of($map) != 'map' {
|
||||
/// // Identical to `@error 'ERROR: Arg is not a map'` outside of tests.
|
||||
/// @return throw.error('Arg is not a map');
|
||||
/// }
|
||||
/// @return map.get($map, $key);
|
||||
/// }
|
||||
///
|
||||
/// // A function that needs to handle potential errors from other functions.
|
||||
/// @function mix-primary-on-surface($values) {
|
||||
/// $primary: get-value($values, 'primary');
|
||||
/// $surface: get-value($values, 'surface');
|
||||
/// $error: throw.get-error($primary, $secondary);
|
||||
/// @if $error {
|
||||
/// // Return early to guard logic against additional errors since
|
||||
/// // $primary or $secondary may be a string instead of a color.
|
||||
/// @return $error;
|
||||
/// }
|
||||
///
|
||||
/// @return color.mix($primary, $surface, 10%);
|
||||
/// }
|
||||
///
|
||||
/// Note: `throw.error()` and `throw.get-error()` are only useful when testing
|
||||
/// error behavior using `sass-true`. If you are not testing a function, use
|
||||
/// `@error` instead.
|
||||
///
|
||||
/// @example scss
|
||||
/// // In a `sass-true` test, `throw.get-error()` can be used to assert that
|
||||
/// // an error is thrown.
|
||||
/// @use 'true' as test with ($catch-errors: true);
|
||||
///
|
||||
/// @include test.describe('module.get-value()') {
|
||||
/// @include test.it('throws an error if the value is not a map') {
|
||||
/// $result: module.get-value('not a map', 'primary');
|
||||
/// @include test.assert-truthy(throw.get-error($result), '$result is an error');
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// @param {*} $error - The value to check.
|
||||
/// @param {list} $errors - Additional values to check. Useful for checking
|
||||
/// multiple errors at the same time.
|
||||
/// @return {string|boolean} The error string if any value is an error, or false
|
||||
/// otherwise.
|
||||
@function get-error($error, $errors...) {
|
||||
@if _is-error($error) {
|
||||
@return $error;
|
||||
}
|
||||
|
||||
@each $additional-error in $errors {
|
||||
@if _is-error($additional-error) {
|
||||
@return $additional-error;
|
||||
}
|
||||
}
|
||||
|
||||
@return false;
|
||||
}
|
||||
|
||||
@function _is-error($error) {
|
||||
@return (meta.type-of($error) == 'string') and
|
||||
(string.index($error, 'ERROR') == 1);
|
||||
}
|
||||
67
sass/ext/_throw_test.scss
Normal file
67
sass/ext/_throw_test.scss
Normal file
@ -0,0 +1,67 @@
|
||||
//
|
||||
// Copyright 2025 Google LLC
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
@use 'true' as test;
|
||||
|
||||
// go/keep-sorted start by_regex='(.+) prefix_order=sass:
|
||||
@use 'throw';
|
||||
// go/keep-sorted end
|
||||
|
||||
@include test.describe('throw') {
|
||||
@include test.describe('get-error()') {
|
||||
@include test.it('returns the string if the value is an error string') {
|
||||
$error: throw.error('test error message');
|
||||
@include test.assert-equal(throw.get-error($error), $error);
|
||||
}
|
||||
|
||||
@include test.it('returns null for non-error strings') {
|
||||
@include test.assert-false(
|
||||
throw.get-error('not an error'),
|
||||
'get-error("not an error") should return null for non-error strings'
|
||||
);
|
||||
}
|
||||
|
||||
@include test.it('returns null for other values') {
|
||||
@include test.assert-false(
|
||||
throw.get-error(1),
|
||||
'get-error(1) should return null'
|
||||
);
|
||||
@include test.assert-false(
|
||||
throw.get-error(true),
|
||||
'get-error(true) should return null'
|
||||
);
|
||||
@include test.assert-false(
|
||||
throw.get-error(null),
|
||||
'get-error(null) should return null'
|
||||
);
|
||||
@include test.assert-false(
|
||||
throw.get-error(()),
|
||||
'get-error(()) should return null'
|
||||
);
|
||||
}
|
||||
|
||||
@include test.it(
|
||||
'returns the first error if multiple values are provided'
|
||||
) {
|
||||
$error: throw.error('test error message');
|
||||
@include test.assert-equal(
|
||||
throw.get-error(
|
||||
'not an error',
|
||||
'still not an error',
|
||||
$error,
|
||||
'not an error either'
|
||||
),
|
||||
$error
|
||||
);
|
||||
}
|
||||
|
||||
@include test.it('returns null if multiple non-error values are provided') {
|
||||
@include test.assert-false(
|
||||
throw.get-error('not an error', 'still not an error'),
|
||||
'get-error("not an error", "still not an error") should return null'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
66
sass/ext/_type.scss
Normal file
66
sass/ext/_type.scss
Normal file
@ -0,0 +1,66 @@
|
||||
//
|
||||
// Copyright 2025 Google LLC
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
// Utilities for Sass type checking.
|
||||
|
||||
// go/keep-sorted start by_regex='(.+) prefix_order=sass:
|
||||
@use 'sass:meta';
|
||||
@use 'sass:string';
|
||||
@use 'throw';
|
||||
// go/keep-sorted end
|
||||
|
||||
/// Returns true if the given value matches the provided type string.
|
||||
///
|
||||
/// The type string supports multiple types separated by `|`, such as
|
||||
/// `'string|null'`. Type options are any values returned by `meta.type-of()`.
|
||||
///
|
||||
/// @example scss
|
||||
/// @function is-empty($value) {
|
||||
/// @if type.matches($value, 'list|map') {
|
||||
/// @return list.length($value) == 0;
|
||||
/// }
|
||||
/// @if type.matches($value, 'string') {
|
||||
/// @return $value == '';
|
||||
/// }
|
||||
/// @return type.matches($value, 'null');
|
||||
/// }
|
||||
///
|
||||
/// @param {*} $value - The value to check the type of.
|
||||
/// @param {string} $type-string - The type to check. May be multiple types
|
||||
/// separated by `|`.
|
||||
/// @return {boolean} True if the value matches the type string.
|
||||
@function matches($value, $type-string) {
|
||||
@if meta.type-of($type-string) != 'string' or $type-string == '' {
|
||||
@return throw.error(
|
||||
'$type-string must be a non-empty string',
|
||||
$source: 'type.matches'
|
||||
);
|
||||
}
|
||||
@if string.index($type-string, ' ') {
|
||||
@return throw.error(
|
||||
'$type-string may not contain spaces',
|
||||
$source: 'type.matches'
|
||||
);
|
||||
}
|
||||
@if string.index($type-string, 'boolean') {
|
||||
@return throw.error(
|
||||
'Use "bool" instead of "boolean"',
|
||||
$source: 'type.matches'
|
||||
);
|
||||
}
|
||||
|
||||
$value-type: meta.type-of($value);
|
||||
@if $value-type == $type-string {
|
||||
@return true;
|
||||
}
|
||||
|
||||
@each $type in string.split($type-string, '|') {
|
||||
@if $value-type == $type {
|
||||
@return true;
|
||||
}
|
||||
}
|
||||
|
||||
@return false;
|
||||
}
|
||||
115
sass/ext/_type_test.scss
Normal file
115
sass/ext/_type_test.scss
Normal file
@ -0,0 +1,115 @@
|
||||
//
|
||||
// Copyright 2025 Google LLC
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
@use 'true' as test;
|
||||
|
||||
// go/keep-sorted start by_regex='(.+) prefix_order=sass:
|
||||
@use 'throw';
|
||||
@use 'type';
|
||||
// go/keep-sorted end
|
||||
|
||||
@include test.describe('type') {
|
||||
@include test.describe('matches()') {
|
||||
@include test.it(
|
||||
'returns true when the value matches a single string type'
|
||||
) {
|
||||
@include test.assert-true(
|
||||
type.matches(1, 'number'),
|
||||
'1 should match "number" type'
|
||||
);
|
||||
@include test.assert-true(
|
||||
type.matches('foo', 'string'),
|
||||
'"foo" should match "string" type'
|
||||
);
|
||||
@include test.assert-true(
|
||||
type.matches(true, 'bool'),
|
||||
'true should match "bool" type'
|
||||
);
|
||||
@include test.assert-true(
|
||||
type.matches(null, 'null'),
|
||||
'null should match "null" type'
|
||||
);
|
||||
@include test.assert-true(
|
||||
type.matches((1, 2, 3), 'list'),
|
||||
'(1, 2, 3) should match "list" type'
|
||||
);
|
||||
$map: (
|
||||
'key': 'value',
|
||||
);
|
||||
@include test.assert-true(
|
||||
type.matches($map, 'map'),
|
||||
'("key": "value") should match "map" type'
|
||||
);
|
||||
}
|
||||
|
||||
@include test.it(
|
||||
'returns true when the value matches multiple string types'
|
||||
) {
|
||||
@include test.assert-true(
|
||||
type.matches(1, 'number|string'),
|
||||
'1 should match "number|string" type'
|
||||
);
|
||||
@include test.assert-true(
|
||||
type.matches('foo', 'number|string'),
|
||||
'"foo" should match "number|string" type'
|
||||
);
|
||||
@include test.assert-true(
|
||||
type.matches(null, 'number|string|null'),
|
||||
'null should match "number|string|null" type'
|
||||
);
|
||||
@include test.assert-true(
|
||||
type.matches((), 'list|map'),
|
||||
'() should match "list|map" type'
|
||||
);
|
||||
}
|
||||
|
||||
@include test.it('returns false when the value does not match any types') {
|
||||
@include test.assert-false(
|
||||
type.matches(1, 'string'),
|
||||
'1 should not match "string" type'
|
||||
);
|
||||
@include test.assert-false(
|
||||
type.matches('foo', 'number'),
|
||||
'"foo" should not match "number" type'
|
||||
);
|
||||
@include test.assert-false(
|
||||
type.matches(1, 'list|map|null'),
|
||||
'1 should not match "list|map|null" type'
|
||||
);
|
||||
}
|
||||
|
||||
@include test.it('throws an error when the type string is empty') {
|
||||
@include test.assert-true(
|
||||
throw.get-error(type.matches('foo', '')),
|
||||
'should throw error'
|
||||
);
|
||||
}
|
||||
|
||||
@include test.it('throws an error when the type string is not a string') {
|
||||
@include test.assert-true(
|
||||
throw.get-error(type.matches(1, 1)),
|
||||
'should throw error'
|
||||
);
|
||||
}
|
||||
|
||||
@include test.it('throws an error when the type string contains spaces') {
|
||||
@include test.assert-true(
|
||||
throw.get-error(type.matches('foo', 'number | string')),
|
||||
'should throw error'
|
||||
);
|
||||
}
|
||||
|
||||
@include test.it('throws an error if the type string uses "boolean" instead of "bool"') {
|
||||
@include test.assert-true(
|
||||
throw.get-error(type.matches(true, 'boolean')),
|
||||
'should throw error'
|
||||
);
|
||||
@include test.assert-true(
|
||||
throw.get-error(type.matches(1, 'number|boolean')),
|
||||
'should throw error'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -8,5 +8,8 @@
|
||||
);
|
||||
|
||||
// go/keep-sorted start
|
||||
@use 'assert_test';
|
||||
@use 'string_ext_test';
|
||||
@use 'throw_test';
|
||||
@use 'type_test';
|
||||
// go/keep-sorted end
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user