dart-sass/test/embedded/file_importer_test.dart
2023-06-06 14:23:17 -07:00

288 lines
9.4 KiB
Dart

// Copyright 2021 Google LLC. 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('vm')
import 'package:path/path.dart' as p;
import 'package:test/test.dart';
import 'package:test_descriptor/test_descriptor.dart' as d;
import 'package:sass/src/embedded/embedded_sass.pb.dart';
import 'package:sass/src/embedded/utils.dart';
import 'embedded_process.dart';
import 'utils.dart';
void main() {
late EmbeddedProcess process;
setUp(() async {
process = await EmbeddedProcess.start();
});
group("emits a protocol error", () {
late OutboundMessage_FileImportRequest request;
setUp(() async {
process.send(compileString("@import 'other'", importers: [
InboundMessage_CompileRequest_Importer()..fileImporterId = 1
]));
request = await getFileImportRequest(process);
});
test("for a response without a corresponding request ID", () async {
process.send(InboundMessage()
..fileImportResponse =
(InboundMessage_FileImportResponse()..id = request.id + 1));
await expectParamsError(
process,
errorId,
"Response ID ${request.id + 1} doesn't match any outstanding "
"requests in compilation $defaultCompilationId.");
await process.shouldExit(76);
});
test("for a response that doesn't match the request type", () async {
process.send(InboundMessage()
..canonicalizeResponse =
(InboundMessage_CanonicalizeResponse()..id = request.id));
await expectParamsError(
process,
errorId,
"Request ID ${request.id} doesn't match response type "
"InboundMessage_CanonicalizeResponse in compilation "
"$defaultCompilationId.");
await process.shouldExit(76);
});
});
group("emits a compile failure", () {
late OutboundMessage_FileImportRequest request;
setUp(() async {
process.send(compileString("@import 'other'", importers: [
InboundMessage_CompileRequest_Importer()..fileImporterId = 1
]));
request = await getFileImportRequest(process);
});
group("for a FileImportResponse with a URL", () {
test("that's empty", () async {
process.send(InboundMessage()
..fileImportResponse = (InboundMessage_FileImportResponse()
..id = request.id
..fileUrl = ""));
await _expectImportError(
process, 'The file importer must return an absolute URL, was ""');
await process.close();
});
test("that's relative", () async {
process.send(InboundMessage()
..fileImportResponse = (InboundMessage_FileImportResponse()
..id = request.id
..fileUrl = "foo"));
await _expectImportError(process,
'The file importer must return an absolute URL, was "foo"');
await process.close();
});
test("that's not file:", () async {
process.send(InboundMessage()
..fileImportResponse = (InboundMessage_FileImportResponse()
..id = request.id
..fileUrl = "other:foo"));
await _expectImportError(process,
'The file importer must return a file: URL, was "other:foo"');
await process.close();
});
});
});
group("includes in FileImportRequest", () {
var compilationId = 1234;
var importerId = 5679;
late OutboundMessage_FileImportRequest request;
setUp(() async {
process.send(
compileString("@import 'other'", id: compilationId, importers: [
InboundMessage_CompileRequest_Importer()..fileImporterId = importerId
]));
request = await getFileImportRequest(process);
});
test("a known importerId", () async {
expect(request.importerId, equals(importerId));
await process.kill();
});
test("the imported URL", () async {
expect(request.url, equals("other"));
await process.kill();
});
test("whether the import came from an @import", () async {
expect(request.fromImport, isTrue);
await process.kill();
});
});
test("errors cause compilation to fail", () async {
process.send(compileString("@import 'other'", importers: [
InboundMessage_CompileRequest_Importer()..fileImporterId = 1
]));
var request = await getFileImportRequest(process);
process.send(InboundMessage()
..fileImportResponse = (InboundMessage_FileImportResponse()
..id = request.id
..error = "oh no"));
var failure = await getCompileFailure(process);
expect(failure.message, equals('oh no'));
expect(failure.span.text, equals("'other'"));
expect(failure.stackTrace, equals('- 1:9 root stylesheet\n'));
await process.close();
});
test("null results count as not found", () async {
process.send(compileString("@import 'other'", importers: [
InboundMessage_CompileRequest_Importer()..fileImporterId = 1
]));
var request = await getFileImportRequest(process);
process.send(InboundMessage()
..fileImportResponse =
(InboundMessage_FileImportResponse()..id = request.id));
var failure = await getCompileFailure(process);
expect(failure.message, equals("Can't find stylesheet to import."));
expect(failure.span.text, equals("'other'"));
await process.close();
});
group("attempts importers in order", () {
test("with multiple file importers", () async {
process.send(compileString("@import 'other'", importers: [
for (var i = 0; i < 10; i++)
InboundMessage_CompileRequest_Importer()..fileImporterId = i
]));
for (var i = 0; i < 10; i++) {
var request = await getFileImportRequest(process);
expect(request.importerId, equals(i));
process.send(InboundMessage()
..fileImportResponse =
(InboundMessage_FileImportResponse()..id = request.id));
}
await process.kill();
});
test("with a mixture of file and normal importers", () async {
process.send(compileString("@import 'other'", importers: [
for (var i = 0; i < 10; i++)
if (i % 2 == 0)
InboundMessage_CompileRequest_Importer()..fileImporterId = i
else
InboundMessage_CompileRequest_Importer()..importerId = i
]));
for (var i = 0; i < 10; i++) {
if (i % 2 == 0) {
var request = await getFileImportRequest(process);
expect(request.importerId, equals(i));
process.send(InboundMessage()
..fileImportResponse =
(InboundMessage_FileImportResponse()..id = request.id));
} else {
var request = await getCanonicalizeRequest(process);
expect(request.importerId, equals(i));
process.send(InboundMessage()
..canonicalizeResponse =
(InboundMessage_CanonicalizeResponse()..id = request.id));
}
}
await process.kill();
});
});
test("tries resolved URL as a relative path first", () async {
await d.file("upstream.scss", "a {b: c}").create();
await d.file("midstream.scss", "@import 'upstream';").create();
process.send(compileString("@import 'midstream'", importers: [
for (var i = 0; i < 10; i++)
InboundMessage_CompileRequest_Importer()..fileImporterId = i
]));
for (var i = 0; i < 5; i++) {
var request = await getFileImportRequest(process);
expect(request.url, equals("midstream"));
expect(request.importerId, equals(i));
process.send(InboundMessage()
..fileImportResponse =
(InboundMessage_FileImportResponse()..id = request.id));
}
var request = await getFileImportRequest(process);
expect(request.importerId, equals(5));
process.send(InboundMessage()
..fileImportResponse = (InboundMessage_FileImportResponse()
..id = request.id
..fileUrl = p.toUri(d.path("midstream")).toString()));
await expectSuccess(process, "a { b: c; }");
await process.close();
});
group("handles an importer for a string compile request", () {
setUp(() async {
await d.file("other.scss", "a {b: c}").create();
});
test("without a base URL", () async {
process.send(compileString("@import 'other'",
importer: InboundMessage_CompileRequest_Importer()
..fileImporterId = 1));
var request = await getFileImportRequest(process);
expect(request.url, equals("other"));
process.send(InboundMessage()
..fileImportResponse = (InboundMessage_FileImportResponse()
..id = request.id
..fileUrl = p.toUri(d.path("other")).toString()));
await expectSuccess(process, "a { b: c; }");
await process.close();
});
test("with a base URL", () async {
process.send(compileString("@import 'other'",
url: p.toUri(d.path("input")).toString(),
importer: InboundMessage_CompileRequest_Importer()
..fileImporterId = 1));
await expectSuccess(process, "a { b: c; }");
await process.close();
});
});
}
/// Asserts that [process] emits a [CompileFailure] result with the given
/// [message] on its protobuf stream and causes the compilation to fail.
Future<void> _expectImportError(EmbeddedProcess process, Object message) async {
var failure = await getCompileFailure(process);
expect(failure.message, equals(message));
expect(failure.span.text, equals("'other'"));
}