diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..40cab31
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,11 @@
+# Set update schedule for GitHub Actions
+# See https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/keeping-your-actions-up-to-date-with-dependabot
+
+version: 2
+updates:
+
+- package-ecosystem: "github-actions"
+  directory: "/"
+  schedule:
+    # Check for updates to GitHub Actions every weekday
+    interval: "monthly"
diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml
new file mode 100644
index 0000000..9bf6845
--- /dev/null
+++ b/.github/workflows/test-package.yml
@@ -0,0 +1,64 @@
+name: Dart CI
+
+on:
+  # Run on PRs and pushes to the default branch.
+  push:
+    branches: [ master ]
+  pull_request:
+    branches: [ master ]
+  schedule:
+    - cron: "0 0 * * 0"
+
+env:
+  PUB_ENVIRONMENT: bot.github
+
+jobs:
+  # Check code formatting and static analysis on a single OS (linux)
+  # against Dart dev.
+  analyze:
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        sdk: [dev]
+    steps:
+      - uses: actions/checkout@v3
+      - uses: dart-lang/setup-dart@v1
+        with:
+          sdk: ${{ matrix.sdk }}
+      - id: install
+        name: Install dependencies
+        run: dart pub get
+      - name: Check formatting
+        run: dart format --output=none --set-exit-if-changed .
+        if: always() && steps.install.outcome == 'success'
+      - name: Analyze code
+        run: dart analyze --fatal-infos
+        if: always() && steps.install.outcome == 'success'
+
+  # Run tests on a matrix consisting of two dimensions:
+  # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+  # 2. release channel: dev
+  test:
+    needs: analyze
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        # Add macos-latest and/or windows-latest if relevant for this package.
+        os: [ubuntu-latest]
+        sdk: [2.12.0, dev]
+    steps:
+      - uses: actions/checkout@v3
+      - uses: dart-lang/setup-dart@v1
+        with:
+          sdk: ${{ matrix.sdk }}
+      - id: install
+        name: Install dependencies
+        run: dart pub get
+      - name: Run VM tests
+        run: dart test --platform vm
+        if: always() && steps.install.outcome == 'success'
+      - name: Run Chrome tests
+        run: dart test --platform chrome
+        if: always() && steps.install.outcome == 'success'
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index e6a4be6..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-language: dart
-
-dart:
-  - dev
-
-jobs:
-  include:
-  - script: pub run --enable-experiment=non-nullable test
-  - script: pub run --enable-experiment=non-nullable test -p chrome
-  - dart_task: dartfmt
-  - dart_task:
-      dartanalyzer: --enable-experiment=non-nullable --fatal-infos --fatal-warnings .
-
-# Only building master means that we don't run two builds for each pull request.
-branches:
-  only: [master]
-
-cache:
-  directories:
-    - $HOME/.pub-cache
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fb80a73..3f2542a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,8 @@
-## 0.17.0-nullsafety
+## 0.17.1-dev
 
+* Add optional parameter to `NumberFormat.compact()` to provide custom pattern.
+
+## 0.17.0
  * Migrate to null safety.
  * Add `@pragma('vm:prefer-inline')` to `Intl` methods that already have
    `@pragma('dart2js:tryInline')`, for the same reason: to help omit message
diff --git a/LICENSE b/LICENSE
index ee99930..7efe25f 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,5 @@
-Copyright 2013, the Dart project authors. All rights reserved.
+Copyright 2013, the Dart project authors.
+
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are
 met:
@@ -9,7 +10,7 @@
       copyright notice, this list of conditions and the following
       disclaimer in the documentation and/or other materials provided
       with the distribution.
-    * Neither the name of Google Inc. nor the names of its
+    * Neither the name of Google LLC nor the names of its
       contributors may be used to endorse or promote products derived
       from this software without specific prior written permission.
 
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 4cce960..d18178b 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -1,7 +1,4 @@
 analyzer:
-  enable-experiment:
-  - non-nullable
-
   language:
     strict-raw-types: true
 
diff --git a/lib/date_time_patterns.dart b/lib/date_time_patterns.dart
index 162dc20..af734be 100644
--- a/lib/date_time_patterns.dart
+++ b/lib/date_time_patterns.dart
@@ -16,7 +16,7 @@
 /// Returns a Map from locale names to another Map that goes from skeletons
 /// to the locale-specific formatting patterns.
 /// Internal use only. Call initializeDateFormatting instead.
