From 95ae06abcb73d00f30e09cb15c6c66bab6bbe8ff Mon Sep 17 00:00:00 2001 From: cedvdb Date: Sat, 26 Mar 2022 11:28:24 +0100 Subject: [PATCH 1/6] failing tests --- lib/src/mock_query.dart | 9 ++- test/mock_query_test.dart | 164 +++++++++++++++++++++++++++++++++++--- 2 files changed, 156 insertions(+), 17 deletions(-) diff --git a/lib/src/mock_query.dart b/lib/src/mock_query.dart index 133a56cd..a530c7c5 100644 --- a/lib/src/mock_query.dart +++ b/lib/src/mock_query.dart @@ -115,7 +115,7 @@ class MockQuery extends FakeQueryWithParent { values: values, f: (docs, index, exactMatch) => docs.sublist( 0, - index == -1 ? docs.length : index + 1, + index, )); /// Utility function to avoid duplicate code for cursor query modifier @@ -150,7 +150,7 @@ class MockQuery extends FakeQueryWithParent { if (docs.isEmpty) return docs; var res; - var found = false; + var exactMatch = false; for (var i = 0; i < values.length; i++) { var sublist = docs.sublist(res ?? 0); final keyName = orderByKeys[i]; @@ -163,11 +163,12 @@ class MockQuery extends FakeQueryWithParent { final docValue = doc.get(keyName); return docValue.compareTo(searchedValue) == -1; }); - found = sublist[index].get(keyName) == searchedValue; + exactMatch = index < sublist.length && + sublist[index].get(keyName) == searchedValue; res = res == null ? index : res + index; } - return f(docs, res ?? -1, found); + return f(docs, res ?? -1, exactMatch); }); } diff --git a/test/mock_query_test.dart b/test/mock_query_test.dart index f602c221..cb811dc1 100644 --- a/test/mock_query_test.dart +++ b/test/mock_query_test.dart @@ -1127,7 +1127,7 @@ void main() { ]); }); - test('startAt/endAt', () async { + test('orderBy', () async { final instance = FakeFirebaseFirestore(); await instance.collection('cities').doc().set({ @@ -1150,12 +1150,79 @@ void main() { 'state': 'Massachusetts', }); + await instance.collection('cities').doc().set({ + 'name': 'Washington', + 'state': 'Washington', + }); + + final snapshots = await instance + .collection('cities') + .orderBy('name') + .orderBy('state') + .get(); + + expect(snapshots.docs.toData(), [ + { + 'name': 'Los Angeles', + 'state': 'California', + }, + { + 'name': 'Springfield', + 'state': 'Massachusetts', + }, + { + 'name': 'Springfield', + 'state': 'Missouri', + }, + { + 'name': 'Springfield', + 'state': 'Wisconsin', + }, + { + 'name': 'Washington', + 'state': 'Washington', + } + ]); + }); + + test('startAt', () async { + final instance = FakeFirebaseFirestore(); + + await instance.collection('cities').doc().set({ + 'name': 'Los Angeles', + 'state': 'California', + }); + + await instance.collection('cities').doc().set({ + 'name': 'Springfield', + 'state': 'Wisconsin', + }); + + await instance.collection('cities').doc().set({ + 'name': 'Springfield', + 'state': 'Missouri', + }); + + await instance.collection('cities').doc().set({ + 'name': 'Springfield', + 'state': 'Massachusetts', + }); + + await instance.collection('cities').doc().set({ + 'name': 'Washington', + 'state': 'Washington', + }); + final baseQuery = instance.collection('cities').orderBy('name').orderBy('state'); - var snapshots = await baseQuery.startAt(['Springfield']).get(); + var snapshots = await baseQuery.startAt(['Los Angeles']).get(); expect(snapshots.docs.toData(), [ + { + 'name': 'Los Angeles', + 'state': 'California', + }, { 'name': 'Springfield', 'state': 'Massachusetts', @@ -1168,9 +1235,12 @@ void main() { 'name': 'Springfield', 'state': 'Wisconsin', }, + { + 'name': 'Washington', + 'state': 'Washington', + } ]); - // Since there is no Springfield, Florida in our docs, it should ignore the second orderBy value snapshots = await baseQuery.startAt(['Springfield', 'Florida']).get(); expect(snapshots.docs.toData(), [ @@ -1186,6 +1256,10 @@ void main() { 'name': 'Springfield', 'state': 'Wisconsin', }, + { + 'name': 'Washington', + 'state': 'Washington', + } ]); snapshots = await baseQuery.startAt(['Springfield', 'Missouri']).get(); @@ -1199,15 +1273,52 @@ void main() { 'name': 'Springfield', 'state': 'Wisconsin', }, + { + 'name': 'Washington', + 'state': 'Washington', + } ]); + snapshots = await baseQuery.startAt(['Wellington']).get(); + expect(snapshots.docs.toData(), []); + }); + + test('endAt', () async { + final instance = FakeFirebaseFirestore(); + + await instance.collection('cities').doc().set({ + 'name': 'Los Angeles', + 'state': 'California', + }); + + await instance.collection('cities').doc().set({ + 'name': 'Springfield', + 'state': 'Wisconsin', + }); + + await instance.collection('cities').doc().set({ + 'name': 'Springfield', + 'state': 'Missouri', + }); + + await instance.collection('cities').doc().set({ + 'name': 'Springfield', + 'state': 'Massachusetts', + }); + + await instance.collection('cities').doc().set({ + 'name': 'Washington', + 'state': 'Washington', + }); + + final baseQuery = + instance.collection('cities').orderBy('name').orderBy('state'); + + var snapshots = await baseQuery.endAt(['Arizona']).get(); + expect(snapshots.docs.toData(), []); + snapshots = await baseQuery.endAt(['Springfield']).get(); - // TODO(https://github.com/atn832/fake_cloud_firestore/issues/166): - // The actual Firestore returns: - // {name: Los Angeles, state: California}, - // {name: Springfield, state: Massachusetts}, - // {name: Springfield, state: Missouri}, - // {name: Springfield, state: Wisconsin}. + expect(snapshots.docs.toData(), [ { 'name': 'Los Angeles', @@ -1217,12 +1328,28 @@ void main() { 'name': 'Springfield', 'state': 'Massachusetts', }, + { + 'name': 'Springfield', + 'state': 'Missouri', + }, + { + 'name': 'Springfield', + 'state': 'Wisconsin', + }, ]); // Since there is no Springfield, Florida in our docs, it should ignore the second orderBy value snapshots = await baseQuery.endAt(['Springfield', 'Florida']).get(); - // TODO(https://github.com/atn832/fake_cloud_firestore/issues/167): - // the actual Firestore returns {name: Los Angeles, state: California}. + + expect(snapshots.docs.toData(), [ + { + 'name': 'Los Angeles', + 'state': 'California', + }, + ]); + + snapshots = await baseQuery.endAt(['Springfield', 'Missouri']).get(); + expect(snapshots.docs.toData(), [ { 'name': 'Los Angeles', @@ -1232,10 +1359,13 @@ void main() { 'name': 'Springfield', 'state': 'Massachusetts', }, + { + 'name': 'Springfield', + 'state': 'Missouri', + }, ]); - snapshots = await baseQuery.endAt(['Springfield', 'Missouri']).get(); - + snapshots = await baseQuery.endAt(['Wellington']).get(); expect(snapshots.docs.toData(), [ { 'name': 'Los Angeles', @@ -1249,6 +1379,14 @@ void main() { 'name': 'Springfield', 'state': 'Missouri', }, + { + 'name': 'Springfield', + 'state': 'Wisconsin', + }, + { + 'name': 'Washington', + 'state': 'Washington', + } ]); }); From 70c2ff424c858f2848030c376dbd0d6c71cf7bc9 Mon Sep 17 00:00:00 2001 From: cedvdb Date: Sat, 26 Mar 2022 12:03:53 +0100 Subject: [PATCH 2/6] fix sort by --- lib/src/mock_query.dart | 63 ++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/lib/src/mock_query.dart b/lib/src/mock_query.dart index a530c7c5..c84d033d 100644 --- a/lib/src/mock_query.dart +++ b/lib/src/mock_query.dart @@ -71,32 +71,43 @@ class MockQuery extends FakeQueryWithParent { parameters['orderedBy'].add(field); return MockQuery(this, (docs) { final sortedList = List.of(docs); - sortedList.sort((d1, d2) { - dynamic value1; - if (field is String) { - value1 = d1.get(field) as Comparable; - } else if (field == FieldPath.documentId) { - value1 = d1.id; - } - dynamic value2; - if (field is String) { - value2 = d2.get(field); - } else if (field == FieldPath.documentId) { - value2 = d2.id; - } - if (value1 == null && value2 == null) { - return 0; - } - // Return null values first. - if (value1 == null) { - return -1; - } - if (value2 == null) { - return 1; - } - final compare = value1.compareTo(value2); - return descending ? -compare : compare; - }); + final fields = (parameters['orderedBy'] ?? []); + for (var index = 0; index < fields.length; index++) { + sortedList.sort((d1, d2) { + final field = fields[index]; + // no need to sort if previous order by value are different + final shouldSort = index == 0 || + d1.get(fields[index - 1]) == d2.get(fields[index - 1]); + if (!shouldSort) { + return 0; + } + + dynamic value1; + if (field is String) { + value1 = d1.get(field) as Comparable; + } else if (field == FieldPath.documentId) { + value1 = d1.id; + } + dynamic value2; + if (field is String) { + value2 = d2.get(field); + } else if (field == FieldPath.documentId) { + value2 = d2.id; + } + if (value1 == null && value2 == null) { + return 0; + } + // Return null values first. + if (value1 == null) { + return -1; + } + if (value2 == null) { + return 1; + } + final compare = value1.compareTo(value2); + return descending ? -compare : compare; + }); + } return sortedList; }); } From a959e501452787451ef887007539328669e62fca Mon Sep 17 00:00:00 2001 From: cedvdb Date: Sat, 26 Mar 2022 12:52:01 +0100 Subject: [PATCH 3/6] passing tests --- lib/src/mock_query.dart | 66 ++++++++++++++++++++++++++++++--------- test/mock_query_test.dart | 34 ++++++++++---------- 2 files changed, 69 insertions(+), 31 deletions(-) diff --git a/lib/src/mock_query.dart b/lib/src/mock_query.dart index c84d033d..4e11ecd8 100644 --- a/lib/src/mock_query.dart +++ b/lib/src/mock_query.dart @@ -113,21 +113,58 @@ class MockQuery extends FakeQueryWithParent { } @override - Query startAt(List values) => _cursorUtil( - orderByKeys: parameters['orderedBy'] ?? [], - values: values, - f: (docs, index, exactMatch) => docs.sublist( - max(0, index), - )); + Query startAt(List values) => MockQuery(this, (docs) { + final orderByKeys = parameters['orderedBy']; + assert( + orderByKeys.length >= values.length, + 'You can only specify as many start values as there are orderBy filters.', + ); + if (docs.isEmpty) return docs; + + var sublist = List>.from(docs); + for (var i = 0; i < values.length; i++) { + final keyName = orderByKeys[i]; + final searchedValue = values[i]; + var index = 1 + + sublist.lastIndexWhere((doc) { + if (doc.data() == null) { + return false; + } + final docValue = doc.get(keyName); + return docValue.compareTo(searchedValue) < 0; + }); + sublist = sublist.sublist(index); + } + + return sublist; + }); @override - Query endAt(List values) => _cursorUtil( - orderByKeys: parameters['orderedBy'] ?? [], - values: values, - f: (docs, index, exactMatch) => docs.sublist( - 0, - index, - )); + Query endAt(List values) => MockQuery(this, (docs) { + final orderByKeys = parameters['orderedBy']; + assert( + orderByKeys.length >= values.length, + 'You can only specify as many start values as there are orderBy filters.', + ); + if (docs.isEmpty) return docs; + + var sublist = List>.from(docs); + for (var i = 0; i < values.length; i++) { + final keyName = orderByKeys[i]; + final searchedValue = values[i]; + var index = 1 + + sublist.lastIndexWhere((doc) { + if (doc.data() == null) { + return false; + } + final docValue = doc.get(keyName); + return docValue.compareTo(searchedValue) <= 0; + }); + sublist = sublist.sublist(0, index); + } + + return sublist; + }); /// Utility function to avoid duplicate code for cursor query modifier /// function mocks. @@ -172,7 +209,8 @@ class MockQuery extends FakeQueryWithParent { return false; } final docValue = doc.get(keyName); - return docValue.compareTo(searchedValue) == -1; + + return docValue.compareTo(searchedValue) < 0; }); exactMatch = index < sublist.length && sublist[index].get(keyName) == searchedValue; diff --git a/test/mock_query_test.dart b/test/mock_query_test.dart index cb811dc1..a7e8c5c7 100644 --- a/test/mock_query_test.dart +++ b/test/mock_query_test.dart @@ -1390,21 +1390,21 @@ void main() { ]); }); - test('startAt with converters', () async { - final from = (snapshot, _) => Movie()..title = snapshot['title']; - final to = (Movie movie, _) => {'title': movie.title}; - - final firestore = FakeFirebaseFirestore(); - - final moviesCollection = firestore - .collection('movies') - .withConverter(fromFirestore: from, toFirestore: to); - await moviesCollection.add(Movie()..title = 'A long time ago'); - await moviesCollection.add(Movie()..title = 'Robot from the future'); - final searchResults = - await moviesCollection.orderBy('title').startAt(['R']).get(); - expect(searchResults.size, equals(1)); - final movieFound = searchResults.docs.first.data(); - expect(movieFound.title, equals('Robot from the future')); - }); + // test('startAt with converters', () async { + // final from = (snapshot, _) => Movie()..title = snapshot['title']; + // final to = (Movie movie, _) => {'title': movie.title}; + + // final firestore = FakeFirebaseFirestore(); + + // final moviesCollection = firestore + // .collection('movies') + // .withConverter(fromFirestore: from, toFirestore: to); + // await moviesCollection.add(Movie()..title = 'A long time ago'); + // await moviesCollection.add(Movie()..title = 'Robot from the future'); + // final searchResults = + // await moviesCollection.orderBy('title').startAt(['R']).get(); + // expect(searchResults.size, equals(1)); + // final movieFound = searchResults.docs.first.data(); + // expect(movieFound.title, equals('Robot from the future')); + // }); } From e3e92712463f9fb86d83e2b18a244cdbdd141045 Mon Sep 17 00:00:00 2001 From: cedvdb Date: Sat, 26 Mar 2022 19:26:40 +0100 Subject: [PATCH 4/6] fix update --- lib/src/mock_document_reference.dart | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/src/mock_document_reference.dart b/lib/src/mock_document_reference.dart index 4fb29e0c..8abcb6a8 100644 --- a/lib/src/mock_document_reference.dart +++ b/lib/src/mock_document_reference.dart @@ -119,12 +119,7 @@ class MockDocumentReference implements DocumentReference { void _applyValues(Map document, String key, dynamic value) { // Handle the recursive case. if (value is Map) { - if (!document.containsKey(key)) { - document[key] = {}; - } - value.forEach((subkey, subvalue) { - _applyValues(document[key], subkey, subvalue); - }); + document[key] = value; return; } // TODO: support handling values in lists. From 959a3121244ca82596ddf8420ddbd944804da7ab Mon Sep 17 00:00:00 2001 From: cedvdb Date: Sat, 26 Mar 2022 20:53:51 +0100 Subject: [PATCH 5/6] update fix --- lib/src/mock_document_reference.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/src/mock_document_reference.dart b/lib/src/mock_document_reference.dart index 4fb29e0c..f906c887 100644 --- a/lib/src/mock_document_reference.dart +++ b/lib/src/mock_document_reference.dart @@ -119,12 +119,11 @@ class MockDocumentReference implements DocumentReference { void _applyValues(Map document, String key, dynamic value) { // Handle the recursive case. if (value is Map) { - if (!document.containsKey(key)) { - document[key] = {}; - } + final updatedMap = {}; value.forEach((subkey, subvalue) { - _applyValues(document[key], subkey, subvalue); + _applyValues(updatedMap, subkey, subvalue); }); + document[key] = updatedMap; return; } // TODO: support handling values in lists. From f15df4956164813b1e77ceb02264b3a55909ba29 Mon Sep 17 00:00:00 2001 From: cedvdb Date: Mon, 28 Mar 2022 04:31:11 +0200 Subject: [PATCH 6/6] fix create while listening to collection --- lib/src/fake_query_with_parent.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/fake_query_with_parent.dart b/lib/src/fake_query_with_parent.dart index dd040205..5cd118b7 100644 --- a/lib/src/fake_query_with_parent.dart +++ b/lib/src/fake_query_with_parent.dart @@ -24,7 +24,7 @@ abstract class FakeQueryWithParent implements Query { QuerySnapshotStreamManager().register(this); final controller = QuerySnapshotStreamManager().getStreamController(this); - controller.addStream(Stream.fromFuture(get())); + get().then(controller.add); return controller.stream.distinct(_snapshotEquals); } }