2018-01-21 01:45:17 +01:00
|
|
|
// Copyright 2018 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.
|
|
|
|
|
|
|
|
@TestOn('node')
|
2018-11-16 00:16:24 +01:00
|
|
|
@Tags(['node'])
|
2018-01-21 01:45:17 +01:00
|
|
|
|
|
|
|
import 'dart:async';
|
|
|
|
import 'dart:js_util';
|
|
|
|
|
|
|
|
import 'package:js/js.dart';
|
|
|
|
import 'package:test/test.dart';
|
|
|
|
|
|
|
|
import '../ensure_npm_package.dart';
|
|
|
|
import 'api.dart';
|
|
|
|
import 'utils.dart';
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
setUpAll(ensureNpmPackage);
|
|
|
|
useSandbox();
|
|
|
|
|
|
|
|
group("rejects a signature", () {
|
|
|
|
test("with an invalid argument list", () {
|
2018-11-16 00:16:24 +01:00
|
|
|
var error = renderSyncError(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: "", functions: jsify({"foo(": allowInterop(neverCalled)})));
|
|
|
|
expect(error.toString(), contains('Invalid signature'));
|
|
|
|
});
|
|
|
|
|
|
|
|
test("that's an empty string", () {
|
2018-11-16 00:16:24 +01:00
|
|
|
var error = renderSyncError(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: "", functions: jsify({"": allowInterop(neverCalled)})));
|
|
|
|
expect(error.toString(), contains('Invalid signature'));
|
|
|
|
});
|
|
|
|
|
|
|
|
test("that's just an argument list", () {
|
2018-11-16 00:16:24 +01:00
|
|
|
var error = renderSyncError(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: "", functions: jsify({r"($var)": allowInterop(neverCalled)})));
|
|
|
|
expect(error.toString(), contains('Invalid signature'));
|
|
|
|
});
|
|
|
|
|
|
|
|
test("with an invalid identifier", () {
|
2018-11-16 00:16:24 +01:00
|
|
|
var error = renderSyncError(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: "", functions: jsify({"~~~": allowInterop(neverCalled)})));
|
|
|
|
expect(error.toString(), contains('Invalid signature'));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
group("allows a signature", () {
|
|
|
|
test("with no argument list", () {
|
|
|
|
expect(
|
2018-11-16 00:16:24 +01:00
|
|
|
renderSync(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: "a {b: foo()}",
|
|
|
|
functions: jsify({
|
|
|
|
"foo": allowInterop(expectAsync0(
|
|
|
|
() => callConstructor(sass.types.Number, [12])))
|
|
|
|
}))),
|
|
|
|
equalsIgnoringWhitespace("a { b: 12; }"));
|
|
|
|
});
|
|
|
|
|
|
|
|
test("with an empty argument list", () {
|
|
|
|
expect(
|
2018-11-16 00:16:24 +01:00
|
|
|
renderSync(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: "a {b: foo()}",
|
|
|
|
functions: jsify({
|
|
|
|
"foo()": allowInterop(expectAsync0(
|
|
|
|
() => callConstructor(sass.types.Number, [12])))
|
|
|
|
}))),
|
|
|
|
equalsIgnoringWhitespace("a { b: 12; }"));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2019-09-04 01:00:07 +02:00
|
|
|
group("are dash-normalized", () {
|
|
|
|
test("when defined with dashes", () {
|
|
|
|
expect(
|
|
|
|
renderSync(RenderOptions(
|
|
|
|
data: "a {b: foo_bar()}",
|
|
|
|
functions: jsify({
|
|
|
|
"foo-bar": allowInterop(expectAsync0(
|
|
|
|
() => callConstructor(sass.types.Number, [12])))
|
|
|
|
}))),
|
|
|
|
equalsIgnoringWhitespace("a { b: 12; }"));
|
|
|
|
});
|
|
|
|
|
|
|
|
test("when defined with underscores", () {
|
|
|
|
expect(
|
|
|
|
renderSync(RenderOptions(
|
|
|
|
data: "a {b: foo-bar()}",
|
|
|
|
functions: jsify({
|
|
|
|
"foo_bar": allowInterop(expectAsync0(
|
|
|
|
() => callConstructor(sass.types.Number, [12])))
|
|
|
|
}))),
|
|
|
|
equalsIgnoringWhitespace("a { b: 12; }"));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2018-01-21 01:45:17 +01:00
|
|
|
group("rejects function calls that", () {
|
|
|
|
test("have too few arguments", () {
|
2018-11-16 00:16:24 +01:00
|
|
|
var error = renderSyncError(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: "a {b: foo()}",
|
|
|
|
functions: jsify({r"foo($var)": allowInterop(neverCalled)})));
|
|
|
|
expect(error.toString(), contains(r'Missing argument $var'));
|
|
|
|
});
|
|
|
|
|
|
|
|
test("have too many arguments", () {
|
2018-11-16 00:16:24 +01:00
|
|
|
var error = renderSyncError(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: "a {b: foo(1, 2)}",
|
|
|
|
functions: jsify({r"foo($var)": allowInterop(neverCalled)})));
|
|
|
|
expect(error.toString(),
|
|
|
|
contains('Only 1 argument allowed, but 2 were passed.'));
|
|
|
|
});
|
|
|
|
|
|
|
|
test("passes a non-existent named argument", () {
|
2018-11-16 00:16:24 +01:00
|
|
|
var error = renderSyncError(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: r"a {b: foo($val: 1)}",
|
|
|
|
functions: jsify({r"foo()": allowInterop(neverCalled)})));
|
|
|
|
expect(error.toString(), contains(r'No argument named $val.'));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
group("passes arguments", () {
|
|
|
|
test("by position", () {
|
|
|
|
expect(
|
2018-11-16 00:16:24 +01:00
|
|
|
renderSync(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: "a {b: last(1px, 2em)}",
|
|
|
|
functions: jsify({
|
|
|
|
r"last($value1, $value2)":
|
|
|
|
allowInterop(expectAsync2((value1, value2) => value2))
|
|
|
|
}))),
|
|
|
|
equalsIgnoringWhitespace("a { b: 2em; }"));
|
|
|
|
});
|
|
|
|
|
|
|
|
test("by name", () {
|
|
|
|
expect(
|
2018-11-16 00:16:24 +01:00
|
|
|
renderSync(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: r"a {b: last($value2: 1px, $value1: 2em)}",
|
|
|
|
functions: jsify({
|
|
|
|
r"last($value1, $value2)":
|
|
|
|
allowInterop(expectAsync2((value1, value2) => value2))
|
|
|
|
}))),
|
|
|
|
equalsIgnoringWhitespace("a { b: 1px; }"));
|
|
|
|
});
|
|
|
|
|
|
|
|
test("by splat", () {
|
|
|
|
expect(
|
2018-11-16 00:16:24 +01:00
|
|
|
renderSync(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: "a {b: last((1px 2em)...)}",
|
|
|
|
functions: jsify({
|
|
|
|
r"last($value1, $value2)":
|
|
|
|
allowInterop(expectAsync2((value1, value2) => value2))
|
|
|
|
}))),
|
|
|
|
equalsIgnoringWhitespace("a { b: 2em; }"));
|
|
|
|
});
|
|
|
|
|
|
|
|
test("by arglist", () {
|
|
|
|
expect(
|
2018-11-16 00:16:24 +01:00
|
|
|
renderSync(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: "a {b: last(1px, 2em)}",
|
|
|
|
functions: jsify({
|
|
|
|
r"last($args...)": allowInterop(expectAsync1(
|
|
|
|
(NodeSassList args) => args.getValue(args.getLength() - 1)))
|
|
|
|
}))),
|
|
|
|
equalsIgnoringWhitespace("a { b: 2em; }"));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
group("rejects a return value that", () {
|
|
|
|
test("isn't a Sass value", () {
|
2018-11-16 00:16:24 +01:00
|
|
|
var error = renderSyncError(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: "a {b: foo()}",
|
|
|
|
functions: jsify({"foo": allowInterop(expectAsync0(() => 10))})));
|
|
|
|
expect(error.toString(), contains('must be a Sass value type'));
|
|
|
|
});
|
|
|
|
|
|
|
|
test("is null", () {
|
2018-11-16 00:16:24 +01:00
|
|
|
var error = renderSyncError(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: "a {b: foo()}",
|
|
|
|
functions: jsify({"foo": allowInterop(expectAsync0(() => null))})));
|
|
|
|
expect(error.toString(), contains('must be a Sass value type'));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
test("gracefully handles an error from the function", () {
|
2018-11-16 00:16:24 +01:00
|
|
|
var error = renderSyncError(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: "a {b: foo()}",
|
|
|
|
functions: jsify({"foo": allowInterop(() => throw "aw beans")})));
|
|
|
|
expect(error.toString(), contains('aw beans'));
|
|
|
|
});
|
|
|
|
|
|
|
|
group("render()", () {
|
|
|
|
test("runs a synchronous function", () {
|
|
|
|
expect(
|
2018-11-16 00:16:24 +01:00
|
|
|
render(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: "a {b: foo()}",
|
|
|
|
functions: jsify({
|
|
|
|
"foo":
|
|
|
|
allowInterop((_) => callConstructor(sass.types.Number, [1]))
|
|
|
|
}))),
|
|
|
|
completion(equalsIgnoringWhitespace("a { b: 1; }")));
|
|
|
|
});
|
|
|
|
|
|
|
|
test("runs an asynchronous function", () {
|
|
|
|
expect(
|
2018-11-16 00:16:24 +01:00
|
|
|
render(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: "a {b: foo()}",
|
|
|
|
functions: jsify({
|
|
|
|
"foo": allowInterop((done) {
|
2019-05-31 16:45:27 +02:00
|
|
|
Timer(Duration.zero, () {
|
2018-01-21 01:45:17 +01:00
|
|
|
done(callConstructor(sass.types.Number, [1]));
|
|
|
|
});
|
|
|
|
})
|
|
|
|
}))),
|
|
|
|
completion(equalsIgnoringWhitespace("a { b: 1; }")));
|
|
|
|
});
|
|
|
|
|
|
|
|
test("reports a synchronous error", () async {
|
2018-11-16 00:16:24 +01:00
|
|
|
var error = await renderError(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: "a {b: foo()}",
|
|
|
|
functions: jsify({"foo": allowInterop((_) => throw "aw beans")})));
|
|
|
|
expect(error.toString(), contains('aw beans'));
|
|
|
|
});
|
|
|
|
|
2019-04-03 10:07:12 +02:00
|
|
|
test("reports a synchronous sass.types.Error", () async {
|
|
|
|
var error = await renderError(RenderOptions(
|
|
|
|
data: "a {b: foo()}",
|
|
|
|
functions: jsify({
|
|
|
|
"foo": allowInterop(
|
|
|
|
(_) => callConstructor(sass.types.Error, ["aw beans"]))
|
|
|
|
})));
|
|
|
|
expect(error.toString(), contains('aw beans'));
|
|
|
|
});
|
|
|
|
|
2018-01-21 01:45:17 +01:00
|
|
|
test("reports an asynchronous error", () async {
|
2018-11-16 00:16:24 +01:00
|
|
|
var error = await renderError(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: "a {b: foo()}",
|
|
|
|
functions: jsify({
|
|
|
|
"foo": allowInterop((done) {
|
2019-05-31 16:45:27 +02:00
|
|
|
Timer(Duration.zero, () {
|
2018-11-16 00:16:24 +01:00
|
|
|
done(JSError("aw beans"));
|
2018-01-21 01:45:17 +01:00
|
|
|
});
|
|
|
|
})
|
|
|
|
})));
|
|
|
|
expect(error.toString(), contains('aw beans'));
|
|
|
|
});
|
|
|
|
|
2019-04-03 10:07:12 +02:00
|
|
|
test("reports an asynchronous sass.types.Error", () async {
|
|
|
|
var error = await renderError(RenderOptions(
|
|
|
|
data: "a {b: foo()}",
|
|
|
|
functions: jsify({
|
|
|
|
"foo": allowInterop((done) {
|
2019-05-31 16:45:27 +02:00
|
|
|
Timer(Duration.zero, () {
|
2019-04-03 10:07:12 +02:00
|
|
|
done(callConstructor(sass.types.Error, ["aw beans"]));
|
|
|
|
});
|
|
|
|
})
|
|
|
|
})));
|
|
|
|
expect(error.toString(), contains('aw beans'));
|
|
|
|
});
|
|
|
|
|
2018-01-21 01:45:17 +01:00
|
|
|
test("reports a null return", () async {
|
2018-11-16 00:16:24 +01:00
|
|
|
var error = await renderError(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: "a {b: foo()}",
|
|
|
|
functions: jsify({
|
|
|
|
"foo": allowInterop((done) {
|
2019-05-31 16:45:27 +02:00
|
|
|
Timer(Duration.zero, () {
|
2018-01-21 01:45:17 +01:00
|
|
|
done(null);
|
|
|
|
});
|
|
|
|
})
|
|
|
|
})));
|
|
|
|
expect(error.toString(), contains('must be a Sass value type'));
|
|
|
|
});
|
|
|
|
|
|
|
|
test("reports a call to done without arguments", () async {
|
2018-11-16 00:16:24 +01:00
|
|
|
var error = await renderError(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: "a {b: foo()}",
|
|
|
|
functions: jsify({
|
|
|
|
"foo": allowInterop((done) {
|
2019-05-31 16:45:27 +02:00
|
|
|
Timer(Duration.zero, () {
|
2018-01-21 01:45:17 +01:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
})
|
|
|
|
})));
|
|
|
|
expect(error.toString(), contains('must be a Sass value type'));
|
|
|
|
});
|
|
|
|
|
|
|
|
test("reports an invalid signature", () async {
|
2018-11-16 00:16:24 +01:00
|
|
|
var error = await renderError(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: "", functions: jsify({"foo(": allowInterop(neverCalled)})));
|
|
|
|
expect(error.toString(), contains('Invalid signature'));
|
|
|
|
});
|
|
|
|
|
|
|
|
group("with fibers", () {
|
|
|
|
setUpAll(() {
|
|
|
|
try {
|
|
|
|
fiber;
|
|
|
|
} catch (_) {
|
|
|
|
throw "Can't load fibers package.\n"
|
2018-06-26 01:39:35 +02:00
|
|
|
"Run pub run grinder before-test.";
|
2018-01-21 01:45:17 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
test("runs a synchronous function", () {
|
|
|
|
expect(
|
2018-11-16 00:16:24 +01:00
|
|
|
render(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: "a {b: foo()}",
|
|
|
|
functions: jsify({
|
|
|
|
"foo": allowInterop(
|
|
|
|
(_) => callConstructor(sass.types.Number, [1]))
|
|
|
|
}),
|
|
|
|
fiber: fiber)),
|
|
|
|
completion(equalsIgnoringWhitespace("a { b: 1; }")));
|
|
|
|
});
|
|
|
|
|
|
|
|
test("runs an asynchronous function", () {
|
|
|
|
expect(
|
2018-11-16 00:16:24 +01:00
|
|
|
render(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: "a {b: foo()}",
|
|
|
|
functions: jsify({
|
|
|
|
"foo": allowInterop((done) {
|
2019-05-31 16:45:27 +02:00
|
|
|
Timer(Duration.zero, () {
|
2018-01-21 01:45:17 +01:00
|
|
|
done(callConstructor(sass.types.Number, [1]));
|
|
|
|
});
|
|
|
|
})
|
|
|
|
}),
|
|
|
|
fiber: fiber)),
|
|
|
|
completion(equalsIgnoringWhitespace("a { b: 1; }")));
|
|
|
|
});
|
|
|
|
|
|
|
|
test("reports a synchronous error", () async {
|
2018-11-16 00:16:24 +01:00
|
|
|
var error = await renderError(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: "a {b: foo()}",
|
|
|
|
functions: jsify({"foo": allowInterop((_) => throw "aw beans")}),
|
|
|
|
fiber: fiber));
|
|
|
|
expect(error.toString(), contains('aw beans'));
|
|
|
|
});
|
|
|
|
|
|
|
|
test("reports an asynchronous error", () async {
|
2018-11-16 00:16:24 +01:00
|
|
|
var error = await renderError(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: "a {b: foo()}",
|
|
|
|
functions: jsify({
|
|
|
|
"foo": allowInterop((done) {
|
2019-05-31 16:45:27 +02:00
|
|
|
Timer(Duration.zero, () {
|
2018-11-16 00:16:24 +01:00
|
|
|
done(JSError("aw beans"));
|
2018-01-21 01:45:17 +01:00
|
|
|
});
|
|
|
|
})
|
|
|
|
}),
|
|
|
|
fiber: fiber));
|
|
|
|
expect(error.toString(), contains('aw beans'));
|
|
|
|
});
|
|
|
|
|
|
|
|
test("reports a null return", () async {
|
2018-11-16 00:16:24 +01:00
|
|
|
var error = await renderError(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: "a {b: foo()}",
|
|
|
|
functions: jsify({
|
|
|
|
"foo": allowInterop((done) {
|
2019-05-31 16:45:27 +02:00
|
|
|
Timer(Duration.zero, () {
|
2018-01-21 01:45:17 +01:00
|
|
|
done(null);
|
|
|
|
});
|
|
|
|
})
|
|
|
|
}),
|
|
|
|
fiber: fiber));
|
|
|
|
expect(error.toString(), contains('must be a Sass value type'));
|
|
|
|
});
|
|
|
|
|
|
|
|
test("reports a call to done without arguments", () async {
|
2018-11-16 00:16:24 +01:00
|
|
|
var error = await renderError(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: "a {b: foo()}",
|
|
|
|
functions: jsify({
|
|
|
|
"foo": allowInterop((done) {
|
2019-05-31 16:45:27 +02:00
|
|
|
Timer(Duration.zero, () {
|
2018-01-21 01:45:17 +01:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
})
|
|
|
|
}),
|
|
|
|
fiber: fiber));
|
|
|
|
expect(error.toString(), contains('must be a Sass value type'));
|
|
|
|
});
|
|
|
|
});
|
2018-09-19 21:28:47 +02:00
|
|
|
});
|
2018-01-21 01:45:17 +01:00
|
|
|
|
|
|
|
// Node Sass currently doesn't provide any representation of first-class
|
|
|
|
// functions, but they shouldn't crash or be corrupted.
|
|
|
|
test("a function is passed through as-is", () {
|
|
|
|
expect(
|
2018-11-16 00:16:24 +01:00
|
|
|
renderSync(RenderOptions(
|
2018-01-21 01:45:17 +01:00
|
|
|
data: "a {b: call(id(get-function('str-length')), 'foo')}",
|
|
|
|
functions: jsify({
|
|
|
|
r"id($value)": allowInterop(expectAsync1((value) => value))
|
|
|
|
}))),
|
|
|
|
equalsIgnoringWhitespace("a { b: 3; }"));
|
|
|
|
});
|
|
|
|
}
|