-Map<String, Map<String, String>> dateTimePatternMap() => const {
+Map<String, Map<String, String>> dateTimePatternMap() => {
       /// Extended set of localized date/time patterns for locale af.
       'af': const {
         'd': 'd', // DAY
diff --git a/lib/message_format.dart b/lib/message_format.dart
index 45b47ef..7b31ac9 100644
--- a/lib/message_format.dart
+++ b/lib/message_format.dart
@@ -76,7 +76,7 @@
 /// produce `"I see Mark and one other person in Athens."` as output.
 ///
 /// Calling `format({'NUM_PEOPLE': 5, 'WHO': 'Mark', 'PLACE': 'Athens'})` would
-/// produce `"I see Mark and one 4 other people in Athens."` as output.
+/// produce `"I see Mark and 4 other people in Athens."` as output.
 /// Notice how the "#" is the value of `NUM_PEOPLE` - 1 (the offset).
 ///
 /// Another important thing to notice is the existence of both `"=1"` and
diff --git a/lib/src/intl/number_format.dart b/lib/src/intl/number_format.dart
index 9a3a8f2..85c1b05 100644
--- a/lib/src/intl/number_format.dart
+++ b/lib/src/intl/number_format.dart
@@ -364,18 +364,24 @@
 
   /// A number format for compact representations, e.g. "1.2M" instead
   /// of "1,200,000".
-  factory NumberFormat.compact({String? locale}) {
+  factory NumberFormat.compact({String? locale, String? pattern}) {
     return _CompactNumberFormat(
         locale: locale,
-        formatType: _CompactFormatType.COMPACT_DECIMAL_SHORT_PATTERN);
+        formatType: _CompactFormatType.COMPACT_DECIMAL_SHORT_PATTERN,
+        getPattern: pattern != null
+            ? (x) => pattern
+            : _CompactNumberFormat._forDecimal);
   }
 
   /// A number format for "long" compact representations, e.g. "1.2 million"
-  /// instead of of "1,200,000".
-  factory NumberFormat.compactLong({String? locale}) {
+  /// instead of "1,200,000".
+  factory NumberFormat.compactLong({String? locale, String? pattern}) {
     return _CompactNumberFormat(
         locale: locale,
-        formatType: _CompactFormatType.COMPACT_DECIMAL_LONG_PATTERN);
+        formatType: _CompactFormatType.COMPACT_DECIMAL_LONG_PATTERN,
+        getPattern: pattern != null
+            ? (x) => pattern
+            : _CompactNumberFormat._forDecimal);
   }
 
   /// A number format for compact currency representations, e.g. "$1.2M" instead
diff --git a/pubspec.yaml b/pubspec.yaml
index 98c064f..870f832 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: intl
-version: 0.17.0-nullsafety
+version: 0.17.1-dev
 homepage: https://github.com/dart-lang/intl
 description: >-
   Contains code to deal with internationalized/localized messages, date and
@@ -7,23 +7,14 @@
   internationalization issues.
 
 environment:
-  # This must remain a tight constraint until nnbd is stable
-  sdk: '>=2.11.0-180.0.dev <2.11.0'
+  sdk: '>=2.12.0-0 <3.0.0'
 
 dependencies:
-  clock: ^1.1.0-nullsafety.1
-  path: ^1.8.0-nullsafety.1
+  clock: ^1.1.0
+  path: ^1.8.0
 
 dev_dependencies:
-  ffi: any
-  fixnum: '>=0.9.0 <0.11.0'
-  js: ^0.6.3-nullsafety
-  test: ^1.16.0-nullsafety.4
-
-dependency_overrides:
-  ffi:
-    git:
-      url: https://github.com/dart-lang/ffi
-      ref: null_safety
-  fixnum:
-    git: https://github.com/dart-lang/fixnum
+  ffi: ^1.0.0
+  fixnum: ^1.0.0
+  js: ^0.6.3
+  test: ^1.16.0
diff --git a/test/date_time_format_custom_test.dart b/test/date_time_format_custom_test.dart
index 4dc42e2..f29f368 100644
--- a/test/date_time_format_custom_test.dart
+++ b/test/date_time_format_custom_test.dart
@@ -4,10 +4,9 @@
 
 /// Test date formatting and parsing using custom locale data, which we get
 /// from the local copy.
-
+import 'package:intl/date_symbol_data_custom.dart';
 import 'package:intl/date_symbol_data_local.dart' as local_symbols;
 import 'package:intl/date_time_patterns.dart' as local_patterns;
-import 'package:intl/date_symbol_data_custom.dart';
 
 import 'date_time_format_test_stub.dart';
 
diff --git a/test/date_time_format_test_stub.dart b/test/date_time_format_test_stub.dart
index a1d5b63..ea4d967 100644
--- a/test/date_time_format_test_stub.dart
+++ b/test/date_time_format_test_stub.dart
@@ -7,8 +7,9 @@
 
 library date_time_format_test;
 
-import 'package:test/test.dart';
 import 'package:intl/intl.dart';
+import 'package:test/test.dart';
+
 import 'date_time_format_test_core.dart';
 
 typedef TestListFunc = List<String> Function();
diff --git a/test/fixnum_test.dart b/test/fixnum_test.dart
index 8f9f874..a5c1a84 100644
--- a/test/fixnum_test.dart
+++ b/test/fixnum_test.dart
@@ -4,9 +4,9 @@
 
 library fixnum_test;
 
+import 'package:fixnum/fixnum.dart';
 import 'package:intl/intl.dart';
 import 'package:test/test.dart';
-import 'package:fixnum/fixnum.dart';
 
 var int64Values = {
   Int64(12345): ['USD12,345.00', '1,234,500%'],
diff --git a/test/intl_test.dart b/test/intl_test.dart
index 77f10c5..a780abe 100644
--- a/test/intl_test.dart
+++ b/test/intl_test.dart
@@ -4,9 +4,9 @@
 
 library intl_test;
 
+import 'package:intl/date_symbol_data_local.dart';
 import 'package:intl/intl.dart';
 import 'package:test/test.dart';
-import 'package:intl/date_symbol_data_local.dart';
 
 void main() {
   test("Locale setting doesn't verify the core locale", () {
diff --git a/test/locale_test.dart b/test/locale_test.dart
index 59d75af..58c4e1b 100644
--- a/test/locale_test.dart
+++ b/test/locale_test.dart
@@ -9,9 +9,8 @@
 ///
 /// For production code, use of ICU would influence what needs and doesn't need
 /// to be tested.
-
-import 'package:test/test.dart';
 import 'package:intl/locale.dart';
+import 'package:test/test.dart';
 
 import 'locale_test_data.dart';
 
diff --git a/test/number_format_compact_icu_test.dart b/test/number_format_compact_icu_test.dart
index 250b580..5cb1255 100644
--- a/test/number_format_compact_icu_test.dart
+++ b/test/number_format_compact_icu_test.dart
@@ -16,58 +16,62 @@
 import 'more_compact_number_test_data.dart' as more_testdata;
 
 main() {
-  var problemLocales = [
+  var problemLocales = {
     // ICU produces numerals in Arabic script, package:intl uses Latin script.
     'ar',
     // package:intl includes some tweaks to compact numbers for Bengali.
     'bn',
-  ];
-
+  };
   runICUTests(systemIcuVersion: 63, skipLocales: problemLocales);
 }
 
 void runICUTests(
-    {int? systemIcuVersion, String? specialIcuLib, List<String>? skipLocales}) {
-  if (!setupICU(
+    {int? systemIcuVersion,
+    String? specialIcuLib,
+    Set<String> skipLocales = const {}}) {
+  if (!_setupICU(
       systemIcuVersion: systemIcuVersion, specialIcuLibPath: specialIcuLib)) {
     return;
   }
 
-  print("Skipping problem locales $skipLocales.");
-  testdata35.compactNumberTestData
-      .removeWhere((k, v) => skipLocales!.contains(k));
+  void validate(String locale, List<List<String>> expected) {
+    _validateShort(locale, expected, skipLocales);
+    _validateLong(locale, expected, skipLocales);
+  }
+
   testdata35.compactNumberTestData.forEach(validate);
-  more_testdata.cldr35CompactNumTests.forEach(validateFancy);
+  more_testdata.cldr35CompactNumTests.forEach(_validateFancy);
 
   test('UNumberFormatter simple integer formatting', () {
-    expect(FormatWithUnumf('en', 'precision-integer', 5142.3), '5,142');
+    expect(_formatWithUnumf('en', 'precision-integer', 5142.3), '5,142');
   });
 }
 
-void validate(String locale, List<List<String>> expected) {
-  validateShort(locale, expected);
-  validateLong(locale, expected);
-}
-
-void validateShort(String locale, List<List<String>> expected) {
+void _validateShort(
+    String locale, List<List<String>> expected, Set<String> skipLocales) {
+  var skip =
+      skipLocales.contains(locale) ? 'Skipping problem locale $locale' : false;
   test('Validate $locale SHORT', () {
     for (var data in expected) {
       var number = num.parse(data.first);
-      expect(FormatWithUnumf(locale, 'compact-short', number), data[1]);
+      expect(_formatWithUnumf(locale, 'compact-short', number), data[1]);
     }
-  });
+  }, skip: skip);
 }
 
-void validateLong(String locale, List<List<String>> expected) {
+void _validateLong(
+    String locale, List<List<String>> expected, Set<String> skipLocales) {
+  var skip =
+      skipLocales.contains(locale) ? 'Skipping problem locale $locale' : false;
   test('Validate $locale LONG', () {
     for (var data in expected) {
       var number = num.parse(data.first);
-      expect(FormatWithUnumf(locale, 'compact-long', number), data[2]);
+      expect(_formatWithUnumf(locale, 'compact-long', number), data[2]);
     }
-  });
+  }, skip: skip);
 }
 
-void validateFancy(more_testdata.CompactRoundingTestCase t) {
+void _validateFancy(more_testdata.CompactRoundingTestCase t) {
   var locale = 'en';
   var skel = 'compact-short';
   if (t.minimumIntegerDigits != null) {
@@ -84,7 +88,7 @@
     skel += ' .' + '#' * t.maximumFractionDigits!;
   }
   test(t.toString(), () {
-    expect(FormatWithUnumf(locale, skel, t.number), t.expected,
+    expect(_formatWithUnumf(locale, skel, t.number), t.expected,
         reason: 'Skeleton: $skel');
   });
 }
@@ -106,7 +110,7 @@
 ///
 /// If [systemIcuVersion] is unspecified, we expect to find all functions in a
 /// library with filename [specialIcuLibPath].
-bool setupICU({int? systemIcuVersion, String? specialIcuLibPath}) {
+bool _setupICU({int? systemIcuVersion, String? specialIcuLibPath}) {
   DynamicLibrary libicui18n;
   String icuVersionSuffix;
   if (systemIcuVersion != null) {
@@ -152,21 +156,21 @@
   return true;
 }
 
-String FormatWithUnumf(String locale, String skeleton, num number) {
+String _formatWithUnumf(String locale, String skeleton, num number) {
   // // Setup:
   // UErrorCode ec = U_ZERO_ERROR;
   // UNumberFormatter* uformatter =
   //     unumf_openForSkeletonAndLocale(u"precision-integer", -1, "en", &ec);
   // UFormattedNumber* uresult = unumf_openResult(&ec);
   // if (U_FAILURE(ec)) { return; }
-  final cLocale = Utf8.toUtf8(locale);
-  final cSkeleton = Utf16.toUtf16(skeleton);
-  final cErrorCode = allocate<Int32>(count: 1);
+  final cLocale = locale.toNativeUtf8();
+  final cSkeleton = skeleton.toNativeUtf16();
+  final cErrorCode = calloc<Int32>();
   cErrorCode.value = 0;
   final uformatter =
       unumf_openForSkeletonAndLocale!(cSkeleton, -1, cLocale, cErrorCode);
-  free(cSkeleton);
-  free(cLocale);
+  calloc.free(cSkeleton);
+  calloc.free(cLocale);
   var errorCode = cErrorCode.value;
   expect(errorCode, lessThanOrEqualTo(0),
       reason: u_errorName!(errorCode).toString());
@@ -201,7 +205,7 @@
   expect(errorCode, equals(15), // U_BUFFER_OVERFLOW_ERROR
       reason: u_errorName!(errorCode).toString());
   cErrorCode.value = 0;
-  final buffer = allocate<Utf16>(count: reqLen + 1);
+  final buffer = calloc<Uint16>(reqLen + 1).cast<Utf16>();
   unumf_resultToString!(uresult, buffer, reqLen + 1, cErrorCode);
   errorCode = cErrorCode.value;
   expect(errorCode, lessThanOrEqualTo(0),
@@ -211,11 +215,11 @@
   // // Cleanup:
   // unumf_close(uformatter);
   // unumf_closeResult(uresult);
-  // free(buffer);
+  // calloc.free(buffer);
   unumf_close!(uformatter);
   unumf_closeResult!(uresult);
-  free(buffer);
-  free(cErrorCode);
+  calloc.free(buffer);
+  calloc.free(cErrorCode);
 
   return result;
 }
@@ -229,10 +233,10 @@
 typedef UErrorNameOp = Pointer<Utf8> Function(int code);
 
 /// [UNumberFormatter](http://icu-project.org/apiref/icu4c/unumberformatter_8h.html#a7c1238b2dd08f32f1ea245ece41e71bd)
-class UNumberFormatter extends Struct {}
+class UNumberFormatter extends Opaque {}
 
 /// [UFormattedNumber](http://icu-project.org/apiref/icu4c/unumberformatter_8h.html#a9d4030bdc4dd1ec4de828bf1bcf4b1b6)
-class UFormattedNumber extends Struct {}
+class UFormattedNumber extends Opaque {}
 
 /// C signature for
 /// [unumf_openForSkeletonAndLocale()](http://icu-project.org/apiref/icu4c/unumberformatter_8h.html#a29339e144833880bda36fb7c17032698)
diff --git a/test/number_format_compact_test.dart b/test/number_format_compact_test.dart
index 0103c97..1545815 100644
--- a/test/number_format_compact_test.dart
+++ b/test/number_format_compact_test.dart
@@ -4,10 +4,13 @@
 
 /// Tests for compact format numbers, e.g. 1.2M rather than 1,200,000
 import 'dart:math';
-import 'package:test/test.dart';
-import 'package:intl/intl.dart';
+
 import 'package:fixnum/fixnum.dart';
+import 'package:intl/intl.dart';
+import 'package:intl/number_symbols_data.dart';
 import 'package:intl/number_symbols_data.dart' as patterns;
+import 'package:test/test.dart';
+
 import 'compact_number_test_data_33.dart' as testdata33;
 // End-goal: to stop testing against testdata33 and use testdata35 instead:
 // import 'compact_number_test_data.dart' as testdata35;
@@ -19,13 +22,43 @@
 //  'mn' : [['4321', '4.32M', 'whatever']]
 };
 
+var compactWithPatternCases = <List<String>>[
+  ['0.012', '+0.01', '+0.01'],
+  ['0.123', '+0.12', '+0.12'],
+  ['1.234', '+1.23', '+1.23'],
+  ['12.34', '+12.3', '+12.3'],
+  ['123.4', '+123', '+123'],
+  ['123.41', '+123', '+123'],
+  ['1234.1', '+1.23K', '+1.23 thousand'],
+  ['12341', '+12.3K', '+12.3 thousand'],
+  ['123412', '+123K', '+123 thousand'],
+  ['1234123', '+1.23M', '+1.23 million'],
+  ['12341234', '+12.3M', '+12.3 million'],
+  ['123412341', '+123M', '+123 million'],
+  ['1234123412', '+1.23B', '+1.23 billion'],
+  ['-0.012', '-0.01', '-0.01'],
+  ['-0.123', '-0.12', '-0.12'],
+  ['-1.234', '-1.23', '-1.23'],
+  ['-12.34', '-12.3', '-12.3'],
+  ['-123.4', '-123', '-123'],
+  ['-123.41', '-123', '-123'],
+  ['-1234.1', '-1.23K', '-1.23 thousand'],
+  ['-12341', '-12.3K', '-12.3 thousand'],
+  ['-123412', '-123K', '-123 thousand'],
+  ['-1234123', '-1.23M', '-1.23 million'],
+  ['-12341234', '-12.3M', '-12.3 million'],
+  ['-123412341', '-123M', '-123 million'],
+  ['-1234123412', '-1.23B', '-1.23 billion'],
+];
+
 void main() {
-  interestingCases.forEach(validate);
-  testdata33.compactNumberTestData.forEach(validate);
-  more_testdata.oldIntlCompactNumTests.forEach(validateFancy);
+  interestingCases.forEach(_validate);
+  testdata33.compactNumberTestData.forEach(_validate);
+  more_testdata.oldIntlCompactNumTests.forEach(_validateFancy);
   // Once code and data is updated to CLDR35:
   // testdata35.compactNumberTestData.forEach(validate);
   // more_testdata.cldr35CompactNumTests.forEach(validateFancy);
+  compactWithPatternCases.forEach(_validateWithPattern);
 
   test("Patterns are consistent across locales", () {
     patterns.compactNumberSymbols.forEach((locale, patterns) {
@@ -139,7 +172,7 @@
 // case.
 // TODO(alanknight): Fix the problems, or at least figure out precisely where
 // the differences are.
-var problemLocalesShort = [
+var _skipLocalsShort = {
   'am', // AM Suffixes differ, not sure why.
   'ca', // For CA, CLDR rules are different. Jumps from 0000 to 00 prefix, so
   // 11 digits prints as 11900.
@@ -167,7 +200,7 @@
   'tr', // TR Doesn't have a 0B format, goes directly to 00B, as a result 54321
   // just prints as 54321
   'ur', // UR Fails one with Expected: '15 ٹریلین'  Actual: '150 کھرب'
-];
+};
 
 /// Locales that have problems in the long format.
 ///
@@ -178,7 +211,7 @@
 ///
 //TODO(alanknight): Narrow these down to particular numbers. Often it's just
 // 999999.
-var problemLocalesLong = [
+var _skipLocalesLong = {
   'ar', 'ar_DZ', 'ar_EG',
   'be', 'bg', 'bs',
   'ca', 'cs', 'da', 'de', 'de_AT', 'de_CH', 'el', 'es', 'es_419', 'es_ES',
@@ -200,55 +233,53 @@
   'sk', 'sl', 'sr', 'sr_Latn', 'sv', 'te', 'tl',
   'ur',
   'uk',
-];
+};
 
-void validate(String locale, List<List<String>> expected) {
-  validateShort(locale, expected);
-  validateLong(locale, expected);
+void _validate(String locale, List<List<String>> expected) {
+  _validateShort(locale, expected);
+  _validateLong(locale, expected);
 }
 
 /// Check each bit of test data against the short compact format, both
 /// formatting and parsing.
-void validateShort(String locale, List<List<String>> expected) {
-  if (problemLocalesShort.contains(locale)) {
-    print("Skipping problem locale '$locale' for SHORT compact number tests");
-    return;
-  }
+void _validateShort(String locale, List<List<String>> expected) {
+  var skip = _skipLocalsShort.contains(locale)
+      ? "Skipping problem locale '$locale' for SHORT compact number tests"
+      : false;
   var shortFormat = NumberFormat.compact(locale: locale);
   test('Validate $locale SHORT', () {
     for (var data in expected) {
       var number = num.parse(data.first);
-      validateNumber(number, shortFormat, data[1]);
+      _validateNumber(number, shortFormat, data[1]);
       var int64Number = Int64(number as int);
-      validateNumber(int64Number, shortFormat, data[1]);
+      _validateNumber(int64Number, shortFormat, data[1]);
       // TODO(alanknight): Make this work for MicroMoney
     }
-  });
+  }, skip: skip);
 }
 
-void validateLong(String locale, List<List<String>> expected) {
-  if (problemLocalesLong.contains(locale)) {
-    print("Skipping problem locale '$locale' for LONG compact number tests");
-    return;
-  }
+void _validateLong(String locale, List<List<String>> expected) {
+  var skip = _skipLocalesLong.contains(locale)
+      ? "Skipping problem locale '$locale' for LONG compact number tests"
+      : false;
   var longFormat = NumberFormat.compactLong(locale: locale);
   test('Validate $locale LONG', () {
     for (var data in expected) {
       var number = num.parse(data.first);
-      validateNumber(number, longFormat, data[2]);
+      _validateNumber(number, longFormat, data[2]);
     }
-  });
+  }, skip: skip);
 }
 
-void validateNumber(number, NumberFormat format, String expected) {
+void _validateNumber(number, NumberFormat format, String expected) {
   var formatted = format.format(number);
-  var ok = closeEnough(formatted, expected);
+  var ok = _closeEnough(formatted, expected);
   if (!ok) {
     expect(
         '$formatted ${formatted.codeUnits}', '$expected ${expected.codeUnits}');
   }
   var parsed = format.parse(formatted);
-  var rounded = roundForPrinting(number, format);
+  var rounded = _roundForPrinting(number, format);
   expect((parsed - rounded) / rounded < 0.001, isTrue);
 }
 
@@ -256,7 +287,7 @@
 /// number that will round to print differently depending on the number
 /// of significant digits, we need to check that as well, e.g.
 /// 999999 may print as 1M.
-num roundForPrinting(number, NumberFormat format) {
+num _roundForPrinting(number, NumberFormat format) {
   var originalLength = NumberFormat.numberOfIntegerDigits(number);
   var additionalDigits = originalLength - format.significantDigits!;
   if (additionalDigits > 0) {
@@ -278,7 +309,7 @@
 /// currently producing and the CLDR data. So if the strings differ only in the
 /// presence or absence of a period at the end or of a space between the number
 /// and the suffix, consider it close enough and return true.
-bool closeEnough(String result, String reference) {
+bool _closeEnough(String result, String reference) {
   var expected = reference.replaceAll(' ', _nbspString);
   if (result == expected) {
     return true;
@@ -313,7 +344,7 @@
       expectedDifference <= 1;
 }
 
-void validateFancy(more_testdata.CompactRoundingTestCase t) {
+void _validateFancy(more_testdata.CompactRoundingTestCase t) {
   var shortFormat = NumberFormat.compact(locale: 'en');
   if (t.maximumIntegerDigits != null) {
     shortFormat.maximumIntegerDigits = t.maximumIntegerDigits!;
@@ -343,3 +374,21 @@
     expect(shortFormat.format(t.number), t.expected);
   });
 }
+
+void _validateWithPattern(List<String> testData) {
+  final locale = 'en_US';
+  final currentLocale = Intl.verifiedLocale(locale, NumberFormat.localeExists);
+  final symbols = numberFormatSymbols[currentLocale];
+  final pattern = '${symbols.PLUS_SIGN}${symbols.DECIMAL_PATTERN};'
+      '${symbols.MINUS_SIGN}${symbols.DECIMAL_PATTERN}';
+  final input = num.parse(testData[0]);
+  test('Validate compact with $locale and $pattern', () {
+    final numberFormat = NumberFormat.compact(locale: locale, pattern: pattern);
+    expect(testData[1], numberFormat.format(input));
+  });
+  test('Validate compactLong with $locale and $pattern', () {
+    final numberFormat =
+        NumberFormat.compactLong(locale: locale, pattern: pattern);
+    expect(testData[2], numberFormat.format(input));
+  });
+}
diff --git a/test/number_format_compact_web_test.dart b/test/number_format_compact_web_test.dart
index 8738a14..b211f56 100644
--- a/test/number_format_compact_web_test.dart
+++ b/test/number_format_compact_web_test.dart
@@ -13,15 +13,15 @@
 import 'more_compact_number_test_data.dart' as more_testdata;
 
 void main() {
-  testdata35.compactNumberTestData.forEach(validate);
-  more_testdata.cldr35CompactNumTests.forEach(validateMore);
+  testdata35.compactNumberTestData.forEach(_validate);
+  more_testdata.cldr35CompactNumTests.forEach(_validateMore);
 
   test('RTL currency formatting', () {
     var basic = intl.NumberFormat.currency(locale: 'he');
     expect(basic.format(1234), '\u200F1,234.00 ILS');
     basic = intl.NumberFormat.currency(locale: 'he', symbol: '₪');
     expect(basic.format(1234), '\u200F1,234.00 ₪');
-    expect(ecmaFormatNumber('he', 1234, style: 'currency', currency: 'ILS'),
+    expect(_ecmaFormatNumber('he', 1234, style: 'currency', currency: 'ILS'),
         '\u200F1,234.00 ₪');
 
     var compact = intl.NumberFormat.compactCurrency(locale: 'he');
@@ -32,19 +32,19 @@
     expect(compact.format(1234), '₪ \u200F1.23K');
     // ECMAScript skips the RTL character for notation:'compact':
     expect(
-        ecmaFormatNumber('he', 1234,
+        _ecmaFormatNumber('he', 1234,
             style: 'currency', currency: 'ILS', notation: 'compact'),
         '₪ 1.2K');
     // short/long compactDisplay doesn't change anything here:
     expect(
-        ecmaFormatNumber('he', 1234,
+        _ecmaFormatNumber('he', 1234,
             style: 'currency',
             currency: 'ILS',
             notation: 'compact',
             compactDisplay: 'short'),
         '₪ 1.2K');
     expect(
-        ecmaFormatNumber('he', 1234,
+        _ecmaFormatNumber('he', 1234,
             style: 'currency',
             currency: 'ILS',
             notation: 'compact',
@@ -56,7 +56,7 @@
   });
 }
 
-String ecmaFormatNumber(String locale, num number,
+String _ecmaFormatNumber(String locale, num number,
     {String? style,
     String? currency,
     String? notation,
@@ -71,60 +71,59 @@
   return js.callMethod(number, 'toLocaleString', [locale, options]);
 }
 
-var ecmaProblemLocalesShort = [
+var _unsupportedChromeLocales = {
   // Not supported in Chrome:
   'af', 'az', 'be', 'br', 'bs', 'eu', 'ga', 'gl', 'gsw', 'haw', 'hy', 'is',
   'ka', 'kk', 'km', 'ky', 'ln', 'lo', 'mk', 'mn', 'mt', 'my', 'ne', 'no',
-  'no-NO', 'or', 'pa', 'si', 'sq', 'ur', 'uz', 'ps',
+  'no-NO', 'or', 'pa', 'si', 'sq', 'ur', 'uz', 'ps', 'chr', 'cy', 'tl', 'zu'
+};
+
+var _skipLocalesShort = [
+  'am', 'bn', 'fa', // Some results change in chrome 88
+  ..._unsupportedChromeLocales
 ];
 
-var ecmaProblemLocalesLong = ecmaProblemLocalesShort +
-    [
-      // Short happens to match 'en', but actually not in Chrome:
-      'chr', 'cy', 'tl', 'zu'
-    ];
+var _skipLocalesLong = _unsupportedChromeLocales;
 
-String fixLocale(String locale) {
+String _fixLocale(String locale) {
   return locale.replaceAll('_', '-');
 }
 
-void validate(String locale, List<List<String>> expected) {
-  validateShort(fixLocale(locale), expected);
-  validateLong(fixLocale(locale), expected);
+void _validate(String locale, List<List<String>> expected) {
+  _validateShort(_fixLocale(locale), expected);
+  _validateLong(_fixLocale(locale), expected);
 }
 
-void validateShort(String locale, List<List<String>> expected) {
-  if (ecmaProblemLocalesShort.contains(locale)) {
-    print("Skipping problem locale '$locale' for SHORT compact number tests");
-    return;
-  }
+void _validateShort(String locale, List<List<String>> expected) {
+  var skip = _skipLocalesShort.contains(locale)
+      ? "Skipping problem locale '$locale' for SHORT compact number tests"
+      : false;
 
   test('Validate $locale SHORT', () {
     for (var data in expected) {
       var number = num.parse(data.first);
-      expect(ecmaFormatNumber(locale, number, notation: 'compact'), data[1]);
+      expect(_ecmaFormatNumber(locale, number, notation: 'compact'), data[1]);
     }
-  });
+  }, skip: skip);
 }
 
-void validateLong(String locale, List<List<String>> expected) {
-  if (ecmaProblemLocalesLong.contains(locale)) {
-    print("Skipping problem locale '$locale' for LONG compact number tests");
-    return;
-  }
+void _validateLong(String locale, List<List<String>> expected) {
+  var skip = _skipLocalesLong.contains(locale)
+      ? "Skipping problem locale '$locale' for LONG compact number tests"
+      : false;
 
   test('Validate $locale LONG', () {
     for (var data in expected) {
       var number = num.parse(data.first);
       expect(
-          ecmaFormatNumber(locale, number,
+          _ecmaFormatNumber(locale, number,
               notation: 'compact', compactDisplay: 'long'),
           data[2]);
     }
-  });
+  }, skip: skip);
 }
 
-void validateMore(more_testdata.CompactRoundingTestCase t) {
+void _validateMore(more_testdata.CompactRoundingTestCase t) {
   var options = js.newObject();
   js.setProperty(options, 'notation', 'compact');
   if (t.maximumIntegerDigits != null) {
diff --git a/test/number_format_test_core.dart b/test/number_format_test_core.dart
index 2c74137..ea79d03 100644
--- a/test/number_format_test_core.dart
+++ b/test/number_format_test_core.dart
@@ -4,9 +4,10 @@
 
 library number_format_test;
 
-import 'package:test/test.dart';
-import 'package:intl/number_symbols_data.dart';
 import 'package:intl/intl.dart';
+import 'package:intl/number_symbols_data.dart';
+import 'package:test/test.dart';
+
 import 'number_test_data.dart';
 
 /// Tests the Numeric formatting library in dart.