// 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.

// Almost all CSS output tests should go in sass-spec rather than here. This
// just covers tests that explicitly validate out that's considered too
// implementation-specific to verify in sass-spec.

@TestOn('vm')
library;

import 'package:test/test.dart';

import 'package:sass/sass.dart';

void main() {
  group("emits private-use area characters as escapes in expanded mode", () {
    testCharacter(String escape) {
      test(escape, () {
        expect(
          compileString("a {b: $escape}"),
          equalsIgnoringWhitespace("a { b: $escape; }"),
        );
      });
    }

    group("in the basic multilingual plane", () {
      testCharacter(r"\e000");
      testCharacter(r"\f000");
      testCharacter(r"\f8ff");
    });

    group("in the supplementary planes", () {
      testCharacter(r"\f0000");
      testCharacter(r"\fabcd");
      testCharacter(r"\ffffd");
      testCharacter(r"\100000");
      testCharacter(r"\10abcd");
      testCharacter(r"\10fffd");

      // Although these aren't technically in private-use area, they're in
      // private-use planes and they have no visual representation to we
      // escape them as well.
      group("that aren't technically in PUAs", () {
        testCharacter(r"\ffffe");
        testCharacter(r"\fffff");
        testCharacter(r"\10fffe");
        testCharacter(r"\10ffff");
      });
    });

    group("adds a space", () {
      test("if followed by a hex character", () {
        expect(
          compileString(r"a {b: '\e000 a'}"),
          equalsIgnoringWhitespace(r'a { b: "\e000 a"; }'),
        );
      });

      test("if followed by a space", () {
        expect(
          compileString(r"a {b: '\e000  '}"),
          equalsIgnoringWhitespace(r'a { b: "\e000  "; }'),
        );
      });
    });
  });

  // Regression test for sass/dart-sass#623. This needs to be tested here
  // because sass-spec normalizes CR LF newlines.
  group("normalizes newlines in a loud comment", () {
    test("in SCSS", () {
      expect(compileString("/* foo\r\n * bar */"), equals("/* foo\n * bar */"));
    });

    test("in Sass", () {
      expect(
        compileString("/*\r\n  foo\r\n  bar", syntax: Syntax.sass),
        equals("/* foo\n * bar */"),
      );
    });
  });

  // Regression test for sass/dart-sass#688. This needs to be tested here
  // because it varies between Dart and Node.
  group("removes exponential notation", () {
    group("for integers", () {
      test(">= 1e21", () {
        expect(
          compileString("a {b: 1e21}"),
          equalsIgnoringWhitespace("a { b: 1${'0' * 21}; }"),
        );
      });

      // At time of writing, numbers that are 20 digits or fewer are not printed
      // in exponential notation by either Dart or Node, and we rely on that to
      // determine when to get rid of the exponent. This test ensures that if that
      // ever changes, we know about it.
      test("< 1e21", () {
        expect(
          compileString("a {b: 1e20}"),
          equalsIgnoringWhitespace("a { b: 1${'0' * 20}; }"),
        );
      });
    });

    group("for floating-point numbers", () {
      test("Infinity", () {
        expect(
          compileString("a {b: 1e999}"),
          equalsIgnoringWhitespace("a { b: calc(infinity); }"),
        );
      });

      test(">= 1e21", () {
        expect(
          compileString("a {b: 1.01e21}"),
          equalsIgnoringWhitespace("a { b: 101${'0' * 19}; }"),
        );
      });

      // At time of writing, numbers that are 20 digits or fewer are not printed
      // in exponential notation by either Dart or Node, and we rely on that to
      // determine when to get rid of the exponent. This test ensures that if that
      // ever changes, we know about it.
      test("< 1e21", () {
        expect(
          compileString("a {b: 1.01e20}"),
          equalsIgnoringWhitespace("a { b: 101${'0' * 18}; }"),
        );
      });
    });
  });

  // Tests for sass/dart-sass#2070.
  //
  // These aren't covered by sass-spec because the inspect format for
  // non-literal values isn't covered by the spec.
  group("uses a nice format to inspect numbers with complex units", () {
    group("finite", () {
      test("top-level", () {
        expect(
          compileString("""
            @use 'sass:meta';
            a {b: meta.inspect(1px * 1em)};
          """),
          equalsIgnoringWhitespace('a { b: calc(1px * 1em); }'),
        );
      });

      test("in calc", () {
        expect(
          compileString("""
            @use 'sass:meta';
            a {b: meta.inspect(calc(1px * 1em))};
          """),
          equalsIgnoringWhitespace('a { b: calc(1px * 1em); }'),
        );
      });

      test("nested in calc", () {
        expect(
          compileString("""
            @use 'sass:meta';
            a {b: meta.inspect(calc(c / (1px * 1em)))};
          """),
          equalsIgnoringWhitespace('a { b: calc(c / (1px * 1em)); }'),
        );
      });

      test("numerator and denominator", () {
        expect(
          compileString("""
            @use 'sass:math';
            @use 'sass:meta';
            a {b: meta.inspect(1px * math.div(math.div(1em, 1s), 1x))};
          """),
          equalsIgnoringWhitespace('a { b: calc(1px * 1em / 1s / 1x); }'),
        );
      });

      test("denominator only", () {
        expect(
          compileString("""
            @use 'sass:math';
            @use 'sass:meta';
            a {b: meta.inspect(math.div(math.div(1, 1s), 1x))};
          """),
          equalsIgnoringWhitespace('a { b: calc(1 / 1s / 1x); }'),
        );
      });
    });
  });

  // Tests for sass/dart-sass#417.
  //
  // Note there's no need for "in Sass" cases as it's not possible to have
  // trailing loud comments in the Sass syntax.
  group("preserve trailing loud comments in SCSS", () {
    test("after open block", () {
      expect(
        compileString("""
          selector { /* please don't move me */
            name: value;
          }
        """),
        equals("""
selector { /* please don't move me */
  name: value;
}"""),
      );
    });

    test("after open block (multi-line selector)", () {
      expect(
        compileString("""
          selector1,
          selector2 { /* please don't move me */
            name: value;
          }
        """),
        equals("""
selector1,
selector2 { /* please don't move me */
  name: value;
}"""),
      );
    });

    test("after close block", () {
      expect(
        compileString("""
          selector {
            name: value;
          } /* please don't move me */
        """),
        equals("""
selector {
  name: value;
} /* please don't move me */"""),
      );
    });

    test("only content in block", () {
      expect(
        compileString("""
          selector {
            /* please don't move me */
          }
        """),
        equals("""
selector {
  /* please don't move me */
}"""),
      );
    });

    test("only content in block (no newlines)", () {
      expect(
        compileString("selector { /* please don't move me */ }"),
        equals("selector { /* please don't move me */ }"),
      );
    });

    test("double trailing empty block", () {
      expect(
        compileString(
            "selector { /* please don't move me */ /* please don't move me */ "
            "}"),
        equals("""
selector { /* please don't move me */ /* please don't move me */
}"""),
      );
    });

    test("double trailing style rule", () {
      expect(
        compileString("""
          selector {
            margin: 1px; /* please don't move me */ /* please don't move me */
          }
        """),
        equals("""
selector {
  margin: 1px; /* please don't move me */ /* please don't move me */
}"""),
      );
    });

    test("after property in block", () {
      expect(
        compileString("""
          selector {
            name1: value1; /* please don't move me 1 */
            name2: value2; /* please don't move me 2 */
            name3: value3; /* please don't move me 3 */
          }
        """),
        equals("""
selector {
  name1: value1; /* please don't move me 1 */
  name2: value2; /* please don't move me 2 */
  name3: value3; /* please don't move me 3 */
}"""),
      );
    });

    test("after rule in block", () {
      expect(
        compileString("""
          selector {
            @rule1; /* please don't move me 1 */
            @rule2; /* please don't move me 2 */
            @rule3; /* please don't move me 3 */
          }
        """),
        equals("""
selector {
  @rule1; /* please don't move me 1 */
  @rule2; /* please don't move me 2 */
  @rule3; /* please don't move me 3 */
}"""),
      );
    });

    test("after top-level statement", () {
      expect(
        compileString("@rule; /* please don't move me */"),
        equals("@rule; /* please don't move me */"),
      );
    });

    // The trailing comment detection logic looks for left braces to detect
    // whether a comment is on the same line as its parent node.  This test
    // checks to make sure it isn't confused by syntax that uses braces for
    // things other than starting child blocks.
    test("selector contains left brace", () {
      expect(
        compileString("""
          @rule1;
          @rule2;
          selector[href*="{"]
          { /* please don't move me */ }

          @rule3;
        """),
        equals("""
@rule1;
@rule2;
selector[href*="{"] { /* please don't move me */ }

@rule3;"""),
      );
    });

    group("loud comments in mixin", () {
      test("empty with spacing", () {
        expect(
          compileString("""
            @mixin loudComment {
              /* ... */
            }

            selector {
              @include loudComment;
            }
          """),
          equals("""
selector {
  /* ... */
}"""),
        );
      });

      test("empty no spacing", () {
        expect(
          compileString("""
            @mixin loudComment{/* ... */}
            selector {@include loudComment;}
          """),
          equals("""
selector {
  /* ... */
}"""),
        );
      });

      test("with style rule", () {
        expect(
          compileString("""
            @mixin loudComment {
              margin: 1px; /* mixin */
            } /* mixin-out */

            selector {
              @include loudComment; /* selector */
            }
          """),
          """
/* mixin-out */
selector {
  margin: 1px; /* mixin */
  /* selector */
}""",
        );
      });
    });

    group("loud comments in nested blocks", () {
      test("with styles", () {
        expect(
          compileString("""
            foo { /* foo */
              padding: 1px; /* foo padding */
              bar { /* bar */
                padding: 2px; /* bar padding */
                baz { /* baz */
                  padding: 3px; /* baz padding */
                  margin: 3px; /* baz margin */
                } /* baz end */
                biz { /* biz */
                  padding: 3px; /* biz padding */
                  margin: 3px; /* biz margin */
                } /* biz end */
                margin: 2px; /* bar margin */
              } /* bar end */
              margin: 1px; /* foo margin */
            } /* foo end */
          """),
          equals("""
foo { /* foo */
  padding: 1px; /* foo padding */
}
foo bar { /* bar */
  padding: 2px; /* bar padding */
}
foo bar baz { /* baz */
  padding: 3px; /* baz padding */
  margin: 3px; /* baz margin */
}
foo bar {
  /* baz end */
}
foo bar biz { /* biz */
  padding: 3px; /* biz padding */
  margin: 3px; /* biz margin */
}
foo bar {
  /* biz end */
  margin: 2px; /* bar margin */
}
foo {
  /* bar end */
  margin: 1px; /* foo margin */
} /* foo end */"""),
        );
      });

      test("empty", () {
        expect(
          compileString("""
            foo { /* foo */
              bar { /* bar */
                baz { /* baz */
                } /* baz end */
                biz { /* biz */
                } /* biz end */
              } /* bar end */
            } /* foo end */
          """),
          equals("""
foo { /* foo */ }
foo bar { /* bar */ }
foo bar baz { /* baz */ }
foo bar {
  /* baz end */
}
foo bar biz { /* biz */ }
foo bar {
  /* biz end */
}
foo {
  /* bar end */
} /* foo end */"""),
        );
      });
    });
  });
}
