// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:analysis_server/src/computer/computer_folding.dart';
import 'package:analysis_server/src/protocol_server.dart';
import 'package:analyzer/src/test_utilities/test_code_format.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

import '../../abstract_context.dart';

void main() {
  defineReflectiveSuite(() {
    defineReflectiveTests(FoldingComputerTest);
  });
}

@reflectiveTest
class FoldingComputerTest extends AbstractContextTest {
  static const commentKinds = {
    FoldingKind.FILE_HEADER,
    FoldingKind.COMMENT,
    FoldingKind.DOCUMENTATION_COMMENT,
  };

  late String sourcePath;
  late TestCode code;
  List<FoldingRegion> regions = [];

  /// Expects no [FoldingRegion]s.
  ///
  /// If [onlyVerify] is provided, folding regions of other kinds are allowed.
  void expectNoRegions({Set<FoldingKind>? onlyVerify}) {
    expectRegions({}, onlyVerify: onlyVerify);
  }

  /// Expects to find a [FoldingRegion] for the code marked [index] with a
  /// [FoldingKind] of [kind].
  ///
  /// If [onlyVerify] is provided, only folding regions with matching kinds will
  /// be verified.
  void expectRegions(
    Map<int, FoldingKind> expected, {
    Set<FoldingKind>? onlyVerify,
  }) {
    var expectedRegions = expected.entries.map((entry) {
      var range = code.ranges[entry.key].sourceRange;
      return FoldingRegion(entry.value, range.offset, range.length);
    }).toSet();

    var actualRegions = onlyVerify == null
        ? regions.toSet()
        : regions.where((region) => onlyVerify.contains(region.kind)).toSet();

    expect(actualRegions, expectedRegions);
  }

  @override
  void setUp() {
    super.setUp();
    sourcePath = convertPath('$testPackageLibPath/test.dart');
  }

  Future<void> test_annotations_class() async {
    var content = '''
@folded.classAnnotation1/*[0*/()
@foldedClassAnnotation2/*0]*/
class MyClass {}
''';

    await _computeRegions(content);
    expectRegions({0: FoldingKind.ANNOTATIONS});
  }

  Future<void> test_annotations_class_constructor() async {
    var content = '''
class MyClass {
  @constructorAnnotation1/*[0*/
  @constructorAnnotation1/*0]*/
  MyClass() {}

  @constructorAnnotation1/*[1*/
  @constructorAnnotation1/*1]*/
  MyClass.named() {}
}
''';

    await _computeRegions(content);
    expectRegions(
      {0: FoldingKind.ANNOTATIONS, 1: FoldingKind.ANNOTATIONS},
      onlyVerify: {FoldingKind.ANNOTATIONS},
    );
  }

  Future<void> test_annotations_class_field() async {
    var content = '''
class MyClass {
  @fieldAnnotation1/*[0*/
  @fieldAnnotation2/*0]*/
  int myField;
}
''';

    await _computeRegions(content);
    expectRegions(
      {0: FoldingKind.ANNOTATIONS},
      onlyVerify: {FoldingKind.ANNOTATIONS},
    );
  }

  Future<void> test_annotations_class_getterSetter() async {
    var content = '''
class MyClass {
  @getterAnnotation1/*[0*/
  @getterAnnotation2/*0]*/
  int get myThing => 1;

  @setterAnnotation1/*[1*/
  @setterAnnotation2/*1]*/
  void set myThing(int value) {}
}
''';

    await _computeRegions(content);
    expectRegions(
      {0: FoldingKind.ANNOTATIONS, 1: FoldingKind.ANNOTATIONS},
      onlyVerify: {FoldingKind.ANNOTATIONS},
    );
  }

  Future<void> test_annotations_class_method() async {
    var content = '''
class MyClass {
  @methodAnnotation1/*[0*/
  @methodAnnotation2/*0]*/
  void myMethod() {}
}
''';

    await _computeRegions(content);
    expectRegions(
      {0: FoldingKind.ANNOTATIONS},
      onlyVerify: {FoldingKind.ANNOTATIONS},
    );
  }

  Future<void> test_annotations_multiline() async {
    var content = '''
@myMultilineAnnotation/*[0*/(
  "this",
  "is a test"
)/*0]*/
void f() {}
''';

    await _computeRegions(content);
    expectRegions(
      {0: FoldingKind.ANNOTATIONS},
      onlyVerify: {FoldingKind.ANNOTATIONS},
    );
  }

