diff --git a/packages/google_cloud_firestore/lib/src/field_value.dart b/packages/google_cloud_firestore/lib/src/field_value.dart index b9e2eecc..16fada82 100644 --- a/packages/google_cloud_firestore/lib/src/field_value.dart +++ b/packages/google_cloud_firestore/lib/src/field_value.dart @@ -440,14 +440,17 @@ void _validateUserInput( case Map(): for (final entry in value.entries) { + final keyPath = switch (entry.key) { + final FieldPath fp => fp, + final String s => FieldPath([s]), + _ => FieldPath([entry.key.toString()]), + }; _validateUserInput( arg, entry.value, description: description, options: options, - path: path == null - ? FieldPath.from(entry.key) - : path._appendPath(FieldPath.from(entry.key)), + path: path == null ? keyPath : path._appendPath(keyPath), level: level + 1, inArray: inArray, ); diff --git a/packages/google_cloud_firestore/test/integration/firestore_test.dart b/packages/google_cloud_firestore/test/integration/firestore_test.dart index 1e5d2969..50208056 100644 --- a/packages/google_cloud_firestore/test/integration/firestore_test.dart +++ b/packages/google_cloud_firestore/test/integration/firestore_test.dart @@ -34,5 +34,38 @@ void main() { expect(collections, containsAll([a, b])); }); + + group('map keys with "/" characters', () { + test('set() round-trips a map with "/" in key', () async { + final docRef = firestore.doc('activities/new-activity'); + + await docRef.set({ + 'activityType': 'activityA', + 'agents': {'products/product-a': 5.0}, + }); + + final data = (await docRef.get()).data()!; + expect(data['activityType'], 'activityA'); + expect( + (data['agents']! as Map)['products/product-a'], + 5.0, + ); + }); + + test('update() round-trips a map value with "/" in key', () async { + final docRef = firestore.doc('activities/update-activity'); + await docRef.set({'activityType': 'activityA'}); + + await docRef.update({ + 'agents': {'products/product-b': 10.0}, + }); + + final data = (await docRef.get()).data()!; + expect( + (data['agents']! as Map)['products/product-b'], + 10.0, + ); + }); + }); }); } diff --git a/packages/google_cloud_firestore/test/unit/firestore_test.dart b/packages/google_cloud_firestore/test/unit/firestore_test.dart index cd9748f4..ec0bada9 100644 --- a/packages/google_cloud_firestore/test/unit/firestore_test.dart +++ b/packages/google_cloud_firestore/test/unit/firestore_test.dart @@ -185,6 +185,39 @@ void main() { final batch = firestore.batch(); expect(batch, isA()); }); + + group('set() with map keys containing "/"', () { + test('accepts a top-level map value with "/" in key', () { + final firestore = Firestore( + settings: const Settings(projectId: 'test'), + ); + final batch = firestore.batch(); + final docRef = firestore.doc('activities/new-activity'); + + expect( + () => batch.set(docRef, { + 'activityType': 'activityA', + 'agents': {'products/product-a': 5.0}, + }), + returnsNormally, + ); + }); + + test('accepts nested maps with "/" in keys', () { + final firestore = Firestore( + settings: const Settings(projectId: 'test'), + ); + final batch = firestore.batch(); + final docRef = firestore.doc('col/doc'); + + expect( + () => batch.set(docRef, { + 'refs': {'users/alice': true, 'users/bob': false}, + }), + returnsNormally, + ); + }); + }); }); group('bulkWriter()', () {