// 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:path/path.dart' as p; import 'package:source_maps/source_maps.dart' as source_maps; import 'package:test/test.dart'; import 'package:test_descriptor/test_descriptor.dart' as d; import 'package:sass_embedded/src/embedded_sass.pb.dart'; import 'embedded_process.dart'; import 'utils.dart'; void main() { ensureExecutableUpToDate(); EmbeddedProcess process; setUp(() async { process = await EmbeddedProcess.start(); }); group("gracefully handles a protocol error", () { test("caused by an empty message", () async { process.inbound.add(InboundMessage()); await expectParseError(process, "InboundMessage.message is not set."); await process.kill(); }); test("caused by an invalid message", () async { process.stdin.add([1, 0, 0, 0, 0]); await expectParseError( process, "Protocol message contained an invalid tag (zero)."); await process.kill(); }); test("without shutting down the compiler", () async { process.inbound.add(InboundMessage()); await expectParseError(process, "InboundMessage.message is not set."); process.inbound.add(compileString("a {b: c}")); await expectLater(process.outbound, emits(isSuccess("a { b: c; }"))); await process.kill(); }); }); group("compiles CSS from", () { test("an SCSS string by default", () async { process.inbound.add(compileString("a {b: 1px + 2px}")); await expectLater(process.outbound, emits(isSuccess("a { b: 3px; }"))); await process.kill(); }); test("an SCSS string explicitly", () async { process.inbound.add(compileString("a {b: 1px + 2px}", syntax: InboundMessage_Syntax.SCSS)); await expectLater(process.outbound, emits(isSuccess("a { b: 3px; }"))); await process.kill(); }); test("an indented syntax string", () async { process.inbound.add(compileString("a\n b: 1px + 2px", syntax: InboundMessage_Syntax.INDENTED)); await expectLater(process.outbound, emits(isSuccess("a { b: 3px; }"))); await process.kill(); }); test("a plain CSS string", () async { process.inbound .add(compileString("a {b: c}", syntax: InboundMessage_Syntax.CSS)); await expectLater(process.outbound, emits(isSuccess("a { b: c; }"))); await process.kill(); }); test("an absolute path", () async { await d.file("test.scss", "a {b: 1px + 2px}").create(); process.inbound.add(InboundMessage() ..compileRequest = (InboundMessage_CompileRequest() ..path = p.absolute(d.path("test.scss")))); await expectLater(process.outbound, emits(isSuccess("a { b: 3px; }"))); await process.kill(); }); test("a relative path", () async { await d.file("test.scss", "a {b: 1px + 2px}").create(); process.inbound.add(InboundMessage() ..compileRequest = (InboundMessage_CompileRequest() ..path = p.relative(d.path("test.scss")))); await expectLater(process.outbound, emits(isSuccess("a { b: 3px; }"))); await process.kill(); }); }); group("compiles CSS in", () { test("expanded mode", () async { process.inbound.add(compileString("a {b: 1px + 2px}", style: InboundMessage_CompileRequest_OutputStyle.EXPANDED)); await expectLater( process.outbound, emits(isSuccess(equals("a {\n b: 3px;\n}")))); await process.kill(); }); test("compressed mode", () async { process.inbound.add(compileString("a {b: 1px + 2px}", style: InboundMessage_CompileRequest_OutputStyle.COMPRESSED)); await expectLater(process.outbound, emits(isSuccess(equals("a{b:3px}")))); await process.kill(); }); test("expanded mode when nested mode is passed", () async { process.inbound.add(compileString("a {b: 1px + 2px}", style: InboundMessage_CompileRequest_OutputStyle.NESTED)); await expectLater( process.outbound, emits(isSuccess(equals("a {\n b: 3px;\n}")))); await process.kill(); }); test("expanded mode when compact mode is passed", () async { process.inbound.add(compileString("a {b: 1px + 2px}", style: InboundMessage_CompileRequest_OutputStyle.COMPACT)); await expectLater( process.outbound, emits(isSuccess(equals("a {\n b: 3px;\n}")))); await process.kill(); }); }); test("doesn't include a source map by default", () async { process.inbound.add(compileString("a {b: 1px + 2px}")); await expectLater(process.outbound, emits(isSuccess("a { b: 3px; }", sourceMap: isEmpty))); await process.kill(); }); test("doesn't include a source map with source_map: false", () async { process.inbound.add(compileString("a {b: 1px + 2px}", sourceMap: false)); await expectLater(process.outbound, emits(isSuccess("a { b: 3px; }", sourceMap: isEmpty))); await process.kill(); }); test("includes a source map if source_map is true", () async { process.inbound.add(compileString("a {b: 1px + 2px}", sourceMap: true)); await expectLater( process.outbound, emits(isSuccess("a { b: 3px; }", sourceMap: predicate((map) { var mapping = source_maps.parse(map); var span = mapping.spanFor(2, 5); expect(span.start.line, equals(0)); expect(span.start.column, equals(3)); expect(span.end, equals(span.start)); return true; })))); await process.kill(); }); group("gracefully handles an error", () { test("from invalid syntax", () async { process.inbound.add(compileString("a {b: }")); var failure = getCompileFailure(await process.outbound.next); expect(failure.message, equals("Expected expression.")); expect(failure.span.text, isEmpty); expect(failure.span.start, equals(location(6, 0, 6))); expect(failure.span.end, equals(location(6, 0, 6))); expect(failure.span.url, isEmpty); expect(failure.span.context, equals("a {b: }")); expect(failure.stackTrace, equals("- 1:7 root stylesheet\n")); await process.kill(); }); test("from the runtime", () async { process.inbound.add(compileString("a {b: 1px + 1em}")); var failure = getCompileFailure(await process.outbound.next); expect(failure.message, equals("Incompatible units em and px.")); expect(failure.span.text, "1px + 1em"); expect(failure.span.start, equals(location(6, 0, 6))); expect(failure.span.end, equals(location(15, 0, 15))); expect(failure.span.url, isEmpty); expect(failure.span.context, equals("a {b: 1px + 1em}")); expect(failure.stackTrace, equals("- 1:7 root stylesheet\n")); await process.kill(); }); test("from a missing file", () async { process.inbound.add(InboundMessage() ..compileRequest = (InboundMessage_CompileRequest()..path = d.path("test.scss"))); var failure = getCompileFailure(await process.outbound.next); expect(failure.message, startsWith("Cannot open file: ")); expect(failure.message.split(":").last.trim(), equalsPath(d.path('test.scss'))); expect(failure.span, equals(SourceSpan())); expect(failure.stackTrace, isEmpty); await process.kill(); }); test("with a multi-line source span", () async { process.inbound.add(compileString(""" a { b: 1px + 1em; } """)); var failure = getCompileFailure(await process.outbound.next); expect(failure.span.text, "1px +\n 1em"); expect(failure.span.start, equals(location(9, 1, 5))); expect(failure.span.end, equals(location(23, 2, 8))); expect(failure.span.url, isEmpty); expect(failure.span.context, equals(" b: 1px +\n 1em;\n")); expect(failure.stackTrace, equals("- 2:6 root stylesheet\n")); await process.kill(); }); test("with multiple stack trace entries", () async { process.inbound.add(compileString(""" @function fail() { @return 1px + 1em; } a { b: fail(); } """)); var failure = getCompileFailure(await process.outbound.next); expect( failure.stackTrace, equals("- 2:11 fail()\n" "- 6:6 root stylesheet\n")); await process.kill(); }); group("and includes the URL from", () { test("a string input", () async { process.inbound .add(compileString("a {b: 1px + 1em}", url: "foo://bar/baz")); var failure = getCompileFailure(await process.outbound.next); expect(failure.span.url, equals("foo://bar/baz")); expect( failure.stackTrace, equals("foo://bar/baz 1:7 root stylesheet\n")); await process.kill(); }); test("a path input", () async { await d.file("test.scss", "a {b: 1px + 1em}").create(); var path = d.path("test.scss"); process.inbound.add(InboundMessage() ..compileRequest = (InboundMessage_CompileRequest()..path = path)); var failure = getCompileFailure(await process.outbound.next); expect(p.fromUri(failure.span.url), equalsPath(path)); expect(failure.stackTrace, endsWith(" 1:7 root stylesheet\n")); expect(failure.stackTrace.split(" ").first, equalsPath(path)); await process.kill(); }); }); test("caused by using Sass features in CSS", () async { process.inbound.add( compileString("a {b: 1px + 2px}", syntax: InboundMessage_Syntax.CSS)); var failure = getCompileFailure(await process.outbound.next); expect(failure.message, equals("Operators aren't allowed in plain CSS.")); expect(failure.span.text, "+"); expect(failure.span.start, equals(location(10, 0, 10))); expect(failure.span.end, equals(location(11, 0, 11))); expect(failure.span.url, isEmpty); expect(failure.span.context, equals("a {b: 1px + 2px}")); expect(failure.stackTrace, equals("- 1:11 root stylesheet\n")); await process.kill(); }); }); }