  Future<void> test_annotations_multiple() async {
    var content = '''
@multipleAnnotations1/*[0*/(
  /*[1*/"this",
  "is a test"
/*1]*/)
@multipleAnnotations2()
@multipleAnnotations3/*0]*/
main3() {}
''';

    await _computeRegions(content);
    expectRegions(
      {0: FoldingKind.ANNOTATIONS},
      onlyVerify: {FoldingKind.ANNOTATIONS},
    );
  }

  Future<void> test_annotations_singleLine() async {
    var content = '''
@noFoldNecessary
main2() {}

@noFoldsForSingleClassAnnotation
class MyClass {}
''';

    await _computeRegions(content);
    expectNoRegions();
  }

  Future<void> test_assertInitializer() async {
    var content = '''
class C/*[0*/ {
  C/*[1*/() : assert(
    /*[2*/true,
    ''
  /*2]*/);/*1]*/
}/*0]*/
''';
    await _computeRegions(content);
    expectRegions({
      0: FoldingKind.CLASS_BODY,
      1: FoldingKind.FUNCTION_BODY,
      2: FoldingKind.INVOCATION,
    });
  }

  Future<void> test_assertStatement() async {
    var content = '''
void f/*[0*/() {
  assert(/*[1*/
    true,
    ''
  /*1]*/);
}/*0]*/
''';
    await _computeRegions(content);
    expectRegions({0: FoldingKind.FUNCTION_BODY, 1: FoldingKind.INVOCATION});
  }

  Future<void> test_class() async {
    var content = '''
// Content before

class Person/*[0*/ {
  Person/*[1*/() {
    print("Hello, world!");
  }/*1]*/

  void sayHello/*[2*/() {
    print("Hello, world!");
  }/*2]*/
}/*0]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({
      0: FoldingKind.CLASS_BODY,
      1: FoldingKind.FUNCTION_BODY,
      2: FoldingKind.FUNCTION_BODY,
    });
  }

  Future<void> test_comment_is_not_considered_file_header() async {
    var content = """
// This is not the file header/*[0*/
// It's just a comment/*0]*/
void f() {}
""";

    await _computeRegions(content);
    expectRegions({0: FoldingKind.COMMENT});
  }

  Future<void> test_comment_multiline() async {
    var content = '''
/*/*[0*/
 * comment 1
 *//*0]*/

/* this comment starts on the same line as delimiters/*[1*/
 * second line
 *//*1]*/
void f() {}
''';

    await _computeRegions(content);
    expectRegions({0: FoldingKind.COMMENT, 1: FoldingKind.COMMENT});
  }

  Future<void> test_comment_singleFollowedByBlankLine() async {
    var content = '''
void f() {
// this is/*[0*/
// a comment/*0]*/
/// this is not part of it
}
''';

    await _computeRegions(content);
    expectRegions({0: FoldingKind.COMMENT}, onlyVerify: commentKinds);
  }

  Future<void> test_comment_singleFollowedByMulti() async {
    var content = '''
void f() {
  // this is/*[0*/
  // a comment/*0]*/
  /* this is not part of it */
  String foo;
}
''';

    await _computeRegions(content);
    expectRegions({0: FoldingKind.COMMENT}, onlyVerify: commentKinds);
  }

  Future<void> test_comment_singleFollowedByTripleSlash() async {
    var content = '''
void f() {
// this is/*[0*/
// a comment/*0]*/
/// this is not part of it
}
''';

    await _computeRegions(content);
    expectRegions({0: FoldingKind.COMMENT}, onlyVerify: commentKinds);
  }

  Future<void> test_constructor() async {
    var content = '''
// Content before

class C/*[0*/ {
  C/*[1*/() {
    //
  }/*1]*/
}/*0]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({0: FoldingKind.CLASS_BODY, 1: FoldingKind.FUNCTION_BODY});
  }

  Future<void> test_constructor_emptyBody_semicolon() async {
    var content = '''
// Content before

class C/*[0*/ {
  C();
}/*0]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({0: FoldingKind.CLASS_BODY});
  }

  Future<void> test_constructor_invocations() async {
    var content = '''
// Content before

final a = new Text(/*[0*/
  "Hello, world!",
/*0]*/);

// Content after
''';

    await _computeRegions(content);
    expectRegions({0: FoldingKind.INVOCATION});
  }

  Future<void> test_constructor_named() async {
    var content = '''
// Content before

class C/*[0*/ {
  C.foo/*[1*/() {
    //
  }/*1]*/
}/*0]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({0: FoldingKind.CLASS_BODY, 1: FoldingKind.FUNCTION_BODY});
  }

  Future<void> test_constructor_new() async {
    var content = '''
// Content before

class C/*[0*/ {
  new/*[1*/() {
    //
  }/*1]*/
}/*0]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({0: FoldingKind.CLASS_BODY, 1: FoldingKind.FUNCTION_BODY});
  }

  Future<void> test_extensionType() async {
    var content = '''
// Content before

extension type E(int it) {/*[0*/
  void m/*[1*/() {
  }/*1]*/
/*0]*/}

