Update revert regex to be more flexible with surrounding text and other quality changes (#2226)

diff --git a/auto_submit/lib/service/github_service.dart b/auto_submit/lib/service/github_service.dart
index 2affddc..87f0692 100644
--- a/auto_submit/lib/service/github_service.dart
+++ b/auto_submit/lib/service/github_service.dart
@@ -145,29 +145,50 @@
   /// Compare the filesets of the current pull request and the original pull
   /// request that is being reverted.
   Future<bool> comparePullRequests(RepositorySlug repositorySlug, PullRequest revert, PullRequest current) async {
-    List<PullRequestFile> originalPullRequestFiles = await getPullRequestFiles(repositorySlug, revert);
-    List<PullRequestFile> currentPullRequestFiles = await getPullRequestFiles(repositorySlug, current);
+    final List<PullRequestFile> originalPullRequestFiles = await getPullRequestFiles(repositorySlug, revert);
+    final List<PullRequestFile> currentPullRequestFiles = await getPullRequestFiles(repositorySlug, current);
 
-    return _validateFileSetsAreEqual(originalPullRequestFiles, currentPullRequestFiles);
+    return validateFileSetsAreEqual(originalPullRequestFiles, currentPullRequestFiles);
   }
 
   /// Validate that each pull request has the same number of files and that the
   /// file names match. This must be the case in order to process the revert.
-  bool _validateFileSetsAreEqual(
-    List<PullRequestFile> revertPullRequestFiles,
-    List<PullRequestFile> currentPullRequestFiles,
+  bool validateFileSetsAreEqual(
+    List<PullRequestFile> revertRequestFileList,
+    List<PullRequestFile> originalRequestFileList,
   ) {
-    List<String?> revertFileNames = [];
-    List<String?> currentFileNames = [];
+    if (revertRequestFileList.length != originalRequestFileList.length) {
+      return false;
+    }
 
-    for (PullRequestFile element in revertPullRequestFiles) {
+    final List<String?> revertFileNames = [];
+    final List<String?> originalFileNames = [];
+
+    for (PullRequestFile element in revertRequestFileList) {
       revertFileNames.add(element.filename);
     }
-    for (PullRequestFile element in currentPullRequestFiles) {
-      currentFileNames.add(element.filename);
+    for (PullRequestFile element in originalRequestFileList) {
+      originalFileNames.add(element.filename);
     }
 
-    return revertFileNames.toSet().containsAll(currentFileNames) &&
-        currentFileNames.toSet().containsAll(revertFileNames);
+    // At this point we know the file lists have the same amount of files but not the same files.
+    if (!revertFileNames.toSet().containsAll(originalFileNames) ||
+        !originalFileNames.toSet().containsAll(revertFileNames)) {
+      return false;
+    }
+
+    // At this point all the files are the same so we can iterate over one list to
+    // compare changes.
+    for (PullRequestFile revertRequestFile in revertRequestFileList) {
+      final PullRequestFile originalRequestFile =
+          originalRequestFileList.firstWhere((element) => element.filename == revertRequestFile.filename);
+      if (revertRequestFile.changesCount != originalRequestFile.changesCount ||
+          revertRequestFile.additionsCount != originalRequestFile.deletionsCount ||
+          revertRequestFile.deletionsCount != originalRequestFile.additionsCount) {
+        return false;
+      }
+    }
+
+    return true;
   }
 }
diff --git a/auto_submit/lib/validations/revert.dart b/auto_submit/lib/validations/revert.dart
index 0c2fee2..2cccccd 100644
--- a/auto_submit/lib/validations/revert.dart
+++ b/auto_submit/lib/validations/revert.dart
@@ -102,10 +102,13 @@
     if (bodyText == null) {
       return null;
     }
-    final RegExp regExp = RegExp(r'^[Rr]everts[\s]+([-\.a-zA-Z_]+/[-\.a-zA-Z_]+#[0-9]+)$', multiLine: true);
+    final RegExp regExp = RegExp(r'[Rr]everts[\s]+([-\.a-zA-Z_]+/[-\.a-zA-Z_]+#[0-9]+)', multiLine: true);
     Iterable<RegExpMatch> matches = regExp.allMatches(bodyText);
-    if (matches.isNotEmpty) {
+
+    if (matches.isNotEmpty && matches.length == 1) {
       return matches.elementAt(0).group(1);
+    } else if (matches.isNotEmpty && matches.length != 1) {
+      log.warning('Detected more than 1 revert link. Cannot process more than one link.');
     }
     return null;
   }
diff --git a/auto_submit/test/src/service/fake_github_service.dart b/auto_submit/test/src/service/fake_github_service.dart
index 3148b27..e39a1d0 100644
--- a/auto_submit/test/src/service/fake_github_service.dart
+++ b/auto_submit/test/src/service/fake_github_service.dart
@@ -229,25 +229,47 @@
     List<PullRequestFile> revertPullRequestFiles = await getPullRequestFiles(repositorySlug, revert);
     List<PullRequestFile> currentPullRequestFiles = await getPullRequestFiles(repositorySlug, current);
 
-    return _validateFileSetsAreEqual(revertPullRequestFiles, currentPullRequestFiles);
+    return validateFileSetsAreEqual(revertPullRequestFiles, currentPullRequestFiles);
   }
 
-  bool _validateFileSetsAreEqual(
-    List<PullRequestFile> revertPullRequestFiles,
-    List<PullRequestFile> currentPullRequestFiles,
+  @override
+  bool validateFileSetsAreEqual(
+    List<PullRequestFile> changeList1,
+    List<PullRequestFile> changeList2,
   ) {
-    List<String?> revertFileNames = [];
-    List<String?> currentFileNames = [];
+    if (changeList1.length != changeList2.length) {
+      return false;
+    }
 
-    for (var element in revertPullRequestFiles) {
+    final List<String?> revertFileNames = [];
+    final List<String?> currentFileNames = [];
+
+    for (PullRequestFile element in changeList1) {
       revertFileNames.add(element.filename);
     }
-    for (var element in currentPullRequestFiles) {
+    for (PullRequestFile element in changeList2) {
       currentFileNames.add(element.filename);
     }
 
-    return revertFileNames.toSet().containsAll(currentFileNames) &&
-        currentFileNames.toSet().containsAll(revertFileNames);
+    // At this point we know the file lists have the same amount of files but not the same files.
+    if (!revertFileNames.toSet().containsAll(currentFileNames) ||
+        !currentFileNames.toSet().containsAll(revertFileNames)) {
+      return false;
+    }
+
+    // At this point all the files are the same so we can iterate over one list to
+    // compare changes.
+    for (PullRequestFile pullRequestFile in changeList1) {
+      PullRequestFile pullRequestFileChangeList2 =
+          changeList2.firstWhere((element) => element.filename == pullRequestFile.filename);
+      if (pullRequestFile.changesCount != pullRequestFileChangeList2.changesCount ||
+          pullRequestFile.additionsCount != pullRequestFileChangeList2.deletionsCount ||
+          pullRequestFile.deletionsCount != pullRequestFileChangeList2.additionsCount) {
+        return false;
+      }
+    }
+
+    return true;
   }
 
   @override
diff --git a/auto_submit/test/validations/revert_test.dart b/auto_submit/test/validations/revert_test.dart
index c81766e..9e72a3b 100644
--- a/auto_submit/test/validations/revert_test.dart
+++ b/auto_submit/test/validations/revert_test.dart
@@ -79,6 +79,12 @@
 
       Some other notes in the description a developer might add.
       And another note."""] = 'flutter/cocoon#123456';
+      tests['Pull request Reverts flutter/flutter#12334 is happening continuously.'] = 'flutter/flutter#12334';
+      tests['Reverts reverts flutter/flutter#9876'] = 'flutter/flutter#9876';
+      tests["""Some junk reverts flutter/cocoon#4563 is happening continuously.
+      Some other tests in the description that someone might add.
+      """] = 'flutter/cocoon#4563';
+      tests['This some text to add flavor before reverts flutter/flutter#8888.'] = 'flutter/flutter#8888';
 
       tests.forEach((key, value) {
         String? linkFound = revert.extractLinkFromText(key);
@@ -93,6 +99,15 @@
       tests['revert flutter/cocoon#123456'] = '';
       tests['Reverts flutter/cocoon#'] = '';
       tests['Reverts flutter123'] = '';
+      // We should not allow processing of more than one link as this can be cause
+      // suspicion of other non revert changes in the pull request.
+      tests["""Reverts flutter/flutter#12345
+      Reverts flutter/flutter#34543"""] = '';
+      tests["""This some text to add flavor before reverts flutter/flutter#8888.
+      Also please reverts flutter/flutter#7678.
+      And reverts flutter/flutter#8763.
+      """] = '';
+      tests['This is some text flutter/flutter#456...44'] = '';
 
       tests.forEach((key, value) {
         String? linkFound = revert.extractLinkFromText(key);
diff --git a/auto_submit/test/validations/revert_test_data.dart b/auto_submit/test/validations/revert_test_data.dart
index a5e2bb3..b19d325 100644
--- a/auto_submit/test/validations/revert_test_data.dart
+++ b/auto_submit/test/validations/revert_test_data.dart
@@ -168,79 +168,154 @@
 const String revertPullRequestFilesJson = """
   [
     {
-      "filename": "dashboard/analysis_options.yaml"
+      "filename": "dashboard/analysis_options.yaml",
+      "additions": 0,
+      "deletions": 1,
+      "changes": 1
     },
     {
-      "filename": "dashboard/lib/build_dashboard_page.dart"
+      "filename": "dashboard/lib/build_dashboard_page.dart",
+      "additions": 35,
+      "deletions": 45,
+      "changes": 80
     },
     {
-      "filename": "dashboard/lib/index_page.dart"
+      "filename": "dashboard/lib/index_page.dart",
+      "additions": 10,
+      "deletions": 12,
+      "changes": 22
     },
     {
-      "filename": "dashboard/lib/logic/brooks.dart"
+      "filename": "dashboard/lib/logic/brooks.dart",
+      "additions": 18,
+      "deletions": 20,
+      "changes": 38
     },
     {
-      "filename": "dashboard/lib/logic/links.dart"
+      "filename": "dashboard/lib/logic/links.dart",
+      "additions": 15,
+      "deletions": 18,
+      "changes": 33
     },
     {
-      "filename": "dashboard/lib/logic/task_grid_filter.dart"
+      "filename": "dashboard/lib/logic/task_grid_filter.dart",
+      "additions": 4,
+      "deletions": 12,
+      "changes": 16
     },
     {
-      "filename": "dashboard/lib/main.dart"
+      "filename": "dashboard/lib/main.dart",
+      "additions": 6,
+      "deletions": 7,
+      "changes": 13
     },
     {
-      "filename": "dashboard/lib/service/appengine_cocoon.dart"
+      "filename": "dashboard/lib/service/appengine_cocoon.dart",
+      "additions": 16,
+      "deletions": 23,
+      "changes": 39
     },
     {
-      "filename": "dashboard/lib/service/dev_cocoon.dart"
+      "filename": "dashboard/lib/service/dev_cocoon.dart",
+      "additions": 8,
+      "deletions": 15,
+      "changes": 23
     },
     {
-      "filename": "dashboard/lib/widgets/lattice.dart"
+      "filename": "dashboard/lib/widgets/lattice.dart",
+      "additions": 18,
+      "deletions": 22,
+      "changes": 40
     },
     {
-      "filename": "dashboard/lib/widgets/luci_task_attempt_summary.dart"
+      "filename": "dashboard/lib/widgets/luci_task_attempt_summary.dart",
+      "additions": 4,
+      "deletions": 5,
+      "changes": 9
     },
     {
-      "filename": "dashboard/lib/widgets/sign_in_button.dart"
+      "filename": "dashboard/lib/widgets/sign_in_button.dart",
+      "additions": 10,
+      "deletions": 12,
+      "changes": 22
     },
     {
-      "filename": "dashboard/lib/widgets/task_grid.dart"
+      "filename": "dashboard/lib/widgets/task_grid.dart",
+      "additions": 13,
+      "deletions": 17,
+      "changes": 30
     },
     {
-      "filename": "dashboard/lib/widgets/task_overlay.dart"
+      "filename": "dashboard/lib/widgets/task_overlay.dart",
+      "additions": 2,
+      "deletions": 3,
+      "changes": 5
     },
     {
-      "filename": "dashboard/pubspec.lock"
+      "filename": "dashboard/pubspec.lock",
+      "additions": 1,
+      "deletions": 1,
+      "changes": 2
     },
     {
-      "filename": "dashboard/test/build_dashboard_page_test.dart"
+      "filename": "dashboard/test/build_dashboard_page_test.dart",
+      "additions": 21,
+      "deletions": 33,
+      "changes": 54
     },
     {
-      "filename": "dashboard/test/index_page_test.dart"
+      "filename": "dashboard/test/index_page_test.dart",
+      "additions": 4,
+      "deletions": 7,
+      "changes": 11
     },
     {
-      "filename": "dashboard/test/logic/qualified_task_test.dart"
+      "filename": "dashboard/test/logic/qualified_task_test.dart",
+      "additions": 9,
+      "deletions": 16,
+      "changes": 25
     },
     {
-      "filename": "dashboard/test/logic/task_grid_filter_test.dart"
+      "filename": "dashboard/test/logic/task_grid_filter_test.dart",
+      "additions": 11,
+      "deletions": 18,
+      "changes": 29
     },
     {
-      "filename": "dashboard/test/service/appengine_cocoon_test.dart"
+      "filename": "dashboard/test/service/appengine_cocoon_test.dart",
+      "additions": 74,
+      "deletions": 112,
+      "changes": 186
     },
     {
-      "filename": "dashboard/test/service/google_authentication_test.dart"
+      "filename": "dashboard/test/service/google_authentication_test.dart",
+      "additions": 2,
+      "deletions": 4,
+      "changes": 6
     },
     {
-      "filename": "dashboard/test/state/build_test.dart"
+      "filename": "dashboard/test/state/build_test.dart",
+      "additions": 20,
+      "deletions": 31,
+      "changes": 51
     },
     {
-      "filename": "dashboard/test/utils/fake_build.dart"
+      "filename": "dashboard/test/utils/fake_build.dart",
+      "additions": 6,
+      "deletions": 10,
+      "changes": 16
     },
     {
-      "filename": "dashboard/test/utils/golden.dart"
+      "filename": "dashboard/test/utils/golden.dart",
+      "additions": 4,
+      "deletions": 6,
+      "changes": 10
     },
     {
-      "filename": "dashboard/test/widgets/accessibility_test.dart"
+      "filename": "dashboard/test/widgets/accessibility_test.dart",
+      "additions": 14,
+      "deletions": 22,
+      "changes": 36
     }
   ]
 """;
@@ -360,79 +435,154 @@
 const String originalPullRequestFilesJson = """
   [
     {
-      "filename": "dashboard/analysis_options.yaml"
+      "filename": "dashboard/analysis_options.yaml",
+      "additions": 1,
+      "deletions": 0,
+      "changes": 1
     },
     {
-      "filename": "dashboard/lib/build_dashboard_page.dart"
+      "filename": "dashboard/lib/build_dashboard_page.dart",
+      "additions": 45,
+      "deletions": 35,
+      "changes": 80
     },
     {
-      "filename": "dashboard/lib/index_page.dart"
+      "filename": "dashboard/lib/index_page.dart",
+      "additions": 12,
+      "deletions": 10,
+      "changes": 22
     },
     {
-      "filename": "dashboard/lib/logic/brooks.dart"
+      "filename": "dashboard/lib/logic/brooks.dart",
+      "additions": 20,
+      "deletions": 18,
+      "changes": 38
     },
     {
-      "filename": "dashboard/lib/logic/links.dart"
+      "filename": "dashboard/lib/logic/links.dart",
+      "additions": 18,
+      "deletions": 15,
+      "changes": 33
     },
     {
-      "filename": "dashboard/lib/logic/task_grid_filter.dart"
+      "filename": "dashboard/lib/logic/task_grid_filter.dart",
+      "additions": 12,
+      "deletions": 4,
+      "changes": 16
     },
     {
-      "filename": "dashboard/lib/main.dart"
+      "filename": "dashboard/lib/main.dart",
+      "additions": 7,
+      "deletions": 6,
+      "changes": 13
     },
     {
-      "filename": "dashboard/lib/service/appengine_cocoon.dart"
+      "filename": "dashboard/lib/service/appengine_cocoon.dart",
+      "additions": 23,
+      "deletions": 16,
+      "changes": 39
     },
     {
-      "filename": "dashboard/lib/service/dev_cocoon.dart"
+      "filename": "dashboard/lib/service/dev_cocoon.dart",
+      "additions": 15,
+      "deletions": 8,
+      "changes": 23
     },
     {
-      "filename": "dashboard/lib/widgets/lattice.dart"
+      "filename": "dashboard/lib/widgets/lattice.dart",
+      "additions": 22,
+      "deletions": 18,
+      "changes": 40
     },
     {
-      "filename": "dashboard/lib/widgets/luci_task_attempt_summary.dart"
+      "filename": "dashboard/lib/widgets/luci_task_attempt_summary.dart",
+      "additions": 5,
+      "deletions": 4,
+      "changes": 9
     },
     {
-      "filename": "dashboard/lib/widgets/sign_in_button.dart"
+      "filename": "dashboard/lib/widgets/sign_in_button.dart",
+      "additions": 12,
+      "deletions": 10,
+      "changes": 22
     },
     {
-      "filename": "dashboard/lib/widgets/task_grid.dart"
+      "filename": "dashboard/lib/widgets/task_grid.dart",
+      "additions": 17,
+      "deletions": 13,
+      "changes": 30
     },
     {
-      "filename": "dashboard/lib/widgets/task_overlay.dart"
+      "filename": "dashboard/lib/widgets/task_overlay.dart",
+      "additions": 3,
+      "deletions": 2,
+      "changes": 5
     },
     {
-      "filename": "dashboard/pubspec.lock"
+      "filename": "dashboard/pubspec.lock",
+      "additions": 1,
+      "deletions": 1,
+      "changes": 2
     },
     {
-      "filename": "dashboard/test/build_dashboard_page_test.dart"
+      "filename": "dashboard/test/build_dashboard_page_test.dart",
+      "additions": 33,
+      "deletions": 21,
+      "changes": 54
     },
     {
-      "filename": "dashboard/test/index_page_test.dart"
+      "filename": "dashboard/test/index_page_test.dart",
+      "additions": 7,
+      "deletions": 4,
+      "changes": 11
     },
     {
-      "filename": "dashboard/test/logic/qualified_task_test.dart"
+      "filename": "dashboard/test/logic/qualified_task_test.dart",
+      "additions": 16,
+      "deletions": 9,
+      "changes": 25
     },
     {
-      "filename": "dashboard/test/logic/task_grid_filter_test.dart"
+      "filename": "dashboard/test/logic/task_grid_filter_test.dart",
+      "additions": 18,
+      "deletions": 11,
+      "changes": 29
     },
     {
-      "filename": "dashboard/test/service/appengine_cocoon_test.dart"
+      "filename": "dashboard/test/service/appengine_cocoon_test.dart",
+      "additions": 112,
+      "deletions": 74,
+      "changes": 186
     },
     {
-      "filename": "dashboard/test/service/google_authentication_test.dart"
+      "filename": "dashboard/test/service/google_authentication_test.dart",
+      "additions": 4,
+      "deletions": 2,
+      "changes": 6
     },
     {
-      "filename": "dashboard/test/state/build_test.dart"
+      "filename": "dashboard/test/state/build_test.dart",
+      "additions": 31,
+      "deletions": 20,
+      "changes": 51
     },
     {
-      "filename": "dashboard/test/utils/fake_build.dart"
+      "filename": "dashboard/test/utils/fake_build.dart",
+      "additions": 10,
+      "deletions": 6,
+      "changes": 16
     },
     {
-      "filename": "dashboard/test/utils/golden.dart"
+      "filename": "dashboard/test/utils/golden.dart",
+      "additions": 6,
+      "deletions": 4,
+      "changes": 10
     },
     {
-      "filename": "dashboard/test/widgets/accessibility_test.dart"
+      "filename": "dashboard/test/widgets/accessibility_test.dart",
+      "additions": 22,
+      "deletions": 14,
+      "changes": 36
     }
   ]
 """;
@@ -440,73 +590,142 @@
 const String originalPullRequestFilesSubsetJson = """
   [
     {
-      "filename": "dashboard/analysis_options.yaml"
+      "filename": "dashboard/analysis_options.yaml",
+      "additions": 1,
+      "deletions": 0,
+      "changes": 1
     },
     {
-      "filename": "dashboard/lib/build_dashboard_page.dart"
+      "filename": "dashboard/lib/build_dashboard_page.dart",
+      "additions": 45,
+      "deletions": 35,
+      "changes": 80
     },
     {
-      "filename": "dashboard/lib/index_page.dart"
+      "filename": "dashboard/lib/index_page.dart",
+      "additions": 12,
+      "deletions": 10,
+      "changes": 22
     },
     {
-      "filename": "dashboard/lib/logic/brooks.dart"
+      "filename": "dashboard/lib/logic/brooks.dart",
+      "additions": 20,
+      "deletions": 18,
+      "changes": 38
     },
     {
-      "filename": "dashboard/lib/logic/links.dart"
+      "filename": "dashboard/lib/logic/links.dart",
+      "additions": 18,
+      "deletions": 15,
+      "changes": 33
     },
     {
-      "filename": "dashboard/lib/logic/task_grid_filter.dart"
+      "filename": "dashboard/lib/logic/task_grid_filter.dart",
+      "additions": 12,
+      "deletions": 4,
+      "changes": 16
     },
     {
-      "filename": "dashboard/lib/service/appengine_cocoon.dart"
+      "filename": "dashboard/lib/service/appengine_cocoon.dart",
+      "additions": 23,
+      "deletions": 16,
+      "changes": 39
     },
     {
-      "filename": "dashboard/lib/service/dev_cocoon.dart"
+      "filename": "dashboard/lib/service/dev_cocoon.dart",
+      "additions": 15,
+      "deletions": 8,
+      "changes": 23
     },
     {
-      "filename": "dashboard/lib/widgets/lattice.dart"
+      "filename": "dashboard/lib/widgets/lattice.dart",
+      "additions": 22,
+      "deletions": 18,
+      "changes": 40
     },
     {
-      "filename": "dashboard/lib/widgets/sign_in_button.dart"
+       "filename": "dashboard/lib/widgets/sign_in_button.dart",
+      "additions": 12,
+      "deletions": 10,
+      "changes": 22
     },
     {
-      "filename": "dashboard/lib/widgets/task_grid.dart"
+      "filename": "dashboard/lib/widgets/task_grid.dart",
+      "additions": 17,
+      "deletions": 13,
+      "changes": 30
     },
     {
-      "filename": "dashboard/lib/widgets/task_overlay.dart"
+      "filename": "dashboard/lib/widgets/task_overlay.dart",
+      "additions": 3,
+      "deletions": 2,
+      "changes": 5
     },
     {
-      "filename": "dashboard/pubspec.lock"
+      "filename": "dashboard/pubspec.lock",
+      "additions": 1,
+      "deletions": 1,
+      "changes": 2
     },
     {
-      "filename": "dashboard/test/build_dashboard_page_test.dart"
+      "filename": "dashboard/test/build_dashboard_page_test.dart",
+      "additions": 33,
+      "deletions": 21,
+      "changes": 54
     },
     {
-      "filename": "dashboard/test/index_page_test.dart"
+      "filename": "dashboard/test/index_page_test.dart",
+      "additions": 7,
+      "deletions": 4,
+      "changes": 11
     },
     {
-      "filename": "dashboard/test/logic/qualified_task_test.dart"
+      "filename": "dashboard/test/logic/qualified_task_test.dart",
+      "additions": 16,
+      "deletions": 9,
+      "changes": 25
     },
     {
-      "filename": "dashboard/test/logic/task_grid_filter_test.dart"
+      "filename": "dashboard/test/logic/task_grid_filter_test.dart",
+      "additions": 18,
+      "deletions": 11,
+      "changes": 29
     },
     {
-      "filename": "dashboard/test/service/appengine_cocoon_test.dart"
+      "filename": "dashboard/test/service/appengine_cocoon_test.dart",
+      "additions": 112,
+      "deletions": 74,
+      "changes": 186
     },
     {
-      "filename": "dashboard/test/service/google_authentication_test.dart"
+      "filename": "dashboard/test/service/google_authentication_test.dart",
+      "additions": 4,
+      "deletions": 2,
+      "changes": 6
     },
     {
-      "filename": "dashboard/test/state/build_test.dart"
+      "filename": "dashboard/test/state/build_test.dart",
+      "additions": 31,
+      "deletions": 20,
+      "changes": 51
     },
     {
-      "filename": "dashboard/test/utils/fake_build.dart"
+      "filename": "dashboard/test/utils/fake_build.dart",
+      "additions": 10,
+      "deletions": 6,
+      "changes": 16
     },
     {
-      "filename": "dashboard/test/utils/golden.dart"
+      "filename": "dashboard/test/utils/golden.dart",
+      "additions": 6,
+      "deletions": 4,
+      "changes": 10
     },
     {
-      "filename": "dashboard/test/widgets/accessibility_test.dart"
+      "filename": "dashboard/test/widgets/accessibility_test.dart",
+      "additions": 22,
+      "deletions": 14,
+      "changes": 36
     }
   ]
 """;
diff --git a/dashboard/pubspec.lock b/dashboard/pubspec.lock
index ce096f8..da03b86 100644
--- a/dashboard/pubspec.lock
+++ b/dashboard/pubspec.lock
@@ -323,7 +323,7 @@
       name: material_color_utilities
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.1.5"
+    version: "0.2.0"
   meta:
     dependency: transitive
     description:
@@ -447,7 +447,7 @@
       name: source_span
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.9.0"
+    version: "1.9.1"
   stack_trace:
     dependency: transitive
     description:
@@ -461,7 +461,7 @@
       name: stream_channel
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.1.0"
+    version: "2.1.1"
   stream_transform:
     dependency: transitive
     description:
@@ -489,7 +489,7 @@
       name: test_api
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.4.12"
+    version: "0.4.14"
   timing:
     dependency: transitive
     description:
@@ -573,7 +573,7 @@
       name: vector_math
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.1.2"
+    version: "2.1.4"
   watcher:
     dependency: transitive
     description:
@@ -596,5 +596,5 @@
     source: hosted
     version: "3.1.1"
 sdks:
-  dart: ">=2.17.0 <3.0.0"
+  dart: ">=2.18.0 <3.0.0"
   flutter: ">=2.10.0"