content: // Copyright 2016 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 'dart:convert';
  
  import 'package:path/path.dart' as p;
  import 'package:test/test.dart';
  import 'package:test_descriptor/test_descriptor.dart' as d;
  import 'package:test_process/test_process.dart';
  
  import 'package:sass/sass.dart' as sass;
  import 'package:sass/src/io.dart';
  
  import '../../utils.dart';
  
  /// Defines test that are shared between the Dart and Node.js CLI test suites.
  void sharedTests(Future<TestProcess> runSass(Iterable<String> arguments)) {
    group("for a simple compilation", () {
      late Map<String, dynamic> map;
      setUp(() async {
        await d.file("test.scss", "a {b: 1 + 2}").create();
  
        await (await runSass(["test.scss", "out.css"])).shouldExit(0);
        map = _readJson("out.css.map");
      });
  
      test("refers to the source file", () {
        expect(map, containsPair("sources", ["test.scss"]));
      });
  
      test("refers to the target file", () {
        expect(map, containsPair("file", "out.css"));
      });
  
      test("contains mappings", () {
        var result = sass.compileStringToResult("a {b: 1 + 2}", sourceMap: true);
        expect(
          map,
          containsPair("mappings", result.sourceMap!.toJson()["mappings"]),
        );
      });
    });
  
    group("with multiple sources", () {
      setUp(() async {
        await d.file("test.scss", """
          @use 'dir/other';
          x {y: z}
        """).create();
        await d.dir("dir", [d.file("other.scss", "a {b: 1 + 2}")]).create();
      });
  
      test("refers to them using relative URLs by default", () async {
        await (await runSass(["test.scss", "out.css"])).shouldExit(0);
        expect(
          _readJson("out.css.map"),
          containsPair("sources", ["dir/other.scss", "test.scss"]),
        );
      });
  
      test(
        "refers to them using relative URLs with --source-map-urls=relative",
        () async {
          await (await runSass([
            "--source-map-urls=relative",
            "test.scss",
            "out.css",
          ]))
              .shouldExit(0);
          expect(
            _readJson("out.css.map"),
            containsPair("sources", ["dir/other.scss", "test.scss"]),
          );
        },
      );
  
      test(
        "refers to them using absolute URLs with --source-map-urls=absolute",
        () async {
          await (await runSass([
            "--source-map-urls=absolute",
            "test.scss",
            "out.css",
          ]))
              .shouldExit(0);
          expect(
            _readJson("out.css.map"),
            containsPair("sources", [
              p.toUri(canonicalize(d.path("dir/other.scss"))).toString(),
              p.toUri(canonicalize(d.path("test.scss"))).toString(),
            ]),
          );
        },
      );
  
      test("includes source contents with --embed-sources", () async {
        await (await runSass([
          "--embed-sources",
          "test.scss",
          "out.css",
        ]))
            .shouldExit(0);
        expect(
          _readJson("out.css.map"),
          containsPair("sourcesContent", [
            "a {b: 1 + 2}",
            readFile(d.path("test.scss")),
          ]),
        );
      });
    });
  
    group("doesn't normalize file case", () {
      setUp(() => d.file("TeSt.scss", "a {b: c}").create());
  
      test("when loaded with the same case", () async {
        await (await runSass(["TeSt.scss", "out.css"])).shouldExit(0);
        expect(_readJson("out.css.map"), containsPair("sources", ["TeSt.scss"]));
      });
  
      test("when imported with the same case", () async {
        await d.file("importer.scss", "@use 'TeSt.scss'").create();
        await (await runSass(["importer.scss", "out.css"])).shouldExit(0);
        expect(_readJson("out.css.map"), containsPair("sources", ["TeSt.scss"]));
      });
  
      // The following tests rely on Windows' case-insensitive filesystem.
  
      test("when loaded with a different case", () async {
        await (await runSass(["test.scss", "out.css"])).shouldExit(0);
        expect(_readJson("out.css.map"), containsPair("sources", ["TeSt.scss"]));
      }, testOn: "windows");
  
      test("when imported with a different case", () async {
        await d.file("importer.scss", "@use 'test.scss'").create();
        await (await runSass(["importer.scss", "out.css"])).shouldExit(0);
        expect(_readJson("out.css.map"), containsPair("sources", ["TeSt.scss"]));
      }, testOn: "windows");
    });
  
    test("includes a source map comment", () async {
      await d.file("test.scss", "a {b: c}").create();
      await (await runSass(["test.scss", "out.css"])).shouldExit(0);
      await d
          .file("out.css", endsWith("\n\n/*# sourceMappingURL=out.css.map */\n"))
          .validate();
    });
  
    group("in another directory", () {
      setUp(() async {
        await d.dir("in", [d.file("test.scss", "x {y: z}")]).create();
        await (await runSass(["in/test.scss", "out/test.css"])).shouldExit(0);
      });
  
      test("refers to a source", () {
        expect(
          _readJson("out/test.css.map"),
          containsPair("sources", ["../in/test.scss"]),
        );
      });
  
      test("includes a source map comment", () async {
        await d
            .file(
              "out/test.css",
              endsWith("\n\n/*# sourceMappingURL=test.css.map */\n"),
            )
            .validate();
      });
    });
  
    test("with --stdin uses a data: URL", () async {
      var sass = await runSass(["--stdin", "out.css"]);
      sass.stdin.writeln("a {b: c}");
      sass.stdin.close();
      await sass.shouldExit(0);
  
      expect(
        _readJson("out.css.map"),
        containsPair("sources", [
          Uri.dataFromString("a {b: c}\n", encoding: utf8).toString(),
        ]),
      );
    });
  
    group("with --no-source-map,", () {
      setUp(() async {
        await d.file("test.scss", "a {b: c}").create();
      });
  
      test("no source map is generated", () async {
        await (await runSass([
          "--no-source-map",
          "test.scss",
          "out.css",
        ]))
            .shouldExit(0);
  
        await d.file("out.css", isNot(contains("/*#"))).validate();
        await d.nothing("out.css.map").validate();
      });
  
      test("--source-map-urls is disallowed", () async {
        var sass = await runSass([
          "--no-source-map",
          "--source-map-urls=absolute",
          "test.scss",
          "out.css",
        ]);
        expect(
          sass.stdout,
          emits("--source-map-urls isn't allowed with --no-source-map."),
        );
        expect(
          sass.stdout,
          emitsThrough(contains("Print this usage information.")),
        );
        await sass.shouldExit(64);
      });
  
      test("--embed-sources is disallowed", () async {
        var sass = await runSass([
          "--no-source-map",
          "--embed-sources",
          "test.scss",
          "out.css",
        ]);
        expect(
          sass.stdout,
          emits("--embed-sources isn't allowed with --no-source-map."),
        );
        expect(
          sass.stdout,
          emitsThrough(contains("Print this usage information.")),
        );
        await sass.shouldExit(64);
      });
  
      test("--embed-source-map is disallowed", () async {
        var sass = await runSass([
          "--no-source-map",
          "--embed-source-map",
          "test.scss",
          "out.css",
        ]);
        expect(
          sass.stdout,
          emits("--embed-source-map isn't allowed with --no-source-map."),
        );
        expect(
          sass.stdout,
          emitsThrough(contains("Print this usage information.")),
        );
        await sass.shouldExit(64);
      });
    });
  
    group("when emitting to stdout", () {
      test("--source-map isn't allowed", () async {
        await d.file("test.scss", "a {b: c}").create();
        var sass = await runSass(["--source-map", "test.scss"]);
        expect(
          sass.stdout,
          emits(
            "When printing to stdout, --source-map requires "
            "--embed-source-map.",
          ),
        );
        expect(
          sass.stdout,
          emitsThrough(contains("Print this usage information.")),
        );
        await sass.shouldExit(64);
      });
  
      test("--source-map-urls is disallowed", () async {
        await d.file("test.scss", "a {b: c}").create();
        var sass = await runSass(["--source-map-urls=absolute", "test.scss"]);
        expect(
          sass.stdout,
          emits(
            "When printing to stdout, --source-map-urls requires "
            "--embed-source-map.",
          ),
        );
        expect(
          sass.stdout,
          emitsThrough(contains("Print this usage information.")),
        );
        await sass.shouldExit(64);
      });
  
      test("--embed-sources is disallowed", () async {
        await d.file("test.scss", "a {b: c}").create();
        var sass = await runSass(["--embed-sources", "test.scss"]);
        expect(
          sass.stdout,
          emits(
            "When printing to stdout, --embed-sources requires "
            "--embed-source-map.",
          ),
        );
        expect(
          sass.stdout,
          emitsThrough(contains("Print this usage information.")),
        );
        await sass.shouldExit(64);
      });
  
      test(
          "--source-map-urls=relative is disallowed even with "
          "--embed-source-map", () async {
        await d.file("test.scss", "a {b: c}").create();
        var sass = await runSass([
          "--source-map-urls=relative",
          "--embed-source-map",
          "test.scss",
        ]);
        expect(
          sass.stdout,
          emits(
            "--source-map-urls=relative isn't allowed when printing to "
            "stdout.",
          ),
        );
        expect(
          sass.stdout,
          emitsThrough(contains("Print this usage information.")),
        );
        await sass.shouldExit(64);
      });
  
      test("everything is allowed with --embed-source-map", () async {
        await d.file("test.scss", "a {b: c}").create();
        var sass = await runSass([
          "--source-map",
          "--source-map-urls=absolute",
          "--embed-sources",
          "--embed-source-map",
          "test.scss",
        ]);
        var css = (await sass.stdout.rest.toList()).join("\n");
        await sass.shouldExit(0);
  
        var map = embeddedSourceMap(css);
        expect(map, isNotEmpty);
        expect(map, isNot(contains("file")));
      });
    });
  
    group("with --embed-source-map", () {
      setUp(() async {
        await d.file("test.scss", "a {b: 1 + 2}").create();
      });
  
      Map<String, dynamic>? map;
      group("with the target in the same directory", () {
        setUp(() async {
          await (await runSass([
            "--embed-source-map",
            "test.scss",
            "out.css",
          ]))
              .shouldExit(0);
          var css = readFile(d.path("out.css"));
          map = embeddedSourceMap(css);
        });
  
        test("contains mappings in the generated CSS", () {
          var result = sass.compileStringToResult(
            "a {b: 1 + 2}",
            sourceMap: true,
          );
          expect(
            map,
            containsPair("mappings", result.sourceMap!.toJson()["mappings"]),
          );
        });
  
        test("refers to the source file", () {
          expect(map, containsPair("sources", ["test.scss"]));
        });
  
        test("refers to the target file", () {
          expect(map, containsPair("file", "out.css"));
        });
  
        test("doesn't generate a source map file", () async {
          await d.nothing("out.css.map").validate();
        });
  
        // Regression test for #457.
        test("--embed-sources works with non-ASCII characters", () async {
          await d.file("test.scss", "a {b: '▼'}").create();
          await (await runSass([
            "--embed-source-map",
            "--embed-sources",
            "test.scss",
            "out.css",
          ]))
              .shouldExit(0);
          var css = readFile(d.path("out.css"));
          map = embeddedSourceMap(css);
  
          expect(map, containsPair("sources", ["test.scss"]));
          expect(map, containsPair("sourcesContent", ["a {b: '▼'}"]));
        });
      });
  
      group("with the target in a different directory", () {
        setUp(() async {
          ensureDir(d.path("dir"));
          await (await runSass([
            "--embed-source-map",
            "test.scss",
            "dir/out.css",
          ]))
              .shouldExit(0);
          var css = readFile(d.path("dir/out.css"));
          map = embeddedSourceMap(css);
        });
  
        test("refers to the source file", () {
          expect(map, containsPair("sources", ["../test.scss"]));
        });
  
        test("refers to the target file", () {
          expect(map, containsPair("file", "out.css"));
        });
      });
  
      test("with stdout as the target", () async {
        var sass = await runSass(["--embed-source-map", "test.scss"]);
        expect(
          sass.stdout,
          emitsInOrder([
            "a {",
            "  b: 3;",
            "}",
            "",
            startsWith("/*# sourceMappingURL=data:"),
          ]),
        );
        await sass.shouldExit(0);
      });
    });
  }
  
  /// Reads the file at [path] within [d.sandbox] and JSON-decodes it.
  Map<String, dynamic> _readJson(String path) =>
      jsonDecode(readFile(d.path(path))) as Map<String, dynamic>;
  