// Content after
''';

    await _computeRegions(content);
    expectRegions({0: FoldingKind.CLASS_BODY, 1: FoldingKind.FUNCTION_BODY});
  }

  Future<void> test_file_header() async {
    var content = """
// Copyright some year by some people/*[0*/
// See LICENCE etc./*0]*/

// This is not the file header
// It's just a comment
void f() {}
""";

    await _computeRegions(content);
    expectRegions(
      {0: FoldingKind.FILE_HEADER},
      onlyVerify: {FoldingKind.FILE_HEADER},
    );
  }

  Future<void> test_file_header_does_not_include_block_comments() async {
    var content = """
/*
 * Copyright some year by some people
 * See LICENCE etc.
 */
/* This shouldn't be part of the file header */

void f() {}
""";

    await _computeRegions(content);
    expectNoRegions(onlyVerify: {FoldingKind.FILE_HEADER});
  }

  Future<void> test_file_header_with_no_function_comment() async {
    var content = '''
// Copyright some year by some people/*[0*/
// See LICENCE etc./*0]*/

void f() {}
''';

    await _computeRegions(content);
    expectRegions({0: FoldingKind.FILE_HEADER});
  }

  Future<void> test_file_header_with_non_end_of_line_comment() async {
    var content = """
// Copyright some year by some people/*[0*/
// See LICENCE etc./*0]*/
/* This shouldn't be part of the file header */

void f() {}
""";

    await _computeRegions(content);
    expectRegions({0: FoldingKind.FILE_HEADER});
  }

  Future<void> test_file_header_with_script_prefix() async {
    var content = """
#! /usr/bin/dart
// Copyright some year by some people/*[0*/
// See LICENCE etc./*0]*/

// This is not the file header
// It's just a comment
void f() {}
""";

    await _computeRegions(content);
    expectRegions(
      {0: FoldingKind.FILE_HEADER},
      onlyVerify: {FoldingKind.FILE_HEADER},
    );
  }

  Future<void> test_fileHeader_singleFollowedByBlank() async {
    var content = '''
// this is/*[0*/
// a file header/*0]*/

// this is not part of it
void f() {}
''';

    await _computeRegions(content);
    expectRegions({0: FoldingKind.FILE_HEADER});
  }

  Future<void> test_function() async {
    var content = '''
// Content before

void f/*[0*/() {
  print("Hello, world!");
}/*0]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({0: FoldingKind.FUNCTION_BODY});
  }

  Future<void> test_function_expression_invocation() async {
    var content = '''
// Content before

getFunc/*[0*/() => (String a, String b) {
  print(a);
};/*0]*/

main2/*[1*/() {
  getFunc()(/*[2*/
    "one",
    "two"
  /*2]*/);
}/*1]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({
      0: FoldingKind.FUNCTION_BODY,
      1: FoldingKind.FUNCTION_BODY,
      2: FoldingKind.INVOCATION,
    });
  }

  Future<void> test_function_expression_underscore_preWildcards() async {
    var content = '''
// /*[0*/
// @dart = 3.4
// (pre wildcard-variables)/*0]*/

// Content before

