dart-sass/test/function_test.dart

1968 lines
70 KiB
Dart

// Copyright 2019 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'package:test/test.dart';
import 'package:sass_embedded/src/embedded_sass.pb.dart';
import 'package:sass_embedded/src/utils.dart';
import 'embedded_process.dart';
import 'utils.dart';
final _true = Value()..singleton = SingletonValue.TRUE;
final _false = Value()..singleton = SingletonValue.FALSE;
final _null = Value()..singleton = SingletonValue.NULL;
late EmbeddedProcess _process;
void main() {
setUp(() async {
_process = await EmbeddedProcess.start();
});
group("emits a compile failure for a custom function with a signature", () {
test("that's empty", () async {
_process.inbound.add(compileString("a {b: c}", functions: [r""]));
await _expectFunctionError(
_process, r'Invalid signature "": Expected identifier.');
await _process.kill();
});
test("that's just a name", () async {
_process.inbound.add(compileString("a {b: c}", functions: [r"foo"]));
await _expectFunctionError(
_process, r'Invalid signature "foo": expected "(".');
await _process.kill();
});
test("without a closing paren", () async {
_process.inbound.add(compileString("a {b: c}", functions: [r"foo($bar"]));
await _expectFunctionError(
_process, r'Invalid signature "foo($bar": expected ")".');
await _process.kill();
});
test("with text after the closing paren", () async {
_process.inbound.add(compileString("a {b: c}", functions: [r"foo() "]));
await _expectFunctionError(
_process, r'Invalid signature "foo() ": expected no more input.');
await _process.kill();
});
test("with invalid arguments", () async {
_process.inbound.add(compileString("a {b: c}", functions: [r"foo($)"]));
await _expectFunctionError(
_process, r'Invalid signature "foo($)": Expected identifier.');
await _process.kill();
});
});
group("includes in FunctionCallRequest", () {
var compilationId = 1234;
late OutboundMessage_FunctionCallRequest request;
setUp(() async {
_process.inbound.add(compileString("a {b: foo()}",
id: compilationId, functions: ["foo()"]));
request = getFunctionCallRequest(await _process.outbound.next);
});
test("the same compilationId as the compilation", () async {
expect(request.compilationId, equals(compilationId));
await _process.kill();
});
test("the function name", () async {
expect(request.name, equals("foo"));
await _process.kill();
});
group("arguments", () {
test("that are empty", () async {
_process.inbound
.add(compileString("a {b: foo()}", functions: ["foo()"]));
var request = getFunctionCallRequest(await _process.outbound.next);
expect(request.arguments, isEmpty);
await _process.kill();
});
test("by position", () async {
_process.inbound.add(compileString("a {b: foo(true, null, false)}",
functions: [r"foo($arg1, $arg2, $arg3)"]));
var request = getFunctionCallRequest(await _process.outbound.next);
expect(request.arguments, equals([_true, _null, _false]));
await _process.kill();
});
test("by name", () async {
_process.inbound.add(compileString(
r"a {b: foo($arg3: true, $arg1: null, $arg2: false)}",
functions: [r"foo($arg1, $arg2, $arg3)"]));
var request = getFunctionCallRequest(await _process.outbound.next);
expect(request.arguments, equals([_null, _false, _true]));
await _process.kill();
});
test("by position and name", () async {
_process.inbound.add(compileString(
r"a {b: foo(true, $arg3: null, $arg2: false)}",
functions: [r"foo($arg1, $arg2, $arg3)"]));
var request = getFunctionCallRequest(await _process.outbound.next);
expect(request.arguments, equals([_true, _false, _null]));
await _process.kill();
});
test("from defaults", () async {
_process.inbound.add(compileString(r"a {b: foo(1, $arg3: 2)}",
functions: [r"foo($arg1: null, $arg2: true, $arg3: false)"]));
var request = getFunctionCallRequest(await _process.outbound.next);
expect(
request.arguments,
equals([
Value()..number = (Value_Number()..value = 1.0),
_true,
Value()..number = (Value_Number()..value = 2.0)
]));
await _process.kill();
});
group("from argument lists", () {
test("with no named arguments", () async {
_process.inbound.add(compileString("a {b: foo(true, false, null)}",
functions: [r"foo($arg, $args...)"]));
var request = getFunctionCallRequest(await _process.outbound.next);
expect(
request.arguments,
equals([
_true,
Value()
..argumentList = (Value_ArgumentList()
..id = 1
..separator = ListSeparator.COMMA
..contents.addAll([_false, _null]))
]));
await _process.kill();
});
test("with named arguments", () async {
_process.inbound.add(compileString(r"a {b: foo(true, $arg: false)}",
functions: [r"foo($args...)"]));
var request = getFunctionCallRequest(await _process.outbound.next);
expect(
request.arguments,
equals([
Value()
..argumentList = (Value_ArgumentList()
..id = 1
..separator = ListSeparator.COMMA
..contents.addAll([_true])
..keywords.addAll({"arg": _false}))
]));
await _process.kill();
});
test("throws if named arguments are unused", () async {
_process.inbound.add(compileString(r"a {b: foo($arg: false)}",
functions: [r"foo($args...)"]));
var request = getFunctionCallRequest(await _process.outbound.next);
_process.inbound.add(InboundMessage()
..functionCallResponse = (InboundMessage_FunctionCallResponse()
..id = request.id
..success = _true));
var failure = getCompileFailure(await _process.outbound.next);
expect(failure.message, equals(r"No argument named $arg."));
await _process.kill();
});
test("doesn't throw if named arguments are used", () async {
_process.inbound.add(compileString(r"a {b: foo($arg: false)}",
functions: [r"foo($args...)"]));
var request = getFunctionCallRequest(await _process.outbound.next);
_process.inbound.add(InboundMessage()
..functionCallResponse = (InboundMessage_FunctionCallResponse()
..id = request.id
..accessedArgumentLists
.add(request.arguments.first.argumentList.id)
..success = _true));
await expectLater(_process.outbound,
emits(isSuccess(equals("a {\n b: true;\n}"))));
await _process.kill();
});
});
});
});
test("returns the result as a SassScript value", () async {
_process.inbound
.add(compileString("a {b: foo() + 2px}", functions: [r"foo()"]));
var request = getFunctionCallRequest(await _process.outbound.next);
_process.inbound.add(InboundMessage()
..functionCallResponse = (InboundMessage_FunctionCallResponse()
..id = request.id
..success = (Value()
..number = (Value_Number()
..value = 1
..numerators.add("px")))));
await expectLater(
_process.outbound, emits(isSuccess(equals("a {\n b: 3px;\n}"))));
await _process.kill();
});
group("calls a first-class function", () {
test("defined in the compiler and passed to and from the host", () async {
_process.inbound.add(compileString(r"""
@use "sass:math";
@use "sass:meta";
a {b: call(foo(meta.get-function("abs", $module: "math")), -1)}
""", functions: [r"foo($arg)"]));
var request = getFunctionCallRequest(await _process.outbound.next);
var value = request.arguments.single;
expect(value.hasCompilerFunction(), isTrue);
_process.inbound.add(InboundMessage()
..functionCallResponse = (InboundMessage_FunctionCallResponse()
..id = request.id
..success = value));
await expectLater(
_process.outbound, emits(isSuccess(equals("a {\n b: 1;\n}"))));
await _process.kill();
});
test("defined in the host", () async {
var compilationId = 1234;
_process.inbound.add(compileString("a {b: call(foo(), true)}",
id: compilationId, functions: [r"foo()"]));
var hostFunctionId = 5678;
var request = getFunctionCallRequest(await _process.outbound.next);
_process.inbound.add(InboundMessage()
..functionCallResponse = (InboundMessage_FunctionCallResponse()
..id = request.id
..success = (Value()
..hostFunction = (Value_HostFunction()
..id = hostFunctionId
..signature = r"bar($arg)"))));
request = getFunctionCallRequest(await _process.outbound.next);
expect(request.compilationId, equals(compilationId));
expect(request.functionId, equals(hostFunctionId));
expect(request.arguments, equals([_true]));
_process.inbound.add(InboundMessage()
..functionCallResponse = (InboundMessage_FunctionCallResponse()
..id = request.id
..success = _false));
await expectLater(
_process.outbound, emits(isSuccess(equals("a {\n b: false;\n}"))));
await _process.kill();
});
test("defined in the host and passed to and from the host", () async {
var compilationId = 1234;
_process.inbound.add(compileString(
r"""
$function: get-host-function();
$function: round-trip($function);
a {b: call($function, true)}
""",
id: compilationId,
functions: [r"get-host-function()", r"round-trip($function)"]));
var hostFunctionId = 5678;
var request = getFunctionCallRequest(await _process.outbound.next);
expect(request.name, equals("get-host-function"));
_process.inbound.add(InboundMessage()
..functionCallResponse = (InboundMessage_FunctionCallResponse()
..id = request.id
..success = (Value()
..hostFunction = (Value_HostFunction()
..id = hostFunctionId
..signature = r"bar($arg)"))));
request = getFunctionCallRequest(await _process.outbound.next);
expect(request.name, equals("round-trip"));
var value = request.arguments.single;
expect(value.hasCompilerFunction(), isTrue);
_process.inbound.add(InboundMessage()
..functionCallResponse = (InboundMessage_FunctionCallResponse()
..id = request.id
..success = value));
request = getFunctionCallRequest(await _process.outbound.next);
expect(request.compilationId, equals(compilationId));
expect(request.functionId, equals(hostFunctionId));
expect(request.arguments, equals([_true]));
_process.inbound.add(InboundMessage()
..functionCallResponse = (InboundMessage_FunctionCallResponse()
..id = request.id
..success = _false));
await expectLater(
_process.outbound, emits(isSuccess(equals("a {\n b: false;\n}"))));
await _process.kill();
});
});
group("serializes to protocol buffers", () {
group("a string that's", () {
group("quoted", () {
test("and empty", () async {
var value = (await _protofy('""')).string;
expect(value.text, isEmpty);
expect(value.quoted, isTrue);
});
test("and non-empty", () async {
var value = (await _protofy('"foo bar"')).string;
expect(value.text, equals("foo bar"));
expect(value.quoted, isTrue);
});
});
group("unquoted", () {
test("and empty", () async {
var value = (await _protofy('unquote("")')).string;
expect(value.text, isEmpty);
expect(value.quoted, isFalse);
});
test("and non-empty", () async {
var value = (await _protofy('"foo bar"')).string;
expect(value.text, equals("foo bar"));
expect(value.quoted, isTrue);
});
});
});
group("a number", () {
group("that's unitless", () {
test("and an integer", () async {
var value = (await _protofy('1')).number;
expect(value.value, equals(1.0));
expect(value.numerators, isEmpty);
expect(value.denominators, isEmpty);
});
test("and a float", () async {
var value = (await _protofy('1.5')).number;
expect(value.value, equals(1.5));
expect(value.numerators, isEmpty);
expect(value.denominators, isEmpty);
});
});
test("with one numerator", () async {
var value = (await _protofy('1em')).number;
expect(value.value, equals(1.0));
expect(value.numerators, ["em"]);
expect(value.denominators, isEmpty);
});
test("with multiple numerators", () async {
var value = (await _protofy('1em * 1px * 1foo')).number;
expect(value.value, equals(1.0));
expect(value.numerators, unorderedEquals(["em", "px", "foo"]));
expect(value.denominators, isEmpty);
});
test("with one denominator", () async {
var value = (await _protofy('math.div(1,1em)')).number;
expect(value.value, equals(1.0));
expect(value.numerators, isEmpty);
expect(value.denominators, ["em"]);
});
test("with multiple denominators", () async {
var value =
(await _protofy('math.div(math.div(math.div(1, 1em), 1px), 1foo)'))
.number;
expect(value.value, equals(1.0));
expect(value.numerators, isEmpty);
expect(value.denominators, unorderedEquals(["em", "px", "foo"]));
});
test("with numerators and denominators", () async {
var value =
(await _protofy('1em * math.div(math.div(1px, 1s), 1foo)')).number;
expect(value.value, equals(1.0));
expect(value.numerators, unorderedEquals(["em", "px"]));
expect(value.denominators, unorderedEquals(["s", "foo"]));
});
});
group("a color that's", () {
group("rgb", () {
group("without alpha:", () {
test("black", () async {
expect(await _protofy('#000'), _rgb(0, 0, 0, 1.0));
});
test("white", () async {
expect(await _protofy('#fff'), equals(_rgb(255, 255, 255, 1.0)));
});
test("in the middle", () async {
expect(await _protofy('#abc'), equals(_rgb(0xaa, 0xbb, 0xcc, 1.0)));
});
});
group("with alpha", () {
test("0", () async {
expect(await _protofy('rgb(10, 20, 30, 0)'),
equals(_rgb(10, 20, 30, 0.0)));
});
test("1", () async {
expect(await _protofy('rgb(10, 20, 30, 1)'),
equals(_rgb(10, 20, 30, 1.0)));
});
test("between 0 and 1", () async {
expect(await _protofy('rgb(10, 20, 30, 0.123)'),
equals(_rgb(10, 20, 30, 0.123)));
});
});
});
group("hsl", () {
group("without alpha:", () {
group("hue", () {
test("0", () async {
expect(await _protofy('hsl(0, 50%, 50%)'), _hsl(0, 50, 50, 1.0));
});
test("360", () async {
expect(
await _protofy('hsl(360, 50%, 50%)'), _hsl(0, 50, 50, 1.0));
});
test("below 0", () async {
expect(await _protofy('hsl(-100, 50%, 50%)'),
_hsl(260, 50, 50, 1.0));
});
test("between 0 and 360", () async {
expect(
await _protofy('hsl(100, 50%, 50%)'), _hsl(100, 50, 50, 1.0));
});
test("above 360", () async {
expect(
await _protofy('hsl(560, 50%, 50%)'), _hsl(200, 50, 50, 1.0));
});
});
group("saturation", () {
test("0", () async {
expect(await _protofy('hsl(0, 0%, 50%)'), _hsl(0, 0, 50, 1.0));
});
test("100", () async {
expect(
await _protofy('hsl(0, 100%, 50%)'), _hsl(0, 100, 50, 1.0));
});
test("in the middle", () async {
expect(await _protofy('hsl(0, 42%, 50%)'), _hsl(0, 42, 50, 1.0));
});
});
group("lightness", () {
test("0", () async {
expect(await _protofy('hsl(0, 50%, 0%)'), _hsl(0, 50, 0, 1.0));
});
test("100", () async {
expect(
await _protofy('hsl(0, 50%, 100%)'), _hsl(0, 50, 100, 1.0));
});
test("in the middle", () async {
expect(await _protofy('hsl(0, 50%, 42%)'), _hsl(0, 50, 42, 1.0));
});
});
});
group("with alpha", () {
test("0", () async {
expect(await _protofy('hsl(10, 20%, 30%, 0)'),
equals(_hsl(10, 20, 30, 0.0)));
});
test("1", () async {
expect(await _protofy('hsl(10, 20%, 30%, 1)'),
equals(_hsl(10, 20, 30, 1.0)));
});
test("between 0 and 1", () async {
expect(await _protofy('hsl(10, 20%, 30%, 0.123)'),
equals(_hsl(10, 20, 30, 0.123)));
});
});
});
});
group("a list", () {
group("with no elements", () {
group("with brackets", () {
test("with unknown separator", () async {
var list = (await _protofy("[]")).list;
expect(list.contents, isEmpty);
expect(list.hasBrackets, isTrue);
expect(list.separator, equals(ListSeparator.UNDECIDED));
});
test("with a comma separator", () async {
var list =
(await _protofy(r"list.join([], [], $separator: comma)")).list;
expect(list.contents, isEmpty);
expect(list.hasBrackets, isTrue);
expect(list.separator, equals(ListSeparator.COMMA));
});
test("with a space separator", () async {
var list =
(await _protofy(r"list.join([], [], $separator: space)")).list;
expect(list.contents, isEmpty);
expect(list.hasBrackets, isTrue);
expect(list.separator, equals(ListSeparator.SPACE));
});
test("with a slash separator", () async {
var list =
(await _protofy(r"list.join([], [], $separator: slash)")).list;
expect(list.contents, isEmpty);
expect(list.hasBrackets, isTrue);
expect(list.separator, equals(ListSeparator.SLASH));
});
});
group("without brackets", () {
test("with unknown separator", () async {
var list = (await _protofy("()")).list;
expect(list.contents, isEmpty);
expect(list.hasBrackets, isFalse);
expect(list.separator, equals(ListSeparator.UNDECIDED));
});
test("with a comma separator", () async {
var list =
(await _protofy(r"list.join((), (), $separator: comma)")).list;
expect(list.contents, isEmpty);
expect(list.hasBrackets, isFalse);
expect(list.separator, equals(ListSeparator.COMMA));
});
test("with a space separator", () async {
var list =
(await _protofy(r"list.join((), (), $separator: space)")).list;
expect(list.contents, isEmpty);
expect(list.hasBrackets, isFalse);
expect(list.separator, equals(ListSeparator.SPACE));
});
test("with a slash separator", () async {
var list =
(await _protofy(r"list.join((), (), $separator: slash)")).list;
expect(list.contents, isEmpty);
expect(list.hasBrackets, isFalse);
expect(list.separator, equals(ListSeparator.SLASH));
});
});
});
group("with one element", () {
group("with brackets", () {
test("with unknown separator", () async {
var list = (await _protofy("[true]")).list;
expect(list.contents, equals([_true]));
expect(list.hasBrackets, isTrue);
expect(list.separator, equals(ListSeparator.UNDECIDED));
});
test("with a comma separator", () async {
var list = (await _protofy(r"[true,]")).list;
expect(list.contents, equals([_true]));
expect(list.hasBrackets, isTrue);
expect(list.separator, equals(ListSeparator.COMMA));
});
test("with a space separator", () async {
var list =
(await _protofy(r"list.join([true], [], $separator: space)"))
.list;
expect(list.contents, equals([_true]));
expect(list.hasBrackets, isTrue);
expect(list.separator, equals(ListSeparator.SPACE));
});
test("with a slash separator", () async {
var list =
(await _protofy(r"list.join([true], [], $separator: slash)"))
.list;
expect(list.contents, equals([_true]));
expect(list.hasBrackets, isTrue);
expect(list.separator, equals(ListSeparator.SLASH));
});
});
group("without brackets", () {
test("with a comma separator", () async {
var list = (await _protofy(r"(true,)")).list;
expect(list.contents, equals([_true]));
expect(list.hasBrackets, isFalse);
expect(list.separator, equals(ListSeparator.COMMA));
});
test("with a space separator", () async {
var list =
(await _protofy(r"list.join(true, (), $separator: space)"))
.list;
expect(list.contents, equals([_true]));
expect(list.hasBrackets, isFalse);
expect(list.separator, equals(ListSeparator.SPACE));
});
test("with a slash separator", () async {
var list =
(await _protofy(r"list.join(true, (), $separator: slash)"))
.list;
expect(list.contents, equals([_true]));
expect(list.hasBrackets, isFalse);
expect(list.separator, equals(ListSeparator.SLASH));
});
});
});
group("with multiple elements", () {
group("with brackets", () {
test("with a comma separator", () async {
var list = (await _protofy(r"[true, null, false]")).list;
expect(list.contents, equals([_true, _null, _false]));
expect(list.hasBrackets, isTrue);
expect(list.separator, equals(ListSeparator.COMMA));
});
test("with a space separator", () async {
var list = (await _protofy(r"[true null false]")).list;
expect(list.contents, equals([_true, _null, _false]));
expect(list.hasBrackets, isTrue);
expect(list.separator, equals(ListSeparator.SPACE));
});
});
group("without brackets", () {
test("with a comma separator", () async {
var list = (await _protofy(r"true, null, false")).list;
expect(list.contents, equals([_true, _null, _false]));
expect(list.hasBrackets, isFalse);
expect(list.separator, equals(ListSeparator.COMMA));
});
test("with a space separator", () async {
var list = (await _protofy(r"true null false")).list;
expect(list.contents, equals([_true, _null, _false]));
expect(list.hasBrackets, isFalse);
expect(list.separator, equals(ListSeparator.SPACE));
});
test("with a slash separator", () async {
var list = (await _protofy(r"list.slash(true, null, false)")).list;
expect(list.contents, equals([_true, _null, _false]));
expect(list.hasBrackets, isFalse);
expect(list.separator, equals(ListSeparator.SLASH));
});
});
});
});
group("an argument list", () {
test("that's empty", () async {
var list = (await _protofy(r"capture-args()")).argumentList;
expect(list.contents, isEmpty);
expect(list.keywords, isEmpty);
expect(list.separator, equals(ListSeparator.COMMA));
});
test("with arguments", () async {
var list =
(await _protofy(r"capture-args(true, null, false)")).argumentList;
expect(list.contents, [_true, _null, _false]);
expect(list.keywords, isEmpty);
expect(list.separator, equals(ListSeparator.COMMA));
});
test("with a space separator", () async {
var list =
(await _protofy(r"capture-args(true null false...)")).argumentList;
expect(list.contents, [_true, _null, _false]);
expect(list.keywords, isEmpty);
expect(list.separator, equals(ListSeparator.SPACE));
});
test("with a slash separator", () async {
var list =
(await _protofy(r"capture-args(list.slash(true, null, false)...)"))
.argumentList;
expect(list.contents, [_true, _null, _false]);
expect(list.keywords, isEmpty);
expect(list.separator, equals(ListSeparator.SLASH));
});
test("with keywords", () async {
var list = (await _protofy(r"capture-args($arg1: true, $arg2: false)"))
.argumentList;
expect(list.contents, isEmpty);
expect(list.keywords, equals({"arg1": _true, "arg2": _false}));
expect(list.separator, equals(ListSeparator.COMMA));
});
});
group("a map", () {
test("with no elements", () async {
expect((await _protofy("map.remove((1: 2), 1)")).map.entries, isEmpty);
});
test("with one element", () async {
expect(
(await _protofy("(true: false)")).map.entries,
equals([
Value_Map_Entry()
..key = _true
..value = _false
]));
});
test("with multiple elements", () async {
expect(
(await _protofy("(true: false, 1: 2, a: b)")).map.entries,
equals([
Value_Map_Entry()
..key = _true
..value = _false,
Value_Map_Entry()
..key = (Value()..number = (Value_Number()..value = 1.0))
..value = (Value()..number = (Value_Number()..value = 2.0)),
Value_Map_Entry()
..key = (Value()
..string = (Value_String()
..text = "a"
..quoted = false))
..value = (Value()
..string = (Value_String()
..text = "b"
..quoted = false))
]));
});
});
group("a calculation", () {
test("with a string argument", () async {
expect(
(await _protofy("calc(var(--foo))")).calculation,
equals(Value_Calculation()
..name = "calc"
..arguments.add(Value_Calculation_CalculationValue()
..string = "var(--foo)")));
});
test("with an interpolation argument", () async {
expect(
(await _protofy("calc(#{var(--foo)})")).calculation,
equals(Value_Calculation()
..name = "calc"
..arguments.add(Value_Calculation_CalculationValue()
..interpolation = "var(--foo)")));
});
test("with number arguments", () async {
expect(
(await _protofy("clamp(1%, 2px, 3em)")).calculation,
equals(Value_Calculation()
..name = "clamp"
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()
..value = 1.0
..numerators.add("%")))
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()
..value = 2.0
..numerators.add("px")))
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()
..value = 3.0
..numerators.add("em")))));
});
test("with a calculation argument", () async {
expect(
(await _protofy("min(max(1%, 2px), 3em)")).calculation,
equals(Value_Calculation()
..name = "min"
..arguments.add(Value_Calculation_CalculationValue()
..calculation = (Value_Calculation()
..name = "max"
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()
..value = 1.0
..numerators.add("%")))
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()
..value = 2.0
..numerators.add("px")))))
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()
..value = 3.0
..numerators.add("em")))));
});
test("with an operation", () async {
expect(
(await _protofy("calc(1% + 2px)")).calculation,
equals(Value_Calculation()
..name = "calc"
..arguments.add(Value_Calculation_CalculationValue()
..operation = (Value_Calculation_CalculationOperation()
..operator = CalculationOperator.PLUS
..left = (Value_Calculation_CalculationValue()
..number = (Value_Number()
..value = 1.0
..numerators.add("%")))
..right = (Value_Calculation_CalculationValue()
..number = (Value_Number()
..value = 2.0
..numerators.add("px")))))));
});
});
test("true", () async {
expect((await _protofy("true")), equals(_true));
});
test("false", () async {
expect((await _protofy("false")), equals(_false));
});
test("true", () async {
expect((await _protofy("null")), equals(_null));
});
});
group("deserializes from protocol buffer", () {
group("a string that's", () {
group("quoted", () {
test("and empty", () async {
expect(
await _deprotofy(Value()
..string = (Value_String()
..text = ""
..quoted = true)),
'""');
});
test("and non-empty", () async {
expect(
await _deprotofy(Value()
..string = (Value_String()
..text = "foo bar"
..quoted = true)),
'"foo bar"');
});
});
group("unquoted", () {
test("and empty", () async {
// We can't use [_deprotofy] here because a property with an empty
// value won't render at all.
await _assertRoundTrips(Value()
..string = (Value_String()
..text = ""
..quoted = false));
});
test("and non-empty", () async {
expect(
await _deprotofy(Value()
..string = (Value_String()
..text = "foo bar"
..quoted = false)),
"foo bar");
});
});
});
group("a number", () {
group("that's unitless", () {
test("and an integer", () async {
expect(
await _deprotofy(Value()..number = (Value_Number()..value = 1.0)),
"1");
});
test("and a float", () async {
expect(
await _deprotofy(Value()..number = (Value_Number()..value = 1.5)),
"1.5");
});
});
test("with one numerator", () async {
expect(
await _deprotofy(Value()
..number = (Value_Number()
..value = 1
..numerators.add("em"))),
"1em");
});
test("with multiple numerators", () async {
expect(
await _deprotofy(
Value()
..number = (Value_Number()
..value = 1
..numerators.addAll(["em", "px", "foo"])),
inspect: true),
"1em*px*foo");
});
test("with one denominator", () async {
expect(
await _deprotofy(
Value()
..number = (Value_Number()
..value = 1
..denominators.add("em")),
inspect: true),
"1em^-1");
});
test("with multiple denominators", () async {
expect(
await _deprotofy(
Value()
..number = (Value_Number()
..value = 1
..denominators.addAll(["em", "px", "foo"])),
inspect: true),
"1(em*px*foo)^-1");
});
test("with numerators and denominators", () async {
expect(
await _deprotofy(
Value()
..number = (Value_Number()
..value = 1
..numerators.addAll(["em", "px"])
..denominators.addAll(["s", "foo"])),
inspect: true),
"1em*px/s*foo");
});
});
group("a color that's", () {
group("rgb", () {
group("without alpha:", () {
test("black", () async {
expect(await _deprotofy(_rgb(0, 0, 0, 1.0)), equals('black'));
});
test("white", () async {
expect(await _deprotofy(_rgb(255, 255, 255, 1.0)), equals('white'));
});
test("in the middle", () async {
expect(await _deprotofy(_rgb(0xaa, 0xbb, 0xcc, 1.0)),
equals('#aabbcc'));
});
});
group("with alpha", () {
test("0", () async {
expect(await _deprotofy(_rgb(10, 20, 30, 0.0)),
equals('rgba(10, 20, 30, 0)'));
});
test("between 0 and 1", () async {
expect(await _deprotofy(_rgb(10, 20, 30, 0.123)),
equals('rgba(10, 20, 30, 0.123)'));
});
});
});
group("hsl", () {
group("without alpha:", () {
group("hue", () {
test("0", () async {
expect(await _deprotofy(_hsl(0, 50, 50, 1.0)), "#bf4040");
});
test("360", () async {
expect(await _deprotofy(_hsl(360, 50, 50, 1.0)), "#bf4040");
});
test("below 0", () async {
expect(await _deprotofy(_hsl(-100, 50, 50, 1.0)), "#6a40bf");
});
test("between 0 and 360", () async {
expect(await _deprotofy(_hsl(100, 50, 50, 1.0)), "#6abf40");
});
test("above 360", () async {
expect(await _deprotofy(_hsl(560, 50, 50, 1.0)), "#4095bf");
});
});
group("saturation", () {
test("0", () async {
expect(await _deprotofy(_hsl(0, 0, 50, 1.0)), "gray");
});
test("100", () async {
expect(await _deprotofy(_hsl(0, 100, 50, 1.0)), "red");
});
test("in the middle", () async {
expect(await _deprotofy(_hsl(0, 42, 50, 1.0)), "#b54a4a");
});
});
group("lightness", () {
test("0", () async {
expect(await _deprotofy(_hsl(0, 50, 0, 1.0)), "black");
});
test("100", () async {
expect(await _deprotofy(_hsl(0, 50, 100, 1.0)), "white");
});
test("in the middle", () async {
expect(await _deprotofy(_hsl(0, 50, 42, 1.0)), "#a13636");
});
});
});
group("with alpha", () {
test("0", () async {
expect(
await _deprotofy(_hsl(10, 20, 30, 0.0)), "rgba(92, 66, 61, 0)");
});
test("between 0 and 1", () async {
expect(await _deprotofy(_hsl(10, 20, 30, 0.123)),
"rgba(92, 66, 61, 0.123)");
});
});
});
});
group("a list", () {
group("with no elements", () {
group("with brackets", () {
group("with unknown separator", () {
_testSerializationAndRoundTrip(
Value()
..list = (Value_List()
..hasBrackets = true
..separator = ListSeparator.UNDECIDED),
"[]");
});
group("with a comma separator", () {
_testSerializationAndRoundTrip(
Value()
..list = (Value_List()
..hasBrackets = true
..separator = ListSeparator.COMMA),
"[]");
});
group("with a space separator", () {
_testSerializationAndRoundTrip(
Value()
..list = (Value_List()
..hasBrackets = true
..separator = ListSeparator.SPACE),
"[]");
});
group("with a slash separator", () {
_testSerializationAndRoundTrip(
Value()
..list = (Value_List()
..hasBrackets = true
..separator = ListSeparator.SLASH),
"[]");
});
});
group("without brackets", () {
group("with unknown separator", () {
_testSerializationAndRoundTrip(
Value()
..list = (Value_List()
..hasBrackets = false
..separator = ListSeparator.UNDECIDED),
"()",
inspect: true);
});
group("with a comma separator", () {
_testSerializationAndRoundTrip(
Value()
..list = (Value_List()
..hasBrackets = false
..separator = ListSeparator.COMMA),
"()",
inspect: true);
});
group("with a space separator", () {
_testSerializationAndRoundTrip(
Value()
..list = (Value_List()
..hasBrackets = false
..separator = ListSeparator.SPACE),
"()",
inspect: true);
});
group("with a slash separator", () {
_testSerializationAndRoundTrip(
Value()
..list = (Value_List()
..hasBrackets = false
..separator = ListSeparator.SLASH),
"()",
inspect: true);
});
});
});
group("with one element", () {
group("with brackets", () {
group("with unknown separator", () {
_testSerializationAndRoundTrip(
Value()
..list = (Value_List()
..contents.add(_true)
..hasBrackets = true
..separator = ListSeparator.UNDECIDED),
"[true]");
});
test("with a comma separator", () async {
expect(
await _deprotofy(
Value()
..list = (Value_List()
..contents.add(_true)
..hasBrackets = true
..separator = ListSeparator.COMMA),
inspect: true),
"[true,]");
});
group("with a space separator", () {
_testSerializationAndRoundTrip(
Value()
..list = (Value_List()
..contents.add(_true)
..hasBrackets = true
..separator = ListSeparator.SPACE),
"[true]");
});
group("with a slash separator", () {
_testSerializationAndRoundTrip(
Value()
..list = (Value_List()
..contents.add(_true)
..hasBrackets = true
..separator = ListSeparator.SLASH),
"[true]");
});
});
group("without brackets", () {
group("with unknown separator", () {
_testSerializationAndRoundTrip(
Value()
..list = (Value_List()
..contents.add(_true)
..hasBrackets = false
..separator = ListSeparator.UNDECIDED),
"true");
});
test("with a comma separator", () async {
expect(
await _deprotofy(
Value()
..list = (Value_List()
..contents.add(_true)
..hasBrackets = false
..separator = ListSeparator.COMMA),
inspect: true),
"(true,)");
});
group("with a space separator", () {
_testSerializationAndRoundTrip(
Value()
..list = (Value_List()
..contents.add(_true)
..hasBrackets = false
..separator = ListSeparator.SPACE),
"true");
});
group("with a slash separator", () {
_testSerializationAndRoundTrip(
Value()
..list = (Value_List()
..contents.add(_true)
..hasBrackets = false
..separator = ListSeparator.SLASH),
"true");
});
});
});
group("with multiple elements", () {
group("with brackets", () {
test("with a comma separator", () async {
expect(
await _deprotofy(
Value()
..list = (Value_List()
..contents.addAll([_true, _null, _false])
..hasBrackets = true
..separator = ListSeparator.COMMA),
inspect: true),
"[true, null, false]");
});
test("with a space separator", () async {
expect(
await _deprotofy(
Value()
..list = (Value_List()
..contents.addAll([_true, _null, _false])
..hasBrackets = true
..separator = ListSeparator.SPACE),
inspect: true),
"[true null false]");
});
test("with a slash separator", () async {
expect(
await _deprotofy(
Value()
..list = (Value_List()
..contents.addAll([_true, _null, _false])
..hasBrackets = true
..separator = ListSeparator.SLASH),
inspect: true),
"[true / null / false]");
});
});
group("without brackets", () {
test("with a comma separator", () async {
expect(
await _deprotofy(
Value()
..list = (Value_List()
..contents.addAll([_true, _null, _false])
..hasBrackets = false
..separator = ListSeparator.COMMA),
inspect: true),
"true, null, false");
});
test("with a space separator", () async {
expect(
await _deprotofy(
Value()
..list = (Value_List()
..contents.addAll([_true, _null, _false])
..hasBrackets = false
..separator = ListSeparator.SPACE),
inspect: true),
"true null false");
});
test("with a slash separator", () async {
expect(
await _deprotofy(
Value()
..list = (Value_List()
..contents.addAll([_true, _null, _false])
..hasBrackets = false
..separator = ListSeparator.SLASH),
inspect: true),
"true / null / false");
});
});
});
});
group("an argument list", () {
test("with no elements", () async {
expect(
await _roundTrip(Value()
..argumentList =
(Value_ArgumentList()..separator = ListSeparator.UNDECIDED)),
equals(Value()
..argumentList = (Value_ArgumentList()
..id = 1
..separator = ListSeparator.UNDECIDED)));
});
test("with comma separator", () async {
expect(
await _roundTrip(Value()
..argumentList = (Value_ArgumentList()
..contents.addAll([_true, _false, _null])
..separator = ListSeparator.COMMA)),
equals(Value()
..argumentList = (Value_ArgumentList()
..id = 1
..contents.addAll([_true, _false, _null])
..separator = ListSeparator.COMMA)));
});
test("with space separator", () async {
expect(
await _roundTrip(Value()
..argumentList = (Value_ArgumentList()
..contents.addAll([_true, _false, _null])
..separator = ListSeparator.SPACE)),
equals(Value()
..argumentList = (Value_ArgumentList()
..id = 1
..contents.addAll([_true, _false, _null])
..separator = ListSeparator.SPACE)));
});
test("with slash separator", () async {
expect(
await _roundTrip(Value()
..argumentList = (Value_ArgumentList()
..contents.addAll([_true, _false, _null])
..separator = ListSeparator.SLASH)),
equals(Value()
..argumentList = (Value_ArgumentList()
..id = 1
..contents.addAll([_true, _false, _null])
..separator = ListSeparator.SLASH)));
});
test("with keywords", () async {
expect(
await _roundTrip(Value()
..argumentList = (Value_ArgumentList()
..keywords.addAll({"arg1": _true, "arg2": _false})
..separator = ListSeparator.COMMA)),
equals(Value()
..argumentList = (Value_ArgumentList()
..id = 1
..keywords.addAll({"arg1": _true, "arg2": _false})
..separator = ListSeparator.COMMA)));
});
});
group("a map", () {
group("with no elements", () {
_testSerializationAndRoundTrip(Value()..map = Value_Map(), "()",
inspect: true);
});
test("with one element", () async {
expect(
await _deprotofy(
Value()
..map = (Value_Map()
..entries.add(Value_Map_Entry()
..key = _true
..value = _false)),
inspect: true),
"(true: false)");
});
test("with multiple elements", () async {
expect(
await _deprotofy(
Value()
..map = (Value_Map()
..entries.addAll([
Value_Map_Entry()
..key = _true
..value = _false,
Value_Map_Entry()
..key =
(Value()..number = (Value_Number()..value = 1.0))
..value =
(Value()..number = (Value_Number()..value = 2.0)),
Value_Map_Entry()
..key = (Value()
..string = (Value_String()
..text = "a"
..quoted = false))
..value = (Value()
..string = (Value_String()
..text = "b"
..quoted = false))
])),
inspect: true),
"(true: false, 1: 2, a: b)");
});
});
group("a calculation", () {
test("with a string argument", () async {
expect(
await _deprotofy(Value()
..calculation = (Value_Calculation()
..name = "calc"
..arguments.add(Value_Calculation_CalculationValue()
..string = "var(--foo)"))),
"calc(var(--foo))");
});
test("with an interpolation argument", () async {
expect(
await _deprotofy(Value()
..calculation = (Value_Calculation()
..name = "calc"
..arguments.add(Value_Calculation_CalculationValue()
..interpolation = "var(--foo)"))),
"calc(var(--foo))");
});
test("with number arguments", () async {
expect(
await _deprotofy(Value()
..calculation = (Value_Calculation()
..name = "clamp"
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()
..value = 1.0
..numerators.add("%")))
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()
..value = 2.0
..numerators.add("px")))
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()
..value = 3.0
..numerators.add("em"))))),
"clamp(1%, 2px, 3em)");
});
test("with a calculation argument", () async {
expect(
await _deprotofy(Value()
..calculation = (Value_Calculation()
..name = "min"
..arguments.add(Value_Calculation_CalculationValue()
..calculation = (Value_Calculation()
..name = "max"
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()
..value = 1.0
..numerators.add("%")))
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()
..value = 2.0
..numerators.add("px")))))
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()
..value = 3.0
..numerators.add("em"))))),
"min(max(1%, 2px), 3em)");
});
test("with an operation", () async {
expect(
await _deprotofy(Value()
..calculation = (Value_Calculation()
..name = "calc"
..arguments.add(Value_Calculation_CalculationValue()
..operation = (Value_Calculation_CalculationOperation()
..operator = CalculationOperator.PLUS
..left = (Value_Calculation_CalculationValue()
..number = (Value_Number()
..value = 1.0
..numerators.add("%")))
..right = (Value_Calculation_CalculationValue()
..number = (Value_Number()
..value = 2.0
..numerators.add("px"))))))),
"calc(1% + 2px)");
});
group("simplifies", () {
test("an operation", () async {
expect(
await _deprotofy(Value()
..calculation = (Value_Calculation()
..name = "calc"
..arguments.add(Value_Calculation_CalculationValue()
..operation = (Value_Calculation_CalculationOperation()
..operator = CalculationOperator.PLUS
..left = (Value_Calculation_CalculationValue()
..number = (Value_Number()..value = 1.0))
..right = (Value_Calculation_CalculationValue()
..number = (Value_Number()..value = 2.0)))))),
"3");
});
test("a nested operation", () async {
expect(
await _deprotofy(Value()
..calculation = (Value_Calculation()
..name = "calc"
..arguments.add(Value_Calculation_CalculationValue()
..operation = (Value_Calculation_CalculationOperation()
..operator = CalculationOperator.PLUS
..left = (Value_Calculation_CalculationValue()
..number = (Value_Number()
..value = 1.0
..numerators.add("%")))
..right = (Value_Calculation_CalculationValue()
..operation = (Value_Calculation_CalculationOperation()
..operator = CalculationOperator.PLUS
..left = (Value_Calculation_CalculationValue()
..number = (Value_Number()
..value = 2.0
..numerators.add("px")))
..right = (Value_Calculation_CalculationValue()
..number = (Value_Number()
..value = 3.0
..numerators.add("px"))))))))),
"calc(1% + 5px)");
});
test("min", () async {
expect(
await _deprotofy(Value()
..calculation = (Value_Calculation()
..name = "min"
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()..value = 1.0))
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()..value = 2.0))
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()..value = 3.0)))),
"1");
});
test("max", () async {
expect(
await _deprotofy(Value()
..calculation = (Value_Calculation()
..name = "max"
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()..value = 1.0))
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()..value = 2.0))
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()..value = 3.0)))),
"3");
});
test("clamp", () async {
expect(
await _deprotofy(Value()
..calculation = (Value_Calculation()
..name = "clamp"
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()..value = 1.0))
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()..value = 2.0))
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()..value = 3.0)))),
"2");
});
});
});
test("true", () async {
expect(await _deprotofy(_true), equals("true"));
});
test("false", () async {
expect(await _deprotofy(_false), equals("false"));
});
test("null", () async {
expect(await _deprotofy(_null, inspect: true), equals("null"));
});
group("and rejects", () {
group("a color", () {
test("with red above 255", () async {
await _expectDeprotofyError(_rgb(256, 0, 0, 1.0),
"RgbColor.red must be between 0 and 255, was 256");
});
test("with green above 255", () async {
await _expectDeprotofyError(_rgb(0, 256, 0, 1.0),
"RgbColor.green must be between 0 and 255, was 256");
});
test("with blue above 255", () async {
await _expectDeprotofyError(_rgb(0, 0, 256, 1.0),
"RgbColor.blue must be between 0 and 255, was 256");
});
test("with RGB alpha below 0", () async {
await _expectDeprotofyError(_rgb(0, 0, 0, -0.1),
"RgbColor.alpha must be between 0 and 1, was -0.1");
});
test("with RGB alpha above 1", () async {
await _expectDeprotofyError(_rgb(0, 0, 0, 1.1),
"RgbColor.alpha must be between 0 and 1, was 1.1");
});
test("with saturation below 0", () async {
await _expectDeprotofyError(_hsl(0, -0.1, 0, 1.0),
"HslColor.saturation must be between 0 and 100, was -0.1");
});
test("with saturation above 100", () async {
await _expectDeprotofyError(
_hsl(0, 100.1, 0, 1.0),
"HslColor.saturation must be between 0 and 100, was "
"100.1");
});
test("with lightness below 0", () async {
await _expectDeprotofyError(_hsl(0, 0, -0.1, 1.0),
"HslColor.lightness must be between 0 and 100, was -0.1");
});
test("with lightness above 100", () async {
await _expectDeprotofyError(
_hsl(0, 0, 100.1, 1.0),
"HslColor.lightness must be between 0 and 100, was "
"100.1");
});
test("with HSL alpha below 0", () async {
await _expectDeprotofyError(_hsl(0, 0, 0, -0.1),
"HslColor.alpha must be between 0 and 1, was -0.1");
});
test("with HSL alpha above 1", () async {
await _expectDeprotofyError(_hsl(0, 0, 0, 1.1),
"HslColor.alpha must be between 0 and 1, was 1.1");
});
});
test("a list with multiple elements and an unknown separator", () async {
await _expectDeprotofyError(
Value()
..list = (Value_List()
..contents.addAll([_true, _false])
..separator = ListSeparator.UNDECIDED),
endsWith("can't have an undecided separator because it has 2 "
"elements"));
});
test("an arglist with an unknown id", () async {
await _expectDeprotofyError(
Value()..argumentList = (Value_ArgumentList()..id = 1),
equals(
"Value.ArgumentList.id 1 doesn't match any known argument lists"));
});
group("a calculation", () {
group("with too many arguments", () {
test("for calc", () async {
await _expectDeprotofyError(
Value()
..calculation = (Value_Calculation()
..name = "calc"
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()..value = 1.0))
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()..value = 2.0))),
equals("Value.Calculation.arguments must have exactly one "
"argument for calc()."));
});
test("for clamp", () async {
await _expectDeprotofyError(
Value()
..calculation = (Value_Calculation()
..name = "clamp"
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()..value = 1.0))
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()..value = 2.0))
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()..value = 3.0))
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()..value = 4.0))),
equals("Value.Calculation.arguments must have exactly 3 "
"arguments for clamp()."));
});
});
group("with too few arguments", () {
test("for calc", () async {
await _expectDeprotofyError(
Value()..calculation = (Value_Calculation()..name = "calc"),
equals("Value.Calculation.arguments must have exactly one "
"argument for calc()."));
});
test("for clamp", () async {
await _expectDeprotofyError(
Value()
..calculation = (Value_Calculation()
..name = "clamp"
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()..value = 1.0))
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()..value = 2.0))),
equals("Value.Calculation.arguments must have exactly 3 "
"arguments for clamp()."));
});
test("for min", () async {
await _expectDeprotofyError(
Value()..calculation = (Value_Calculation()..name = "min"),
equals("Value.Calculation.arguments must have at least 1 "
"argument for min()."));
});
test("for max", () async {
await _expectDeprotofyError(
Value()..calculation = (Value_Calculation()..name = "max"),
equals("Value.Calculation.arguments must have at least 1 "
"argument for max()."));
});
});
test("reports a compilation failure when simplification fails",
() async {
_process.inbound
.add(compileString("a {b: foo()}", functions: [r"foo()"]));
var request = getFunctionCallRequest(await _process.outbound.next);
expect(request.arguments, isEmpty);
_process.inbound.add(InboundMessage()
..functionCallResponse = (InboundMessage_FunctionCallResponse()
..id = request.id
..success = (Value()
..calculation = (Value_Calculation()
..name = "min"
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()
..value = 1.0
..numerators.add("px")))
..arguments.add(Value_Calculation_CalculationValue()
..number = (Value_Number()
..value = 2.0
..numerators.add("s")))))));
var failure = getCompileFailure(await _process.outbound.next);
expect(failure.message, equals("1px and 2s are incompatible."));
expect(_process.kill(), completes);
});
});
group("reports a compilation error for a function with a signature", () {
Future<void> expectSignatureError(
String signature, Object message) async {
_process.inbound.add(
compileString("a {b: inspect(foo())}", functions: [r"foo()"]));
var request = getFunctionCallRequest(await _process.outbound.next);
expect(request.arguments, isEmpty);
_process.inbound.add(InboundMessage()
..functionCallResponse = (InboundMessage_FunctionCallResponse()
..id = request.id
..success = (Value()
..hostFunction = (Value_HostFunction()
..id = 1234
..signature = signature))));
var failure = getCompileFailure(await _process.outbound.next);
expect(failure.message, message);
expect(_process.kill(), completes);
}
test("that's empty", () async {
await expectSignatureError(
"", r'Invalid signature "": Expected identifier.');
});
test("that's just a name", () async {
await expectSignatureError(
"foo", r'Invalid signature "foo": expected "(".');
});
test("without a closing paren", () async {
await expectSignatureError(
r"foo($bar", r'Invalid signature "foo($bar": expected ")".');
});
test("with text after the closing paren", () async {
await expectSignatureError(r"foo() ",
r'Invalid signature "foo() ": expected no more input.');
});
test("with invalid arguments", () async {
await expectSignatureError(
r"foo($)", r'Invalid signature "foo($)": Expected identifier.');
});
});
});
});
}
/// Evaluates [sassScript] in the compiler, passes it to a custom function, and
/// returns the protocol buffer result.
Future<Value> _protofy(String sassScript) async {
_process.inbound.add(compileString("""
@use 'sass:list';
@use 'sass:map';
@use 'sass:math';
@use 'sass:meta';
@function capture-args(\$args...) {
\$_: meta.keywords(\$args);
@return \$args;
}
\$_: foo(($sassScript));
""", functions: [r"foo($arg)"]));
var request = getFunctionCallRequest(await _process.outbound.next);
expect(_process.kill(), completes);
return request.arguments.single;
}
/// Defines two tests: one that asserts that [value] is serialized to the CSS
/// value [expected], and one that asserts that it survives a round trip in the
/// same protocol buffer format.
///
/// This is necessary for values that can be serialized but also have metadata
/// that's not visible in the serialized form.
void _testSerializationAndRoundTrip(Value value, String expected,
{bool inspect = false}) {
test("is serialized correctly",
() async => expect(await _deprotofy(value, inspect: inspect), expected));
test("preserves metadata", () => _assertRoundTrips(value));
}
/// Sends [value] to the compiler and returns its string serialization.
///
/// If [inspect] is true, this returns the value as serialized by the
/// `meta.inspect()` function.
Future<String> _deprotofy(Value value, {bool inspect = false}) async {
_process.inbound.add(compileString(
inspect ? "a {b: inspect(foo())}" : "a {b: foo()}",
functions: [r"foo()"]));
var request = getFunctionCallRequest(await _process.outbound.next);
expect(request.arguments, isEmpty);
_process.inbound.add(InboundMessage()
..functionCallResponse = (InboundMessage_FunctionCallResponse()
..id = request.id
..success = value));
var success = getCompileSuccess(await _process.outbound.next);
expect(_process.kill(), completes);
return RegExp(r" b: (.*);").firstMatch(success.css)![1]!;
}
/// Asserts that [value] causes a parameter error with a message matching
/// [message] when deserializing it from a protocol buffer.
Future<void> _expectDeprotofyError(Value value, Object message) async {
_process.inbound.add(compileString("a {b: foo()}", functions: [r"foo()"]));
var request = getFunctionCallRequest(await _process.outbound.next);
expect(request.arguments, isEmpty);
_process.inbound.add(InboundMessage()
..functionCallResponse = (InboundMessage_FunctionCallResponse()
..id = request.id
..success = value));
await expectParamsError(_process, errorId, message);
await _process.kill();
}
/// Sends [value] to the compiler to convert to a native Sass value, then sends
/// it back out to the host as a protocol buffer and asserts the two buffers are
/// identical.
///
/// Generally [_deprotofy] should be used instead unless there are details about
/// the internal structure of the value that won't show up in its string
/// representation.
Future<void> _assertRoundTrips(Value value) async =>
expect(await _roundTrip(value), equals(value));
/// Sends [value] to the compiler to convert to a native Sass value, then sends
/// it back out to the host as a protocol buffer and returns the result.
Future<Value> _roundTrip(Value value) async {
_process.inbound.add(compileString("""
\$_: outbound(inbound());
""", functions: ["inbound()", r"outbound($arg)"]));
var request = getFunctionCallRequest(await _process.outbound.next);
expect(request.arguments, isEmpty);
_process.inbound.add(InboundMessage()
..functionCallResponse = (InboundMessage_FunctionCallResponse()
..id = request.id
..success = value));
request = getFunctionCallRequest(await _process.outbound.next);
expect(_process.kill(), completes);
return request.arguments.single;
}
/// Returns a [Value] that's an RGB color with the given fields.
Value _rgb(int red, int green, int blue, double alpha) => Value()
..rgbColor = (Value_RgbColor()
..red = red
..green = green
..blue = blue
..alpha = alpha);
/// Returns a [Value] that's an HSL color with the given fields.
Value _hsl(num hue, num saturation, num lightness, double alpha) => Value()
..hslColor = (Value_HslColor()
..hue = hue * 1.0
..saturation = saturation * 1.0
..lightness = lightness * 1.0
..alpha = alpha);
/// Asserts that [process] emits a [CompileFailure] result with the given
/// [message] on its protobuf stream and causes the compilation to fail.
Future<void> _expectFunctionError(
EmbeddedProcess process, Object message) async {
var failure = getCompileFailure(await process.outbound.next);
expect(failure.message, equals(message));
}