Merge pull request #1129 from sass/feature.hwb

Add support for HWB functions
This commit is contained in:
Natalie Weizenbaum 2020-10-29 15:42:35 -07:00 committed by GitHub
commit 28a582f45e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 348 additions and 172 deletions

View File

@ -1,3 +1,31 @@
## 1.28.0
* Add a [`color.hwb()`] function to `sass:color` that can express colors in [HWB] format.
[`color.hwb()`]: https://sass-lang.com/documentation/modules/color#hwb
[HWB]: https://en.wikipedia.org/wiki/HWB_color_model
* Add [`color.whiteness()`] and [`color.blackness()`] functions to `sass:color`
to get a color's [HWB] whiteness and blackness components.
[`color.whiteness()`]: https://sass-lang.com/documentation/modules/color#whiteness
[`color.blackness()`]: https://sass-lang.com/documentation/modules/color#blackness
* Add `$whiteness` and `$blackness` parameters to [`color.adjust()`],
[`color.change()`], and [`color.scale()`] to modify a color's [HWB] whiteness
and blackness components.
[`color.adjust()`]: https://sass-lang.com/documentation/modules/color#adjust
[`color.change()`]: https://sass-lang.com/documentation/modules/color#change
[`color.scale()`]: https://sass-lang.com/documentation/modules/color#scale
### Dart API
* Add [HWB] support to the `SassColor` class, including a `SassColor.hwb()`
constructor, `whiteness` and `blackness` getters, and a `changeHwb()` method.
[HWB]: https://en.wikipedia.org/wiki/HWB_color_model
## 1.27.2
* No user-visible changes.

View File