void f/*[1*/() {
  _/*[2*/(){
  }/*2]*/
}/*1]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({
      0: FoldingKind.FILE_HEADER,
      1: FoldingKind.FUNCTION_BODY,
      2: FoldingKind.FUNCTION_BODY,
    });
  }

  Future<void> test_function_expression_wildcard() async {
    var content = '''
// Content before

void f/*[0*/() {
  _/*[1*/(){
  }/*1]*/
}/*0]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({0: FoldingKind.FUNCTION_BODY, 1: FoldingKind.FUNCTION_BODY});
  }

  Future<void> test_function_with_dart_doc() async {
    var content = '''
// Content before

/// This is a doc comment/*[0*/
/// that spans lines/*0]*/
void f/*[1*/() {
  print("Hello, world!");
}/*1]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({
      0: FoldingKind.DOCUMENTATION_COMMENT,
      1: FoldingKind.FUNCTION_BODY,
    });
  }

  Future<void> test_invocations() async {
    var content = '''
// Content before

void f/*[0*/() {
  print(/*[1*/
    "Hello, world!",
  /*1]*/);
}/*0]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({0: FoldingKind.FUNCTION_BODY, 1: FoldingKind.INVOCATION});
  }

  Future<void> test_literal_list() async {
    var content = '''
// Content before

void f/*[0*/() {
  final List<String> things = <String>[/*[1*/
    "one",
    "two"
  /*1]*/];
}/*0]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({0: FoldingKind.FUNCTION_BODY, 1: FoldingKind.LITERAL});
  }

  Future<void> test_literal_list_insideNullAwareElement() async {
    var content = '''
// Content before

void f/*[0*/() {
  final List<List<String>> things = <List<String>>[/*[1*/
    ?<String>[/*[2*/
      "one",
      "two"
    /*2]*/];
  /*1]*/];
}/*0]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({
      0: FoldingKind.FUNCTION_BODY,
      1: FoldingKind.LITERAL,
      2: FoldingKind.LITERAL,
    });
  }

  Future<void> test_literal_list_insideNullAwareKey() async {
    var content = '''
// Content before

void f/*[0*/() {
  final Map<List<String>, bool> things = <List<String>, bool>{/*[1*/
    ?<String>[/*[2*/
      "one",
      "two"
    /*2]*/]: true
  /*1]*/};
}/*0]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({
      0: FoldingKind.FUNCTION_BODY,
      1: FoldingKind.LITERAL,
      2: FoldingKind.LITERAL,
    });
  }

  Future<void> test_literal_list_insideNullAwareValue() async {
    var content = '''
// Content before

void f/*[0*/() {
  final Map<Symbol, List<String>> things = <Symbol, List<String>>{/*[1*/
    #key: ?<String>[/*[2*/
      "one",
      "two"
    /*2]*/]
  /*1]*/};
}/*0]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({
      0: FoldingKind.FUNCTION_BODY,
      1: FoldingKind.LITERAL,
      2: FoldingKind.LITERAL,
    });
  }

  Future<void> test_literal_list_withNullAwareElement() async {
    var content = '''
// Content before

void f/*[0*/() {
  final List<String> things = <String>[/*[1*/
    "one",
    ?null
    "two"
  /*1]*/];
}/*0]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({0: FoldingKind.FUNCTION_BODY, 1: FoldingKind.LITERAL});
  }

  Future<void> test_literal_map() async {
    var content = '''
// Content before

void f/*[0*/() {
  final Map<String, String> things = <String, String>{/*[1*/
    "one": "one",
    "two": "two"
    /*1]*/};
}/*0]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({0: FoldingKind.FUNCTION_BODY, 1: FoldingKind.LITERAL});
  }

  Future<void> test_literal_map_insideNullAwareKey() async {
    var content = '''
// Content before

void f/*[0*/() {
  final Map<Map<String, int>, false>  things = <Map<String, int>, false>{/*[1*/
    ?<String, int>{/*[2*/
      "one": 1,
      "two": 2
    /*2]*/}: true
  /*1]*/};
}/*0]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({
      0: FoldingKind.FUNCTION_BODY,
      1: FoldingKind.LITERAL,
      2: FoldingKind.LITERAL,
    });
  }

  Future<void> test_literal_map_insideNullAwareValue() async {
    var content = '''
// Content before

void f/*[0*/() {
  final Map<Symbol, Map<String, int>>  things = <Symbol, Map<String, int>>{/*[1*/
    #key: ?<String, int>{/*[2*/
      "one": 1,
      "two": 2
    /*2]*/}
  /*1]*/};
}/*0]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({
      0: FoldingKind.FUNCTION_BODY,
      1: FoldingKind.LITERAL,
      2: FoldingKind.LITERAL,
    });
  }

  Future<void> test_literal_map_withNullAwareKey() async {
    var content = '''
// Content before

void f/*[0*/() {
  final Map<String, int> things = <String, int>{/*[1*/
    "one": 1,
    ?null: 0,
    "two": 2
  /*1]*/};
}/*0]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({0: FoldingKind.FUNCTION_BODY, 1: FoldingKind.LITERAL});
  }

  Future<void> test_literal_map_withNullAwareValue() async {
    var content = '''
// Content before

void f/*[0*/() {
  final Map<String, int> things = <String, int>{/*[1*/
    "one": 1,
    "null": ?null,
    "two": 2
  /*1]*/};
}/*0]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({0: FoldingKind.FUNCTION_BODY, 1: FoldingKind.LITERAL});
  }

  Future<void> test_literal_record() async {
    var content = '''
// Content before

void f/*[0*/() {
  final r = (/*[1*/
    "one",
    2,
    (/*[2*/
    'nested',
    3,
    'field record',
    /*2]*/),
  /*1]*/);
}/*0]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({
      0: FoldingKind.FUNCTION_BODY,
      1: FoldingKind.LITERAL,
      2: FoldingKind.LITERAL,
    });
  }

  Future<void> test_literal_set_insideNullAwareElement() async {
    var content = '''
// Content before

void f/*[0*/() {
  final Set<Set<String>> things = <Set<String>>{/*[1*/
    ?<String>{/*[2*/
      "one",
      "two"
    /*2]*/}
  /*1]*/};
}/*0]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({
      0: FoldingKind.FUNCTION_BODY,
      1: FoldingKind.LITERAL,
      2: FoldingKind.LITERAL,
    });
  }

  Future<void> test_literal_set_insideNullAwareKey() async {
    var content = '''
// Content before

void f/*[0*/() {
  final Map<Set<String>, bool> things = <Set<String>, bool>{/*[1*/
    ?<String>{/*[2*/
      "one",
      "two"
    /*2]*/}: true
  /*1]*/};
}/*0]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({
      0: FoldingKind.FUNCTION_BODY,
      1: FoldingKind.LITERAL,
      2: FoldingKind.LITERAL,
    });
  }

  Future<void> test_literal_set_insideNullAwareValue() async {
    var content = '''
// Content before

void f/*[0*/() {
  final Map<Symbol, Set<String>> things = <Symbol, Set<String>>{/*[1*/
    #key: ?<String>{/*[2*/
      "one",
      "two"
    /*2]*/}
  /*1]*/};
}/*0]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({
      0: FoldingKind.FUNCTION_BODY,
      1: FoldingKind.LITERAL,
      2: FoldingKind.LITERAL,
    });
  }

  Future<void> test_literal_set_withNullAwareElement() async {
    var content = '''
// Content before

void f/*[0*/() {
  final Set<String> things = <String>{/*[1*/
    "one",
    ?null
    "two"
  /*1]*/};
}/*0]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({0: FoldingKind.FUNCTION_BODY, 1: FoldingKind.LITERAL});
  }

  Future<void> test_mixin() async {
    var content = '''
// Content before

mixin M/*[0*/ {
  void m/*[1*/() {
    print("Got to m");
  }/*1]*/
}/*0]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({0: FoldingKind.CLASS_BODY, 1: FoldingKind.FUNCTION_BODY});
  }

  Future<void> test_multiple_directive_types() async {
    var content = """
import/*[0*/ 'dart:async';

