Repository: flutter/packages
Pull request #8999: [gis_web] Make maybeEnum more robust in google_identity_services_web shared file.
Author: login: vikdubey
Labels: name: autosubmit
description: Merge PR when tree becomes green via auto submit App
name: platform-web
name: triage-web
description: Should be looked at in web triage
Created: 2025-04-04T12:46:53Z | Merged: 2025-04-22T17:51:21Z

When the needle value is unexpected, instead of throwing error, it'll return null now.

Fixes https://github.com/flutter/flutter/issues/166548

## Pre-Review Checklist

- [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs.
- [x] I read the [Tree Hygiene] page, which explains my responsibilities.
- [x] I read and followed the [relevant style guides] and ran [the auto-formatter].
- [x] I signed the [CLA].
- [x] The title of the PR starts with the name of the package surrounded by square brackets, e.g. `[shared_preferences]`
- [x] I [linked to at least one issue that this PR fixes] in the description above.
- [x] I updated `pubspec.yaml` with an appropriate new version according to the [pub versioning philosophy], or I have commented below to indicate which [version change exemption] this PR falls under[^1].
- [x] I updated `CHANGELOG.md` to add a description of the change, [following repository CHANGELOG style], or I have commented below to indicate which [CHANGELOG exemption] this PR falls under[^1].
- [x] I updated/added any relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or I have commented below to indicate which [test exemption] this PR falls under[^1].
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on [Discord].

[^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.

<!-- Links -->
[Contributor Guide]: https://github.com/flutter/packages/blob/main/CONTRIBUTING.md
[Tree Hygiene]: https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md
[relevant style guides]: https://github.com/flutter/packages/blob/main/CONTRIBUTING.md#style
[the auto-formatter]: https://github.com/flutter/packages/blob/main/script/tool/README.md#format-code
[CLA]: https://cla.developers.google.com/
[Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md
[linked to at least one issue that this PR fixes]: https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#overview
[pub versioning philosophy]: https://dart.dev/tools/pub/versioning
[version change exemption]: https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#version
[following repository CHANGELOG style]: https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changelog-style
[CHANGELOG exemption]: https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changelog
[test exemption]: https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#tests



Diff:
diff --git a/packages/google_identity_services_web/CHANGELOG.md b/packages/google_identity_services_web/CHANGELOG.md
index 74323f88c30e..1f8f8b4c2c79 100644
--- a/packages/google_identity_services_web/CHANGELOG.md
+++ b/packages/google_identity_services_web/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.3.3+1
+
+* Handles potential exceptions gracefully while fetching `Moment*Reason` for invalid value.
+
 ## 0.3.3
 
 * Moves all the JavaScript types to extend `JSObject`.
diff --git a/packages/google_identity_services_web/README.md b/packages/google_identity_services_web/README.md
index 733644af5ba0..f82eeb9b372a 100644
--- a/packages/google_identity_services_web/README.md
+++ b/packages/google_identity_services_web/README.md
@@ -93,10 +93,3 @@ Refer to the official documentation site for the latest browser compatibility
 information of the underlying JS SDK:
 
 * **Sign In With Google > [Supported browsers and platforms](https://developers.google.com/identity/gsi/web/guides/supported-browsers)**
-
-## Testing
-
-This web-only package uses `dart:test` to test its features. They can be run
-with `dart test -p chrome`.
-
-_(Look at `test/README.md` and `tool/run_tests.dart` for more info.)_
diff --git a/packages/google_identity_services_web/example/integration_test/js_interop_id_test.dart b/packages/google_identity_services_web/example/integration_test/js_interop_id_test.dart
index 6c5da4bcd149..c49ed40cf3c0 100644
--- a/packages/google_identity_services_web/example/integration_test/js_interop_id_test.dart
+++ b/packages/google_identity_services_web/example/integration_test/js_interop_id_test.dart
@@ -83,8 +83,11 @@ void main() async {
   });
 
   group('prompt', () {
-    testWidgets('supports a moment notification callback', (_) async {
+    testWidgets(
+        'supports a moment notification callback with correct type and reason',
+        (_) async {
       id.initialize(IdConfiguration(client_id: 'testing_1-2-3'));
+      utils.setMockMomentNotification('skipped', 'user_cancel');
 
       final StreamController<PromptMomentNotification> controller =
           StreamController<PromptMomentNotification>();
@@ -93,11 +96,27 @@ void main() async {
 
       final PromptMomentNotification moment = await controller.stream.first;
 
-      // These defaults are set in mock-gis.js
       expect(moment.getMomentType(), MomentType.skipped);
       expect(moment.getSkippedReason(), MomentSkippedReason.user_cancel);
     });
 
+    testWidgets(
+        'supports a moment notification callback while handling invalid reason '
+        'value gracefully', (_) async {
+      id.initialize(IdConfiguration(client_id: 'testing_1-2-3'));
+      utils.setMockMomentNotification('skipped', 'random_invalid_reason');
+
+      final StreamController<PromptMomentNotification> controller =
+          StreamController<PromptMomentNotification>();
+
+      id.prompt(controller.add);
+
+      final PromptMomentNotification moment = await controller.stream.first;
+
+      expect(moment.getMomentType(), MomentType.skipped);
+      expect(moment.getSkippedReason(), isNull);
+    });
+
     testWidgets('calls config callback with credential response', (_) async {
       const String expected = 'should_be_a_proper_jwt_token';
       utils.setMockCredentialResponse(expected);
diff --git a/packages/google_identity_services_web/example/integration_test/utils.dart b/packages/google_identity_services_web/example/integration_test/utils.dart
index a75d2d9e9ee8..a27cce2f4059 100644
--- a/packages/google_identity_services_web/example/integration_test/utils.dart
+++ b/packages/google_identity_services_web/example/integration_test/utils.dart
@@ -90,12 +90,18 @@ void setMockTokenResponse(TokenClient client, [String? authToken]) {
   client.setMockTokenResponse(authToken?.toJS);
 }
 
-/// Allows calling a `setMockCredentialResponse` method (set by mock-gis.js)
+/// Allows calling `setMockCredentialResponse` and `setMockMomentNotification`
+/// methods (set by mock-gis.js).
 extension on GoogleAccountsId {
   external void setMockCredentialResponse(
     JSString credential,
     JSString select_by, //ignore: non_constant_identifier_names
   );
+
+  external void setMockMomentNotification(
+    JSString momentType,
+    JSString reason,
+  );
 }
 
 /// Sets a mock credential response in `google.accounts.id`.
@@ -103,6 +109,12 @@ void setMockCredentialResponse([String value = 'default_value']) {
   _getGoogleAccountsId().setMockCredentialResponse(value.toJS, 'auto'.toJS);
 }
 
+/// Sets a mock moment notification in `google.accounts.id`.
+void setMockMomentNotification(String momentType, String reason) {
+  _getGoogleAccountsId()
+      .setMockMomentNotification(momentType.toJS, reason.toJS);
+}
+
 GoogleAccountsId _getGoogleAccountsId() {
   return _getDeepProperty<GoogleAccountsId>(
       web.window as JSObject, 'google.accounts.id');
diff --git a/packages/google_identity_services_web/example/web/mock-gis.js b/packages/google_identity_services_web/example/web/mock-gis.js
index 5921c5eb89f6..e99ac6347eec 100644
--- a/packages/google_identity_services_web/example/web/mock-gis.js
+++ b/packages/google_identity_services_web/example/web/mock-gis.js
@@ -22,9 +22,6 @@ class PromptMomentNotification {
   isNotDisplayed() { return this.isDisplayMoment() && this.reason; }
 }
 
-const CREDENTIAL_RETURNED = new PromptMomentNotification("dismissed", "credential_returned");
-const USER_CANCEL = new PromptMomentNotification("skipped", "user_cancel");
-
 function callAsync(func, timeout = 100) {
   window.setTimeout(func, timeout)
 }
@@ -47,11 +44,11 @@ class Id {
         if (callback) {
           callback(this.mockCredentialResponse);
         }
-        if (momentListener) {
-          momentListener(CREDENTIAL_RETURNED);
+      }
+      if (momentListener) {
+        if (this.mockMomentNotification) {
+          momentListener(this.mockMomentNotification);
         }
-      } else if (momentListener) {
-        momentListener(USER_CANCEL);
       }
     });
   }
@@ -61,11 +58,15 @@ class Id {
       select_by: select_by,
     };
   }
+  setMockMomentNotification(momentType, reason) {
+    this.mockMomentNotification = new PromptMomentNotification(momentType, reason);
+  }
   disableAutoSelect() {}
   storeCredential() {}
   cancel() {}
   revoke(hint, callback) {
     this.mockCredentialResponse = null;
+    this.mockMomentNotification = null;
     if (!callback) {
       return;
     }
diff --git a/packages/google_identity_services_web/lib/src/js_interop/shared.dart b/packages/google_identity_services_web/lib/src/js_interop/shared.dart
index a56450383261..ea762b457fb8 100644
--- a/packages/google_identity_services_web/lib/src/js_interop/shared.dart
+++ b/packages/google_identity_services_web/lib/src/js_interop/shared.dart
@@ -3,11 +3,18 @@
 // found in the LICENSE file.
 
 /// Attempts to retrieve an enum value from [haystack] if [needle] is not null.
+///
+/// Returns `null` if no enum value in [haystack] matches [needle].
 T? maybeEnum<T extends Enum>(String? needle, List<T> haystack) {
   if (needle == null) {
     return null;
   }
-  return haystack.byName(needle);
+  for (final T value in haystack) {
+    if (value.name == needle) {
+      return value;
+    }
+  }
+  return null;
 }
 
 /// The type of several functions from the library, that don't receive
diff --git a/packages/google_identity_services_web/pubspec.yaml b/packages/google_identity_services_web/pubspec.yaml
index cedb0e5b194b..4433fc6c6e07 100644
--- a/packages/google_identity_services_web/pubspec.yaml
+++ b/packages/google_identity_services_web/pubspec.yaml
@@ -2,7 +2,7 @@ name: google_identity_services_web
 description: A Dart JS-interop layer for Google Identity Services. Google's new sign-in SDK for Web that supports multiple types of credentials.
 repository: https://github.com/flutter/packages/tree/main/packages/google_identity_services_web
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_identiy_services_web%22
-version: 0.3.3
+version: 0.3.3+1
 
 environment:
   sdk: ^3.4.0
diff --git a/packages/google_identity_services_web/test/README.md b/packages/google_identity_services_web/test/README.md
index 38faf114033b..5166f0e24597 100644
--- a/packages/google_identity_services_web/test/README.md
+++ b/packages/google_identity_services_web/test/README.md
@@ -1,6 +1,6 @@
 # Tests
 
-Use `dart run tool/run_tests.dart` to run tests in this package.
+Use `dart test -p chrome <test_name>.dart` to run tests in this package.
 
 ## Failed to run Chrome: No such file or directory
 


Reviews:
user: login: stuartmorgan-g
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-16T13:26:29Z
user: login: vikdubey
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T06:06:20Z
user: login: vikdubey
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T06:07:00Z
user: login: stuartmorgan-g
body: LGTM, but @ditman or another web reviewer should provide the second approval.
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-17T17:12:33Z
user: login: ditman
body: LGTM, thanks for making this more robust @vikdubey!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-22T17:49:15Z

Review comments:
diff_hunk: @@ -1,3 +1,7 @@
+## 0.3.4
path: packages/google_identity_services_web/CHANGELOG.md
user: login: stuartmorgan-g
body: This can just be 0.3.3+1 since it's a minor internal change.
created_at: 2025-04-16T13:23:10Z
author_association: COLLABORATOR
diff_hunk: @@ -1,6 +1,6 @@
 # Tests
path: packages/google_identity_services_web/test/README.md
user: login: stuartmorgan-g
body: Thanks for fixing this! Let's also remove the `Testing` section from the main README since that doesn't belong in the client-facing README, and we shouldn't have two copies of instructions as they can get out of sync (as demonstrated by this one having become stale.).
created_at: 2025-04-16T13:26:23Z
author_association: COLLABORATOR
diff_hunk: @@ -1,3 +1,7 @@
+## 0.3.4
path: packages/google_identity_services_web/CHANGELOG.md
user: login: vikdubey
body: Makes sense, updated, thanks!
created_at: 2025-04-17T06:06:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,6 +1,6 @@
 # Tests
path: packages/google_identity_services_web/test/README.md
user: login: vikdubey
body: Updated, thanks!
created_at: 2025-04-17T06:07:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3,11 +3,18 @@
 // found in the LICENSE file.
 
 /// Attempts to retrieve an enum value from [haystack] if [needle] is not null.
+///
+/// Returns `null` if no enum value in [haystack] matches [needle].
 T? maybeEnum<T extends Enum>(String? needle, List<T> haystack) {
   if (needle == null) {
     return null;
   }
-  return haystack.byName(needle);
+  for (final T value in haystack) {
+    if (value.name == needle) {
+      return value;
+    }
+  }
path: packages/google_identity_services_web/lib/src/js_interop/shared.dart
user: login: ditman
body: A future improvement may be to log a warning or similar when we attempt to decode a value of T that will return null, so people can report bugs. We'll see if that becomes a problem later.
created_at: 2025-04-22T17:48:55Z
author_association: MEMBER

PR comments:
user: login: flutter-dashboard[bot]
created_at: 2025-04-04T12:46:56Z
body: It looks like this pull request may not have tests. Please make sure to add tests or get an explicit test exemption before merging.

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, **is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?**

__Reviewers__: Read the [Tree Hygiene page](https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#how-to-review-code) and make sure this patch meets those guidelines before LGTMing.If you believe this PR qualifies for a test exemption, contact "@test-exemption-reviewer" in the #hackers channel in [Discord](https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md) (don't just cc them here, they won't see it!). The test exemption team is a small volunteer group, so _all_ reviewers should feel empowered to ask for tests, without delegating that responsibility entirely to the test exemption group.
author_association: NONE
user: login: google-cla[bot]
created_at: 2025-04-04T12:46:57Z
body: Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this [failed invocation](https://github.com/flutter/packages/pull/8999/checks?check_run_id=39986840330) of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.
author_association: NONE
user: login: ditman
created_at: 2025-04-04T18:40:44Z
body: Thanks Vikash, I reached out to you internally with next steps that are only relevant for Googlers.
author_association: MEMBER