content: // 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:test/test.dart';
  
  import 'package:sass_embedded/src/embedded_sass.pb.dart';
  import 'package:sass_embedded/src/utils.dart';
  
  import 'embedded_process.dart';
  
  /// Returns a [InboundMessage] that compiles the given plain CSS
  /// string.
  InboundMessage compileString(String css,
      {int? id,
      bool? alertColor,
      bool? alertAscii,
      Syntax? syntax,
      OutputStyle? style,
      String? url,
      bool? sourceMap,
      bool? sourceMapIncludeSources,
      Iterable<InboundMessage_CompileRequest_Importer>? importers,
      InboundMessage_CompileRequest_Importer? importer,
      Iterable<String>? functions}) {
    var input = InboundMessage_CompileRequest_StringInput()..source = css;
    if (syntax != null) input.syntax = syntax;
    if (url != null) input.url = url;
    if (importer != null) input.importer = importer;
  
    var request = InboundMessage_CompileRequest()..string = input;
    if (id != null) request.id = id;
    if (importers != null) request.importers.addAll(importers);
    if (style != null) request.style = style;
    if (sourceMap != null) request.sourceMap = sourceMap;
    if (sourceMapIncludeSources != null) {
      request.sourceMapIncludeSources = sourceMapIncludeSources;
    }
    if (functions != null) request.globalFunctions.addAll(functions);
    if (alertColor != null) request.alertColor = alertColor;
    if (alertAscii != null) request.alertAscii = alertAscii;
  
    return InboundMessage()..compileRequest = request;
  }
  
  /// Asserts that [process] emits a [ProtocolError] parse error with the given
  /// [message] on its protobuf stream and prints a notice on stderr.
  Future<void> expectParseError(EmbeddedProcess process, Object message) async {
    await expectLater(process.outbound,
        emits(isProtocolError(errorId, ProtocolErrorType.PARSE, message)));
  
    var stderrPrefix = "Host caused parse error: ";
    await expectLater(
        process.stderr,
        message is String
            ? emitsInOrder("$stderrPrefix$message".split("\n"))
            : emits(startsWith(stderrPrefix)));
  }
  
  /// Asserts that [process] emits a [ProtocolError] params error with the given
  /// [message] on its protobuf stream and prints a notice on stderr.
  Future<void> expectParamsError(
      EmbeddedProcess process, int id, Object message) async {
    await expectLater(process.outbound,
        emits(isProtocolError(id, ProtocolErrorType.PARAMS, message)));
  
    var stderrPrefix = "Host caused params error"
        "${id == errorId ? '' : " with request $id"}: ";
    await expectLater(
        process.stderr,
        message is String
            ? emitsInOrder("$stderrPrefix$message".split("\n"))
            : emits(startsWith(stderrPrefix)));
  }
  
  /// Asserts that an [OutboundMessage] is a [ProtocolError] with the given [id],
  /// [type], and optionally [message].
  Matcher isProtocolError(int id, ProtocolErrorType type, [Object? message]) =>
      predicate((value) {
        expect(value, isA<OutboundMessage>());
        var outboundMessage = value as OutboundMessage;
        expect(outboundMessage.hasError(), isTrue,
            reason: "Expected $outboundMessage to be a ProtocolError");
        expect(outboundMessage.error.id, equals(id));
        expect(outboundMessage.error.type, equals(type));
        if (message != null) expect(outboundMessage.error.message, message);
        return true;
      });
  
  /// Asserts that [message] is an [OutboundMessage] with a
  /// `CanonicalizeRequest` and returns it.
  OutboundMessage_CanonicalizeRequest getCanonicalizeRequest(Object? value) {
    expect(value, isA<OutboundMessage>());
    var message = value as OutboundMessage;
    expect(message.hasCanonicalizeRequest(), isTrue,
        reason: "Expected $message to have a CanonicalizeRequest");
    return message.canonicalizeRequest;
  }
  
  /// Asserts that [message] is an [OutboundMessage] with a `ImportRequest` and
  /// returns it.
  OutboundMessage_ImportRequest getImportRequest(Object? value) {
    expect(value, isA<OutboundMessage>());
    var message = value as OutboundMessage;
    expect(message.hasImportRequest(), isTrue,
        reason: "Expected $message to have a ImportRequest");
    return message.importRequest;
  }
  
  /// Asserts that [message] is an [OutboundMessage] with a `FileImportRequest`
  /// and returns it.
  OutboundMessage_FileImportRequest getFileImportRequest(Object? value) {
    expect(value, isA<OutboundMessage>());
    var message = value as OutboundMessage;
    expect(message.hasFileImportRequest(), isTrue,
        reason: "Expected $message to have a FileImportRequest");
    return message.fileImportRequest;
  }
  
  /// Asserts that [message] is an [OutboundMessage] with a
  /// `FunctionCallRequest` and returns it.
  OutboundMessage_FunctionCallRequest getFunctionCallRequest(Object? value) {
    expect(value, isA<OutboundMessage>());
    var message = value as OutboundMessage;
    expect(message.hasFunctionCallRequest(), isTrue,
        reason: "Expected $message to have a FunctionCallRequest");
    return message.functionCallRequest;
  }
  
  /// Asserts that [message] is an [OutboundMessage] with a
  /// `CompileResponse.Failure` and returns it.
  OutboundMessage_CompileResponse_CompileFailure getCompileFailure(
      Object? value) {
    var response = getCompileResponse(value);
    expect(response.hasFailure(), isTrue,
        reason: "Expected $response to be a failure");
    return response.failure;
  }
  
  /// Asserts that [message] is an [OutboundMessage] with a
  /// `CompileResponse.Success` and returns it.
  OutboundMessage_CompileResponse_CompileSuccess getCompileSuccess(
      Object? value) {
    var response = getCompileResponse(value);
    expect(response.hasSuccess(), isTrue,
        reason: "Expected $response to be a success");
    return response.success;
  }
  
  /// Asserts that [message] is an [OutboundMessage] with a `CompileResponse` and
  /// returns it.
  OutboundMessage_CompileResponse getCompileResponse(Object? value) {
    expect(value, isA<OutboundMessage>());
    var message = value as OutboundMessage;
    expect(message.hasCompileResponse(), isTrue,
        reason: "Expected $message to have a CompileResponse");
    return message.compileResponse;
  }
  
  /// Asserts that [message] is an [OutboundMessage] with a `LogEvent` and
  /// returns it.
  OutboundMessage_LogEvent getLogEvent(Object? value) {
    expect(value, isA<OutboundMessage>());
    var message = value as OutboundMessage;
    expect(message.hasLogEvent(), isTrue,
        reason: "Expected $message to have a LogEvent");
    return message.logEvent;
  }
  
  /// Asserts that an [OutboundMessage] is a `CompileResponse` with CSS that
  /// matches [css], with a source map that matches [sourceMap] (if passed).
  ///
  /// If [css] is a [String], this automatically wraps it in
  /// [equalsIgnoringWhitespace].
  ///
  /// If [sourceMap] is a function, `response.success.sourceMap` is passed to it.
  /// Otherwise, it's treated as a matcher for `response.success.sourceMap`.
  Matcher isSuccess(Object css, {Object? sourceMap}) => predicate((value) {
        var success = getCompileSuccess(value);
        expect(success.css, css is String ? equalsIgnoringWhitespace(css) : css);
        if (sourceMap is void Function(String)) {
          sourceMap(success.sourceMap);
        } else if (sourceMap != null) {
          expect(success.sourceMap, sourceMap);
        }
        return true;
      });
  
  /// Returns a [SourceSpan_SourceLocation] with the given [offset], [line], and
  /// [column].
  SourceSpan_SourceLocation location(int offset, int line, int column) =>
      SourceSpan_SourceLocation()
        ..offset = offset
        ..line = line
        ..column = column;
  
  /// Returns a matcher that verifies whether the given value refers to the same
  /// path as [expected].
  Matcher equalsPath(String expected) => predicate<String>(
      (actual) => p.equals(actual, expected), "equals $expected");
  