// We can have comments
import 'package:a/b.dart';
import 'package:b/c.dart';

export '../a.dart';/*0]*/

void f() {}
""";

    await _computeRegions(content);
    expectRegions({0: FoldingKind.DIRECTIVES});
  }

  Future<void> test_multiple_import_directives() async {
    var content = """
import/*[0*/ 'dart:async';

// We can have comments
import 'package:a/b.dart';
import 'package:b/c.dart';

import '../a.dart';/*0]*/

void f() {}
""";

    await _computeRegions(content);
    expectRegions({0: FoldingKind.DIRECTIVES});
  }

  Future<void> test_multiple_import_directives_partFile() async {
    var content = """
part/*[0*/ of 'part.dart';

import 'dart:async';

// We can have comments
import 'package:a/b.dart';
import 'package:b/c.dart';

import '../a.dart';/*0]*/

void f() {}
""";

    await _computeRegions(content);
    expectRegions({0: FoldingKind.DIRECTIVES});
  }

  Future<void> test_nested_function() async {
    var content = '''
// Content before

void f/*[0*/() {
  doPrint/*[1*/() {
    print("Hello, world!");
  }/*1]*/
  doPrint();
}/*0]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({0: FoldingKind.FUNCTION_BODY, 1: FoldingKind.FUNCTION_BODY});
  }

  Future<void> test_nested_invocations() async {
    var content = '''
// Content before

void f/*[0*/() {
  a(/*[1*/
    b(/*[2*/
      c(/*[3*/
        d()
      /*3]*/),
    /*2]*/),
  /*1]*/);
}/*0]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({
      0: FoldingKind.FUNCTION_BODY,
      1: FoldingKind.INVOCATION,
      2: FoldingKind.INVOCATION,
      3: FoldingKind.INVOCATION,
    });
  }

  Future<void> test_parameters_function() async {
    var content = '''
foo/*[0*/(
  /*[1*/String aaaaa,
  String bbbbb, {
  String ccccc,
  }/*1]*/) {}/*0]*/
''';
    await _computeRegions(content);
    expectRegions({0: FoldingKind.FUNCTION_BODY, 1: FoldingKind.PARAMETERS});
  }

  Future<void> test_parameters_method() async {
    var content = '''
class C/*[0*/ {
  C/*[1*/(
    /*[2*/String aaaaa,
    String bbbbb,
  /*2]*/) : super();/*1]*/
}/*0]*/
''';
    await _computeRegions(content);
    expectRegions({
      0: FoldingKind.CLASS_BODY,
      1: FoldingKind.FUNCTION_BODY,
      2: FoldingKind.PARAMETERS,
    });
  }

  Future<void> test_primaryConstructor_constructorBody() async {
    var content = '''
// Content before

class C(/*[0*/
  var int x,
  var int y,
/*0]*/)/*[1*/ {
  this/*[2*/ {
    print('');
  }/*2]*/
}/*1]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({
      0: FoldingKind.PARAMETERS,
      1: FoldingKind.CLASS_BODY,
      2: FoldingKind.FUNCTION_BODY,
    });
  }

  Future<void> test_primaryConstructor_constructorInitializer() async {
    var content = '''
// Content before

class C(/*[0*/
  var int x,
  var int y,
/*0]*/)/*[1*/ {
  this/*[2*/ : xPlus = x+1,
    yPlus = y+1;/*2]*/
}/*1]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({
      0: FoldingKind.PARAMETERS,
      1: FoldingKind.CLASS_BODY,
      2: FoldingKind.FUNCTION_BODY,
    });
  }

  Future<void> test_primaryConstructor_emptyClassBody_braces() async {
    var content = '''
// Content before

class C(/*[0*/
  var int x,
  var int y,
/*0]*/) extends Object {}

// Content after
''';

    await _computeRegions(content);
    expectRegions({0: FoldingKind.PARAMETERS});
  }

  Future<void> test_primaryConstructor_emptyClassBody_semicolon() async {
    var content = '''
// Content before

class C(/*[0*/
  var int x,
  var int y,
/*0]*/) extends Object;

// Content after
''';

    await _computeRegions(content);
    expectRegions({0: FoldingKind.PARAMETERS});
  }

  Future<void> test_primaryConstructor_nonEmptyClassBody() async {
    var content = '''
// Content before

class C(/*[0*/
  var int x,
  var int y,
/*0]*/)/*[1*/ {
  //
}/*1]*/

// Content after
''';

    await _computeRegions(content);
    expectRegions({0: FoldingKind.PARAMETERS, 1: FoldingKind.CLASS_BODY});
  }

  Future<void> test_single_import_directives() async {
    var content = """
import 'dart:async';

void f() {}
""";

    await _computeRegions(content);
    expectNoRegions();
  }

  Future<void> test_tryCatchFinally() async {
    var content = '''
void f/*[0*/() {
  try {/*[1*/
    print('');
    print('');/*1]*/
  } on ArgumentError catch (_) {/*[2*/
    print('');
    print('');/*2]*/
  } catch (e) {/*[3*/
    print('');
    print('');/*3]*/
  } finally {/*[4*/
    print('');
    print('');/*4]*/
  }
}/*0]*/
''';

    await _computeRegions(content);
    expectRegions({
      0: FoldingKind.FUNCTION_BODY,
      1: FoldingKind.BLOCK,
      2: FoldingKind.BLOCK,
      3: FoldingKind.BLOCK,
      4: FoldingKind.BLOCK,
    });
  }

  Future<void> _computeRegions(String sourceContent) async {
    code = TestCode.parseNormalized(sourceContent);
    var file = newFile(sourcePath, code.code);
    var result = await getResolvedUnit(file);
    var computer = DartUnitFoldingComputer(result.lineInfo, result.unit);
    regions = computer.compute();
  }
}