@ -265,6 +265,34 @@ final module = BuiltInModule("color", functions: [
return color.changeHsl(saturation: 0);
}),
// ### HWB
BuiltInCallable.overloadedFunction("hwb", {
r"$hue, $whiteness, $blackness, $alpha: 1": (arguments) => _hwb(arguments),
r"$channels": (arguments) {
var parsed = _parseChannels(
"hwb", [r"$hue", r"$whiteness", r"$blackness"], arguments.first);
// `hwb()` doesn't (currently) support special number or variable strings.
if (parsed is SassString) {
throw SassScriptException('Expected numeric channels, got "$parsed".');
} else {
return _hwb(parsed as List<Value>);
}
}
}),
_function(
"whiteness",
r"$color",
(arguments) =>
SassNumber(arguments.first.assertColor("color").whiteness, "%")),
_function(
"blackness",
r"$color",
(arguments) =>
SassNumber(arguments.first.assertColor("color").blackness, "%")),
// ### Opacity
_removedColorFunction("opacify", "alpha"),
_removedColorFunction("fade-in", "alpha"),
@ -368,165 +396,14 @@ final _complement = _function("complement", r"$color", (arguments) {
// Miscellaneous
final _adjust = _function("adjust", r"$color, $kwargs...", (arguments) {
var color = arguments[0].assertColor("color");
var argumentList = arguments[1] as SassArgumentList;
if (argumentList.asList.isNotEmpty) {
throw SassScriptException(
"Only one positional argument is allowed. All other arguments must "
"be passed by name.");
}
final _adjust = _function("adjust", r"$color, $kwargs...",
(arguments) => _updateComponents(arguments, adjust: true));
var keywords = Map.of(argumentList.keywords);
num getInRange(String name, num min, num max) =>
keywords.remove(name)?.assertNumber(name)?.valueInRange(min, max, name);
final _scale = _function("scale", r"$color, $kwargs...",
(arguments) => _updateComponents(arguments, scale: true));
var red = _fuzzyRoundOrNull(getInRange("red", -255, 255));
var green = _fuzzyRoundOrNull(getInRange("green", -255, 255));
var blue = _fuzzyRoundOrNull(getInRange("blue", -255, 255));
var hue = keywords.remove("hue")?.assertNumber("hue")?.value;
var saturation = getInRange("saturation", -100, 100);
var lightness = getInRange("lightness", -100, 100);
var alpha = getInRange("alpha", -1, 1);
if (keywords.isNotEmpty) {
throw SassScriptException(
"No ${pluralize('argument', keywords.length)} named "
"${toSentence(keywords.keys.map((name) => "\$$name"), 'or')}.");
}
var hasRgb = red != null || green != null || blue != null;
var hasHsl = hue != null || saturation != null || lightness != null;
if (hasRgb) {
if (hasHsl) {
throw SassScriptException(
"RGB parameters may not be passed along with HSL parameters.");
}
return color.changeRgb(
red: (color.red + (red ?? 0)).clamp(0, 255) as int,
green: (color.green + (green ?? 0)).clamp(0, 255) as int,
blue: (color.blue + (blue ?? 0)).clamp(0, 255) as int,
alpha: (color.alpha + (alpha ?? 0)).clamp(0, 1));
} else if (hasHsl) {
return color.changeHsl(
hue: color.hue + (hue ?? 0),
saturation: (color.saturation + (saturation ?? 0)).clamp(0, 100),
lightness: (color.lightness + (lightness ?? 0)).clamp(0, 100),
alpha: (color.alpha + (alpha ?? 0)).clamp(0, 1));
} else if (alpha != null) {
return color.changeAlpha((color.alpha + (alpha ?? 0)).clamp(0, 1));
} else {
return color;
}
});
final _scale = _function("scale", r"$color, $kwargs...", (arguments) {
var color = arguments[0].assertColor("color");
var argumentList = arguments[1] as SassArgumentList;
if (argumentList.asList.isNotEmpty) {
throw SassScriptException(
"Only one positional argument is allowed. All other arguments must "
"be passed by name.");
}
var keywords = Map.of(argumentList.keywords);
num getScale(String name) {
var value = keywords.remove(name);
if (value == null) return null;
var number = value.assertNumber(name);
number.assertUnit("%", name);
return number.valueInRange(-100, 100, name) / 100;
}
num scaleValue(num current, num scale, num max) {
if (scale == null) return current;
return current + (scale > 0 ? max - current : current) * scale;
}
var red = getScale("red");
var green = getScale("green");
var blue = getScale("blue");
var saturation = getScale("saturation");
var lightness = getScale("lightness");
var alpha = getScale("alpha");
if (keywords.isNotEmpty) {
throw SassScriptException(
"No ${pluralize('argument', keywords.length)} named "
"${toSentence(keywords.keys.map((name) => "\$$name"), 'or')}.");
}
var hasRgb = red != null || green != null || blue != null;
var hasHsl = saturation != null || lightness != null;
if (hasRgb) {
if (hasHsl) {
throw SassScriptException(
"RGB parameters may not be passed along with HSL parameters.");
}
return color.changeRgb(
red: fuzzyRound(scaleValue(color.red, red, 255)),
green: fuzzyRound(scaleValue(color.green, green, 255)),
blue: fuzzyRound(scaleValue(color.blue, blue, 255)),
alpha: scaleValue(color.alpha, alpha, 1));
} else if (hasHsl) {
return color.changeHsl(
saturation: scaleValue(color.saturation, saturation, 100),
lightness: scaleValue(color.lightness, lightness, 100),
alpha: scaleValue(color.alpha, alpha, 1));
} else if (alpha != null) {
return color.changeAlpha(scaleValue(color.alpha, alpha, 1));
} else {
return color;
}
});
final _change = _function("change", r"$color, $kwargs...", (arguments) {
var color = arguments[0].assertColor("color");
var argumentList = arguments[1] as SassArgumentList;
if (argumentList.asList.isNotEmpty) {
throw SassScriptException(
"Only one positional argument is allowed. All other arguments must "
"be passed by name.");
}
var keywords = Map.of(argumentList.keywords);
num getInRange(String name, num min, num max) =>
keywords.remove(name)?.assertNumber(name)?.valueInRange(min, max, name);
var red = _fuzzyRoundOrNull(getInRange("red", 0, 255));
var green = _fuzzyRoundOrNull(getInRange("green", 0, 255));
var blue = _fuzzyRoundOrNull(getInRange("blue", 0, 255));
var hue = keywords.remove("hue")?.assertNumber("hue")?.value;
var saturation = getInRange("saturation", 0, 100);
var lightness = getInRange("lightness", 0, 100);
var alpha = getInRange("alpha", 0, 1);
if (keywords.isNotEmpty) {
throw SassScriptException(
"No ${pluralize('argument', keywords.length)} named "
"${toSentence(keywords.keys.map((name) => "\$$name"), 'or')}.");
}
var hasRgb = red != null || green != null || blue != null;
var hasHsl = hue != null || saturation != null || lightness != null;
if (hasRgb) {
if (hasHsl) {
throw SassScriptException(
"RGB parameters may not be passed along with HSL parameters.");
}
return color.changeRgb(red: red, green: green, blue: blue, alpha: alpha);
} else if (hasHsl) {
return color.changeHsl(
hue: hue, saturation: saturation, lightness: lightness, alpha: alpha);
} else if (alpha != null) {
return color.changeAlpha(alpha);
} else {
return color;
}
});
final _change = _function("change", r"$color, $kwargs...",
(arguments) => _updateComponents(arguments, change: true));
final _ieHexStr = _function("ie-hex-str", r"$color", (arguments) {
var color = arguments[0].assertColor("color");
@ -538,6 +415,102 @@ final _ieHexStr = _function("ie-hex-str", r"$color", (arguments) {
quotes: false);
});
/// Implementation for `color.change`, `color.adjust`, and `color.scale`.
///
/// Exactly one of [change], [adjust], and [scale] must be true to determine
/// which function should be executed.
SassColor _updateComponents(List<Value> arguments,
{bool change = false, bool adjust = false, bool scale = false}) {
assert([change, adjust, scale].where((x) => x).length == 1);
var color = arguments[0].assertColor("color");
var argumentList = arguments[1] as SassArgumentList;
if (argumentList.asList.isNotEmpty) {
throw SassScriptException(
"Only one positional argument is allowed. All other arguments must "
"be passed by name.");
}
var keywords = Map.of(argumentList.keywords);
/// Gets and validates the parameter with [name] from keywords.
///
/// [max] should be 255 for RGB channels, 1 for the alpha channel, and 100
/// for saturation, lightness, whiteness, and blackness.
num getParam(String name, num max, {bool assertPercent = false}) {
var number = keywords.remove(name)?.assertNumber(name);
if (number == null) return null;
if (scale || assertPercent) number.assertUnit("%", name);
if (scale) max = 100;
return number.valueInRange(change ? 0 : -max, max, name);
}
var alpha = getParam("alpha", 1);
var red = getParam("red", 255);
var green = getParam("green", 255);
var blue = getParam("blue", 255);
var hue = scale ? null : keywords.remove("hue")?.assertNumber("hue")?.value;
var saturation = getParam("saturation", 100);
var lightness = getParam("lightness", 100);
var whiteness = getParam("whiteness", 100, assertPercent: true);
var blackness = getParam("blackness", 100, assertPercent: true);
if (keywords.isNotEmpty) {
throw SassScriptException(
"No ${pluralize('argument', keywords.length)} named "
"${toSentence(keywords.keys.map((name) => '\$$name'), 'or')}.");
}
var hasRgb = red != null || green != null || blue != null;
var hasSL = saturation != null || lightness != null;
var hasWB = whiteness != null || blackness != null;
if (hasRgb && (hasSL || hasWB || hue != null)) {
throw SassScriptException("RGB parameters may not be passed along with "
"${hasWB ? 'HWB' : 'HSL'} parameters.");
}
if (hasSL && hasWB) {
throw SassScriptException(
"HSL parameters may not be passed along with HWB parameters.");
}
/// Updates [current] based on [param], clamped within [max].
num updateValue(num current, num param, num max) {
if (param == null) return current;
if (change) return param;
if (adjust) return (current + param).clamp(0, max);
return current + (param > 0 ? max - current : current) * (param / 100);
}
int updateRgb(int current, num param) =>
fuzzyRound(updateValue(current, param, 255));
if (hasRgb) {
return color.changeRgb(
red: updateRgb(color.red, red),
green: updateRgb(color.green, green),
blue: updateRgb(color.blue, blue),
alpha: updateValue(color.alpha, alpha, 1));
} else if (hasWB) {
return color.changeHwb(
hue: change ? hue : color.hue + (hue ?? 0),
whiteness: updateValue(color.whiteness, whiteness, 100),
blackness: updateValue(color.blackness, blackness, 100),
alpha: updateValue(color.alpha, alpha, 1));
} else if (hue != null || hasSL) {
return color.changeHsl(
hue: change ? hue : color.hue + (hue ?? 0),
saturation: updateValue(color.saturation, saturation, 100),
lightness: updateValue(color.lightness, lightness, 100),
alpha: updateValue(color.alpha, alpha, 1));
} else if (alpha != null) {
return color.changeAlpha(updateValue(color.alpha, alpha, 1));
} else {
return color;
}
}
/// Returns a string representation of [name] called with [arguments], as though
/// it were a plain CSS function.
SassString _functionString(String name, Iterable<Value> arguments) =>
@ -636,6 +609,25 @@ Value _hsl(String name, List<Value> arguments) {
: _percentageOrUnitless(alpha.assertNumber("alpha"), 1, "alpha"));
}
/// Create an HWB color from the given [arguments].
Value _hwb(List<Value> arguments) {
var alpha = arguments.length > 3 ? arguments[3] : null;
var hue = arguments[0].assertNumber("hue");
var whiteness = arguments[1].assertNumber("whiteness");
var blackness = arguments[2].assertNumber("blackness");
whiteness.assertUnit("%", "whiteness");
blackness.assertUnit("%", "whiteness");
return SassColor.hwb(
hue.value,
whiteness.valueInRange(0, 100, "whiteness"),
blackness.valueInRange(0, 100, "whiteness"),
alpha == null
? null
: _percentageOrUnitless(alpha.assertNumber("alpha"), 1, "alpha"));
}
Object /* SassString | List<Value> */ _parseChannels(
String name, List<String> argumentNames, Value channels) {
if (channels.isVar) return _functionString(name, [channels]);
@ -773,9 +765,6 @@ SassColor _transparentize(List<Value> arguments) {
(color.alpha - amount.valueInRange(0, 1, "amount")).clamp(0, 1));
}
/// Like [fuzzyRound], but returns `null` if [number] is `null`.
int _fuzzyRoundOrNull(num number) => number == null ? null : fuzzyRound(number);
/// Like [new BuiltInCallable.function], but always sets the URL to
/// `sass:color`.
BuiltInCallable _function(

View File

@ -55,6 +55,20 @@ class SassColor extends Value implements ext.SassColor {
num _lightness;
num get whiteness {
// Because HWB is (currently) used much less frequently than HSL or RGB, we
// don't cache its values because we expect the memory overhead of doing so
// to outweigh the cost of recalculating it on access.
return math.min(math.min(red, green), blue) / 255 * 100;
}
num get blackness {
// Because HWB is (currently) used much less frequently than HSL or RGB, we
// don't cache its values because we expect the memory overhead of doing so
// to outweigh the cost of recalculating it on access.
return 100 - math.max(math.max(red, green), blue) / 255 * 100;
}
final num alpha;
/// The original string representation of this color, or `null` if one is
@ -81,6 +95,35 @@ class SassColor extends Value implements ext.SassColor {
alpha = alpha == null ? 1 : fuzzyAssertRange(alpha, 0, 1, "alpha"),
originalSpan = null;
factory SassColor.hwb(num hue, num whiteness, num blackness, [num alpha]) {
// From https://www.w3.org/TR/css-color-4/#hwb-to-rgb
var scaledHue = hue % 360 / 360;
var scaledWhiteness = fuzzyAssertRange(whiteness, 0, 100, "whiteness") / 100;
var scaledBlackness = fuzzyAssertRange(blackness, 0, 100, "blackness") / 100;
var sum = scaledWhiteness + scaledBlackness;
if (sum > 1) {
scaledWhiteness /= sum;
scaledBlackness /= sum;
}
var factor = 1 - scaledWhiteness - scaledBlackness;
int toRgb(num hue) {
var channel = _hueToRgb(0, 1, hue) * factor + scaledWhiteness;
return fuzzyRound(channel * 255);
}
// Because HWB is (currently) used much less frequently than HSL or RGB, we
// don't cache its values because we expect the memory overhead of doing so
// to outweigh the cost of recalculating it on access. Instead, we eagerly
// convert it to RGB and then convert back if necessary.
return SassColor.rgb(
toRgb(scaledHue + 1/3),
toRgb(scaledHue),
toRgb(scaledHue - 1/3),
alpha);
}
SassColor._(this._red, this._green, this._blue, this._hue, this._saturation,
this._lightness, this.alpha)
: originalSpan = null;
@ -97,6 +140,10 @@ class SassColor extends Value implements ext.SassColor {
SassColor.hsl(hue ?? this.hue, saturation ?? this.saturation,
lightness ?? this.lightness, alpha ?? this.alpha);
SassColor changeHwb({num hue, num whiteness, num blackness, num alpha}) =>
SassColor.hwb(hue ?? this.hue, whiteness ?? this.whiteness,
blackness ?? this.blackness, alpha ?? this.alpha);
SassColor changeAlpha(num alpha) => SassColor._(_red, _green, _blue, _hue,
_saturation, _lightness, fuzzyAssertRange(alpha, 0, 1, "alpha"));
@ -177,29 +224,26 @@ class SassColor extends Value implements ext.SassColor {
scaledSaturation -
scaledLightness * scaledSaturation;
var m1 = scaledLightness * 2 - m2;
_red = _hueToRgb(m1, m2, scaledHue + 1 / 3);
_green = _hueToRgb(m1, m2, scaledHue);
_blue = _hueToRgb(m1, m2, scaledHue - 1 / 3);
_red = fuzzyRound(_hueToRgb(m1, m2, scaledHue + 1 / 3) * 255);
_green = fuzzyRound(_hueToRgb(m1, m2, scaledHue) * 255);
_blue = fuzzyRound(_hueToRgb(m1, m2, scaledHue - 1 / 3) * 255);
}
/// An algorithm from the CSS3 spec:
/// http://www.w3.org/TR/css3-color/#hsl-color.
int _hueToRgb(num m1, num m2, num hue) {
static num _hueToRgb(num m1, num m2, num hue) {
if (hue < 0) hue += 1;
if (hue > 1) hue -= 1;
num result;
if (hue < 1 / 6) {
result = m1 + (m2 - m1) * hue * 6;
return m1 + (m2 - m1) * hue * 6;
} else if (hue < 1 / 2) {
result = m2;
return m2;
} else if (hue < 2 / 3) {
result = m1 + (m2 - m1) * (2 / 3 - hue) * 6;
return m1 + (m2 - m1) * (2 / 3 - hue) * 6;
} else {
result = m1;
return m1;
}
return fuzzyRound(result * 255);
}
/// Returns an `rgb()` or `rgba()` function call that will evaluate to this

View File

@ -28,6 +28,12 @@ abstract class SassColor extends Value {
/// This color's lightness, a percentage between `0` and `100`.
num get lightness;
/// This color's whiteness, a percentage between `0` and `100`.
num get whiteness;
/// This color's blackness, a percentage between `0` and `100`.
num get blackness;
/// This color's alpha channel, between `0` and `1`.
num get alpha;
@ -45,12 +51,22 @@ abstract class SassColor extends Value {
factory SassColor.hsl(num hue, num saturation, num lightness, [num alpha]) =
internal.SassColor.hsl;
/// Creates an HWB color.
///
/// Throws a [RangeError] if [whiteness] or [blackness] aren't between `0` and
/// `100`, or if [alpha] isn't between `0` and `1`.
factory SassColor.hwb(num hue, num whiteness, num blackness, [num alpha]) =
internal.SassColor.hwb;
/// Changes one or more of this color's RGB channels and returns the result.
SassColor changeRgb({int red, int green, int blue, num alpha});
/// Changes one or more of this color's HSL channels and returns the result.
SassColor changeHsl({num hue, num saturation, num lightness, num alpha});
/// Changes one or more of this color's HWB channels and returns the result.
SassColor changeHwb({num hue, num whiteness, num blackness, num alpha});
/// Returns a new copy of this color with the alpha channel set to [alpha].
SassColor changeAlpha(num alpha);
}

View File

@ -1,5 +1,5 @@
name: sass
version: 1.27.2
version: 1.28.0
description: A Sass implementation in Dart.
author: Sass Team
homepage: https://github.com/sass/dart-sass

View File

@ -28,6 +28,12 @@ void main() {
expect(value.lightness, equals(20.392156862745097));
});
test("has HWB channels", () {
expect(value.hue, equals(210));
expect(value.whiteness, equals(7.0588235294117645));
expect(value.blackness, equals(66.27450980392157));
});
test("has an alpha channel", () {
expect(value.alpha, equals(1));
});
@ -114,6 +120,46 @@ void main() {
});
});
group("changeHwb()", () {
test("changes HWB values", () {
expect(value.changeHwb(hue: 120),
equals(SassColor.hwb(120, 7.0588235294117645, 66.27450980392157)));
expect(value.changeHwb(whiteness: 20),
equals(SassColor.hwb(210, 20, 66.27450980392157)));
expect(value.changeHwb(blackness: 42),
equals(SassColor.hwb(210, 7.0588235294117645, 42)));
expect(
value.changeHwb(alpha: 0.5),
equals(
SassColor.hwb(210, 7.0588235294117645, 66.27450980392157, 0.5)));
expect(
value.changeHwb(
hue: 120, whiteness: 42, blackness: 42, alpha: 0.5),
equals(SassColor.hwb(120, 42, 42, 0.5)));
expect(
value.changeHwb(whiteness: 50),
equals(SassColor.hwb(210, 43, 57)));
});
test("allows valid values", () {
expect(value.changeHwb(whiteness: 0).whiteness, equals(0));
expect(value.changeHwb(whiteness: 100).whiteness, equals(60.0));
expect(value.changeHwb(blackness: 0).blackness, equals(0));
expect(value.changeHwb(blackness: 100).blackness, equals(93.33333333333333));
expect(value.changeHwb(alpha: 0).alpha, equals(0));
expect(value.changeHwb(alpha: 1).alpha, equals(1));
});
test("disallows invalid values", () {
expect(() => value.changeHwb(whiteness: -0.1), throwsRangeError);
expect(() => value.changeHwb(whiteness: 100.1), throwsRangeError);
expect(() => value.changeHwb(blackness: -0.1), throwsRangeError);
expect(() => value.changeHwb(blackness: 100.1), throwsRangeError);
expect(() => value.changeHwb(alpha: -0.1), throwsRangeError);
expect(() => value.changeHwb(alpha: 1.1), throwsRangeError);
});
});
group("changeAlpha()", () {
test("changes the alpha value", () {
expect(value.changeAlpha(0.5),
@ -161,6 +207,11 @@ void main() {
expect(value.lightness, equals(42));
});
test("has HWB channels", () {
expect(value.whiteness, equals(24.313725490196077));
expect(value.blackness, equals(40.3921568627451));
});
test("has an alpha channel", () {
expect(value.alpha, equals(1));
});
@ -168,6 +219,7 @@ void main() {
test("equals the same color", () {
expect(value, equalsWithHash(SassColor.rgb(0x3E, 0x98, 0x3E)));
expect(value, equalsWithHash(SassColor.hsl(120, 42, 42)));
expect(value, equalsWithHash(SassColor.hwb(120, 24.313725490196077, 40.3921568627451)));
});
});
@ -210,4 +262,51 @@ void main() {
expect(() => SassColor.hsl(0, 0, 0, 1.1), throwsRangeError);
});
});
group("new SassColor.hwb()", () {
SassColor value;
setUp(() => value = SassColor.hwb(120, 42, 42));
test("has RGB channels", () {
expect(value.red, equals(0x6B));
expect(value.green, equals(0x94));
expect(value.blue, equals(0x6B));
});
test("has HSL channels", () {
expect(value.hue, equals(120));
expect(value.saturation, equals(16.078431372549026));
expect(value.lightness, equals(50));
});
test("has HWB channels", () {
expect(value.whiteness, equals(41.96078431372549));
expect(value.blackness, equals(41.96078431372548));
});
test("has an alpha channel", () {
expect(value.alpha, equals(1));
});
test("equals the same color", () {
expect(value, equalsWithHash(SassColor.rgb(0x6B, 0x94, 0x6B)));
expect(value, equalsWithHash(SassColor.hsl(120, 16, 50)));
expect(value, equalsWithHash(SassColor.hwb(120, 42, 42)));
});
test("allows valid values", () {
expect(SassColor.hwb(0, 0, 0, 0), equals(parseValue("rgba(255, 0, 0, 0)")));
expect(SassColor.hwb(4320, 100, 100, 1),
equals(parseValue("grey")));
});
test("disallows invalid values", () {
expect(() => SassColor.hwb(0, -0.1, 0, 0), throwsRangeError);
expect(() => SassColor.hwb(0, 0, -0.1, 0), throwsRangeError);
expect(() => SassColor.hwb(0, 0, 0, -0.1), throwsRangeError);
expect(() => SassColor.hwb(0, 100.1, 0, 0), throwsRangeError);
expect(() => SassColor.hwb(0, 0, 100.1, 0), throwsRangeError);
expect(() => SassColor.hwb(0, 0, 0, 1.1), throwsRangeError);
});
});
}