diff --git a/packages/cloud_firestore/cloud_firestore_web/CHANGELOG.md b/packages/cloud_firestore/cloud_firestore_web/CHANGELOG.md new file mode 100644 index 000000000000..e53776993662 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.1.0 + +- Initial release \ No newline at end of file diff --git a/packages/cloud_firestore/cloud_firestore_web/LICENSE b/packages/cloud_firestore/cloud_firestore_web/LICENSE new file mode 100644 index 000000000000..000b4618d2bd --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/LICENSE @@ -0,0 +1,27 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// 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 +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/packages/cloud_firestore/cloud_firestore_web/README.md b/packages/cloud_firestore/cloud_firestore_web/README.md new file mode 100644 index 000000000000..b9d0e93f883d --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/README.md @@ -0,0 +1,52 @@ +# cloud_firestore_web + +The web implementation of [`cloud_firestore`][1]. + +## Usage + +### Import the package + +To use this plugin in your Flutter app on the web, simply add it as a +dependency in your `pubspec.yaml` alongside the base `cloud_firestore` +plugin. + +_(This is only temporary: in the future we hope to make this package +an "endorsed" implementation of `cloud_firestore`, so it will automatically +be included in your app when you run your Flutter app on the web.)_ + +Add this to your `pubspec.yaml`: + +```yaml +... +dependencies: + ... + cloud_firestore: ^0.13.1 + cloud_firestore_web: ^0.1.0 + ... +``` + +### Updating `index.html` + +Due to [this bug in dartdevc][2], you will need to manually add the Firebase +JavaScript files to your `index.html` file. + +In your app directory, edit `web/index.html` to add the line: + +```html + + ... + + + + + + +``` + +### Using the plugin + +Once you have added the `cloud_firebase_web` dependency to your pubspec, +you can use `package:cloud_firebase` as normal. + +[1]: ../cloud_firestore +[2]: https://github.com/dart-lang/sdk/issues/33979 diff --git a/packages/cloud_firestore/cloud_firestore_web/android/.gitignore b/packages/cloud_firestore/cloud_firestore_web/android/.gitignore new file mode 100644 index 000000000000..c6cbe562a427 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/android/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures diff --git a/packages/cloud_firestore/cloud_firestore_web/android/build.gradle b/packages/cloud_firestore/cloud_firestore_web/android/build.gradle new file mode 100644 index 000000000000..e3db786e3805 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/android/build.gradle @@ -0,0 +1,33 @@ +group 'io.flutter.plugins.cloud_firestore_web' +version '1.0' + +buildscript { + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.5.0' + } +} + +rootProject.allprojects { + repositories { + google() + jcenter() + } +} + +apply plugin: 'com.android.library' + +android { + compileSdkVersion 28 + + defaultConfig { + minSdkVersion 16 + } + lintOptions { + disable 'InvalidPackage' + } +} diff --git a/packages/cloud_firestore/cloud_firestore_web/android/gradle.properties b/packages/cloud_firestore/cloud_firestore_web/android/gradle.properties new file mode 100644 index 000000000000..3148384dab31 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/android/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx1536M +android.enableR8=true \ No newline at end of file diff --git a/packages/cloud_firestore/cloud_firestore_web/android/gradle/wrapper/gradle-wrapper.properties b/packages/cloud_firestore/cloud_firestore_web/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000000..019065d1d650 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip diff --git a/packages/cloud_firestore/cloud_firestore_web/android/settings.gradle b/packages/cloud_firestore/cloud_firestore_web/android/settings.gradle new file mode 100644 index 000000000000..6bbf9cba49da --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'cloud_firestore_web' diff --git a/packages/cloud_firestore/cloud_firestore_web/android/src/main/AndroidManifest.xml b/packages/cloud_firestore/cloud_firestore_web/android/src/main/AndroidManifest.xml new file mode 100644 index 000000000000..ff4f493a984c --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/packages/cloud_firestore/cloud_firestore_web/android/src/main/java/io/flutter/plugins/cloud_firestore_web/FirestoreWebPlugin.java b/packages/cloud_firestore/cloud_firestore_web/android/src/main/java/io/flutter/plugins/cloud_firestore_web/FirestoreWebPlugin.java new file mode 100644 index 000000000000..af02befd488e --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/android/src/main/java/io/flutter/plugins/cloud_firestore_web/FirestoreWebPlugin.java @@ -0,0 +1,15 @@ +package io.flutter.plugins.cloud_firestore_web; + +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.plugin.common.PluginRegistry.Registrar; + +/** FirebaseAuthWebPlugin */ +public class FirestoreWebPlugin implements FlutterPlugin { + @Override + public void onAttachedToEngine(FlutterPluginBinding flutterPluginBinding) {} + + public static void registerWith(Registrar registrar) {} + + @Override + public void onDetachedFromEngine(FlutterPluginBinding binding) {} +} diff --git a/packages/cloud_firestore/cloud_firestore_web/ios/cloud_firestore_web.podspec b/packages/cloud_firestore/cloud_firestore_web/ios/cloud_firestore_web.podspec new file mode 100644 index 000000000000..2e9b05c81076 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/ios/cloud_firestore_web.podspec @@ -0,0 +1,21 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html +# +Pod::Spec.new do |s| + s.name = 'cloud_firestore_web' + s.version = '0.1.0' + s.summary = 'No-op implementation of cloud_firestore_web web plugin to avoid build issues on iOS' + s.description = <<-DESC + temp fake firebase_auth_web plugin + DESC + s.homepage = 'https://github.com/FirebaseExtended/flutterfire/tree/master/packages/cloud_firestore/cloud_firestore_web' + s.license = { :file => '../LICENSE' } + s.author = { 'Flutter Team' => 'flutter-dev@googlegroups.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.public_header_files = 'Classes/**/*.h' + s.dependency 'Flutter' + + s.ios.deployment_target = '8.0' + end + diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/cloud_firestore_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/cloud_firestore_web.dart new file mode 100644 index 000000000000..291880726be8 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/lib/cloud_firestore_web.dart @@ -0,0 +1,96 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; +import 'package:firebase/firebase.dart' as firebase; +import 'package:firebase/firestore.dart' show Firestore, Settings; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; + +import 'package:cloud_firestore_web/src/collection_reference_web.dart'; +import 'package:cloud_firestore_web/src/field_value_factory_web.dart'; +import 'package:cloud_firestore_web/src/document_reference_web.dart'; +import 'package:cloud_firestore_web/src/query_web.dart'; +import 'package:cloud_firestore_web/src/transaction_web.dart'; +import 'package:cloud_firestore_web/src/write_batch_web.dart'; + +/// Web implementation for [FirestorePlatform] +/// delegates calls to firestore web plugin +class FirestoreWeb extends FirestorePlatform { + /// instance of Firestore from the web plugin + final Firestore _webFirestore; + + /// Called by PluginRegistry to register this plugin for Flutter Web + static void registerWith(Registrar registrar) { + FirestorePlatform.instance = FirestoreWeb(); + } + + /// Builds an instance of [CloudFirestoreWeb] with an optional [FirebaseApp] instance + /// If [app] is null then the created instance will use the default [FirebaseApp] + FirestoreWeb({FirebaseApp app}) + : _webFirestore = firebase + .firestore(firebase.app((app ?? FirebaseApp.instance).name)), + super(app: app ?? FirebaseApp.instance) { + FieldValueFactoryPlatform.instance = FieldValueFactoryWeb(); + } + + @override + FirestorePlatform withApp(FirebaseApp app) => FirestoreWeb(app: app); + + @override + CollectionReferencePlatform collection(String path) { + return CollectionReferenceWeb(this, _webFirestore, path.split('/')); + } + + @override + QueryPlatform collectionGroup(String path) { + return QueryWeb(this, path, _webFirestore.collectionGroup(path), + isCollectionGroup: true); + } + + @override + DocumentReferencePlatform document(String path) => + DocumentReferenceWeb(_webFirestore, this, path.split('/')); + + @override + WriteBatchPlatform batch() => WriteBatchWeb(_webFirestore.batch()); + + @override + Future enablePersistence(bool enable) async { + if (enable) { + await _webFirestore.enablePersistence(); + } + } + + @override + Future settings( + {bool persistenceEnabled, + String host, + bool sslEnabled, + int cacheSizeBytes}) async { + if (host != null && sslEnabled != null) { + _webFirestore.settings(Settings( + cacheSizeBytes: cacheSizeBytes ?? 40000000, + host: host, + ssl: sslEnabled)); + } else { + _webFirestore + .settings(Settings(cacheSizeBytes: cacheSizeBytes ?? 40000000)); + } + if (persistenceEnabled) { + await _webFirestore.enablePersistence(); + } + } + + @override + Future> runTransaction( + TransactionHandler transactionHandler, + {Duration timeout = const Duration(seconds: 5)}) async { + Map result; + await _webFirestore.runTransaction((transaction) async { + result = await transactionHandler(TransactionWeb(transaction, this)); + }).timeout(timeout); + return result is Map ? result : {}; + } +} diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/collection_reference_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/collection_reference_web.dart new file mode 100644 index 000000000000..7cc586951865 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/collection_reference_web.dart @@ -0,0 +1,192 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; +import 'package:firebase/firestore.dart' as web; +import 'package:meta/meta.dart'; + +import 'package:cloud_firestore_web/src/document_reference_web.dart'; +import 'package:cloud_firestore_web/src/query_web.dart'; + +/// Web implementation for Firestore [CollectionReferencePlatform] +class CollectionReferenceWeb extends CollectionReferencePlatform { + /// instance of Firestore from the web plugin + final web.Firestore _webFirestore; + final FirestorePlatform _firestorePlatform; + // disabling lint as it's only visible for testing + @visibleForTesting + QueryWeb queryDelegate; // ignore: public_member_api_docs + + /// Creates an instance of [CollectionReferenceWeb] which represents path + /// at [pathComponents] and uses implementation of [webFirestore] + CollectionReferenceWeb( + this._firestorePlatform, this._webFirestore, List pathComponents) + : queryDelegate = QueryWeb( + _firestorePlatform, + pathComponents.join("/"), + _webFirestore.collection(pathComponents.join("/")), + ), + super(_firestorePlatform, pathComponents); + + @override + DocumentReferencePlatform parent() { + if (pathComponents.length < 2) { + return null; + } + return DocumentReferenceWeb( + _webFirestore, + firestore, + (List.from(pathComponents)..removeLast()), + ); + } + + @override + DocumentReferencePlatform document([String path]) { + List childPath; + if (path == null) { + web.DocumentReference doc = + _webFirestore.collection(pathComponents.join('/')).doc(); + childPath = doc.path.split('/'); + } else { + childPath = List.from(pathComponents)..addAll(path.split(('/'))); + } + return DocumentReferenceWeb( + _webFirestore, + firestore, + childPath, + ); + } + + @override + Future add(Map data) async { + final DocumentReferencePlatform newDocument = document(); + await newDocument.setData(data); + return newDocument; + } + + @override + Map buildArguments() => queryDelegate.buildArguments(); + + @override + QueryPlatform endAt(List values) { + _resetQueryDelegate(); + return queryDelegate.endAt(values); + } + + @override + QueryPlatform endAtDocument(DocumentSnapshotPlatform documentSnapshot) { + _resetQueryDelegate(); + return queryDelegate.endAtDocument(documentSnapshot); + } + + @override + QueryPlatform endBefore(List values) { + _resetQueryDelegate(); + return queryDelegate.endBefore(values); + } + + @override + QueryPlatform endBeforeDocument(DocumentSnapshotPlatform documentSnapshot) { + _resetQueryDelegate(); + return queryDelegate.endBeforeDocument(documentSnapshot); + } + + @override + FirestorePlatform get firestore => _firestorePlatform; + + @override + Future getDocuments({ + Source source = Source.serverAndCache, + }) => + queryDelegate.getDocuments(source: source); + + @override + String get id => pathComponents.isEmpty ? null : pathComponents.last; + + @override + bool get isCollectionGroup => false; + + @override + QueryPlatform limit(int length) { + _resetQueryDelegate(); + return queryDelegate.limit(length); + } + + @override + QueryPlatform orderBy( + field, { + bool descending = false, + }) { + _resetQueryDelegate(); + return queryDelegate.orderBy(field, descending: descending); + } + + @override + Map get parameters => queryDelegate.parameters; + + @override + String get path => pathComponents.join("/"); + + @override + CollectionReferencePlatform reference() => queryDelegate.reference(); + + @override + Stream snapshots({ + bool includeMetadataChanges = false, + }) => + queryDelegate.snapshots(includeMetadataChanges: includeMetadataChanges); + + @override + QueryPlatform startAfter(List values) { + _resetQueryDelegate(); + return queryDelegate.startAfter(values); + } + + @override + QueryPlatform startAfterDocument(DocumentSnapshotPlatform documentSnapshot) { + _resetQueryDelegate(); + return queryDelegate.startAfterDocument(documentSnapshot); + } + + @override + QueryPlatform startAt(List values) { + _resetQueryDelegate(); + return queryDelegate.startAt(values); + } + + @override + QueryPlatform startAtDocument(DocumentSnapshotPlatform documentSnapshot) { + _resetQueryDelegate(); + return queryDelegate.startAtDocument(documentSnapshot); + } + + @override + QueryPlatform where( + field, { + isEqualTo, + isLessThan, + isLessThanOrEqualTo, + isGreaterThan, + isGreaterThanOrEqualTo, + arrayContains, + List arrayContainsAny, + List whereIn, + bool isNull, + }) { + _resetQueryDelegate(); + return queryDelegate.where(field, + isEqualTo: isEqualTo, + isLessThan: isLessThan, + isLessThanOrEqualTo: isLessThanOrEqualTo, + isGreaterThan: isGreaterThan, + isGreaterThanOrEqualTo: isGreaterThanOrEqualTo, + arrayContains: arrayContains, + arrayContainsAny: arrayContainsAny, + whereIn: whereIn, + isNull: isNull); + } + + void _resetQueryDelegate() => + queryDelegate = queryDelegate.resetQueryDelegate(); +} diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/document_reference_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/document_reference_web.dart new file mode 100644 index 000000000000..373ae6f83261 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/document_reference_web.dart @@ -0,0 +1,66 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; +import 'package:firebase/firestore.dart' as web; + +import 'package:cloud_firestore_web/src/collection_reference_web.dart'; +import 'package:cloud_firestore_web/src/utils/document_reference_utils.dart'; +import 'package:cloud_firestore_web/src/utils/codec_utility.dart'; + +/// Web implementation for firestore [DocumentReferencePlatform] +class DocumentReferenceWeb extends DocumentReferencePlatform { + /// instance of Firestore from the web plugin + final web.Firestore firestoreWeb; + + /// instance of DocumentReference from the web plugin + final web.DocumentReference delegate; + + /// Creates an instance of [CollectionReferenceWeb] which represents path + /// at [pathComponents] and uses implementation of [firestoreWeb] + DocumentReferenceWeb( + this.firestoreWeb, + FirestorePlatform firestore, + List pathComponents, + ) : delegate = firestoreWeb.doc(pathComponents.join("/")), + super(firestore, pathComponents); + + @override + Future setData( + Map data, { + bool merge = false, + }) => + delegate.set( + CodecUtility.encodeMapData(data), + web.SetOptions(merge: merge), + ); + + @override + Future updateData(Map data) => + delegate.update(data: CodecUtility.encodeMapData(data)); + + @override + Future get({ + Source source = Source.serverAndCache, + }) async { + return fromWebDocumentSnapshotToPlatformDocumentSnapshot( + await delegate.get(), this.firestore); + } + + @override + Future delete() => delegate.delete(); + + @override + Stream snapshots({ + bool includeMetadataChanges = false, + }) { + Stream querySnapshots = delegate.onSnapshot; + if (includeMetadataChanges) { + querySnapshots = delegate.onMetadataChangesSnapshot; + } + return querySnapshots.map((webSnapshot) => + fromWebDocumentSnapshotToPlatformDocumentSnapshot( + webSnapshot, this.firestore)); + } +} diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/field_value_factory_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/field_value_factory_web.dart new file mode 100644 index 000000000000..cfae0922dfa1 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/field_value_factory_web.dart @@ -0,0 +1,32 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; +import 'package:firebase/firestore.dart' as web; +import 'package:js/js_util.dart'; + +import 'package:cloud_firestore_web/src/field_value_web.dart'; + +/// An implementation of [FieldValueFactoryPlatform] which builds [FieldValuePlatform] +/// instance that is [jsify] friendly +class FieldValueFactoryWeb extends FieldValueFactoryPlatform { + @override + FieldValuePlatform arrayRemove(List elements) => + FieldValueWeb(web.FieldValue.arrayRemove(elements)); + + @override + FieldValuePlatform arrayUnion(List elements) => + FieldValueWeb(web.FieldValue.arrayUnion(elements)); + + @override + FieldValuePlatform delete() => FieldValueWeb(web.FieldValue.delete()); + + @override + FieldValuePlatform increment(num value) => + FieldValueWeb(web.FieldValue.increment(value)); + + @override + FieldValuePlatform serverTimestamp() => + FieldValueWeb(web.FieldValue.serverTimestamp()); +} diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/field_value_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/field_value_web.dart new file mode 100644 index 000000000000..167802590087 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/field_value_web.dart @@ -0,0 +1,16 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; +import 'package:firebase/firestore.dart' as web; + +/// Implementation of [FieldValuePlatform] that is compatible with +/// firestore web plugin +class FieldValueWeb extends FieldValuePlatform { + /// The js-interop delegate for this [FieldValuePlatform] + web.FieldValue data; + + /// Constructs a web version of [FieldValuePlatform] wrapping a web [FieldValue]. + FieldValueWeb(this.data) : super(); +} diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/query_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/query_web.dart new file mode 100644 index 000000000000..aa30435580b9 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/query_web.dart @@ -0,0 +1,306 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; +import 'package:cloud_firestore_web/src/utils/codec_utility.dart'; +import 'package:firebase/firestore.dart' as web; + +import 'package:cloud_firestore_web/src/utils/document_reference_utils.dart'; + +/// Web implementation for firestore [QueryPlatform] +class QueryWeb extends QueryPlatform { + final web.Query _webQuery; + final FirestorePlatform _firestore; + final bool _isCollectionGroup; + final String _path; + final List _orderByKeys; + static const _kChangeTypeAdded = "added"; + static const _kChangeTypeModified = "modified"; + static const _kChangeTypeRemoved = "removed"; + + /// Builds an instance of [QueryWeb] delegating to a package:firebase [Query] + /// to delegate queries to underlying firestore web plugin + QueryWeb( + this._firestore, + this._path, + this._webQuery, { + bool isCollectionGroup, + List orderByKeys, + }) : this._isCollectionGroup = isCollectionGroup ?? false, + this._orderByKeys = orderByKeys ?? [], + super( + firestore: _firestore, + pathComponents: _path.split('/'), + isCollectionGroup: isCollectionGroup, + ); + + @override + Stream snapshots({ + bool includeMetadataChanges = false, + }) { + assert(_webQuery != null); + Stream querySnapshots = _webQuery.onSnapshot; + if (includeMetadataChanges) { + querySnapshots = _webQuery.onSnapshotMetadata; + } + return querySnapshots.map(_webQuerySnapshotToQuerySnapshot); + } + + @override + Future getDocuments({ + Source source = Source.serverAndCache, + }) async { + assert(_webQuery != null); + return _webQuerySnapshotToQuerySnapshot(await _webQuery.get()); + } + + @override + Map buildArguments() => Map(); + + @override + QueryPlatform endAt(List values) => QueryWeb( + this._firestore, + this._path, + _webQuery != null ? _webQuery.endAt(fieldValues: values) : null, + isCollectionGroup: _isCollectionGroup, + ); + + @override + QueryPlatform endAtDocument(DocumentSnapshotPlatform documentSnapshot) { + assert(_webQuery != null && _orderByKeys.isNotEmpty); + return QueryWeb( + this._firestore, + this._path, + _webQuery.endAt( + fieldValues: + _orderByKeys.map((key) => documentSnapshot.data[key]).toList()), + isCollectionGroup: _isCollectionGroup); + } + + @override + QueryPlatform endBefore(List values) => QueryWeb( + this._firestore, + this._path, + _webQuery != null ? _webQuery.endBefore(fieldValues: values) : null, + isCollectionGroup: _isCollectionGroup, + ); + + @override + QueryPlatform endBeforeDocument(DocumentSnapshotPlatform documentSnapshot) { + assert(_webQuery != null && _orderByKeys.isNotEmpty); + return QueryWeb( + this._firestore, + this._path, + _webQuery.endBefore( + fieldValues: + _orderByKeys.map((key) => documentSnapshot.data[key]).toList()), + isCollectionGroup: _isCollectionGroup); + } + + @override + FirestorePlatform get firestore => _firestore; + + @override + bool get isCollectionGroup => _isCollectionGroup; + + @override + QueryPlatform limit(int length) => QueryWeb( + this._firestore, + this._path, + _webQuery != null ? _webQuery.limit(length) : null, + orderByKeys: _orderByKeys, + isCollectionGroup: _isCollectionGroup, + ); + + @override + QueryPlatform orderBy( + field, { + bool descending = false, + }) { + dynamic usableField = field; + if (field == FieldPath.documentId) { + usableField = web.FieldPath.documentId(); + } + return QueryWeb( + this._firestore, + this._path, + _webQuery.orderBy(usableField, descending ? "desc" : "asc"), + orderByKeys: _orderByKeys..add(usableField), + isCollectionGroup: _isCollectionGroup, + ); + } + + @override + String get path => this._path; + + @override + List get pathComponents => this._path.split("/"); + + @override + CollectionReferencePlatform reference() => firestore.collection(_path); + + @override + QueryPlatform startAfter(List values) => QueryWeb( + this._firestore, + this._path, + _webQuery.startAfter(fieldValues: values), + orderByKeys: _orderByKeys, + isCollectionGroup: _isCollectionGroup, + ); + + @override + QueryPlatform startAfterDocument(DocumentSnapshotPlatform documentSnapshot) { + assert(_webQuery != null && _orderByKeys.isNotEmpty); + return QueryWeb( + this._firestore, + this._path, + _webQuery.startAfter( + fieldValues: + _orderByKeys.map((key) => documentSnapshot.data[key]).toList()), + orderByKeys: _orderByKeys, + isCollectionGroup: _isCollectionGroup); + } + + @override + QueryPlatform startAt(List values) => QueryWeb( + this._firestore, + this._path, + _webQuery.startAt(fieldValues: values), + orderByKeys: _orderByKeys, + isCollectionGroup: _isCollectionGroup, + ); + + @override + QueryPlatform startAtDocument(DocumentSnapshotPlatform documentSnapshot) { + assert(_webQuery != null && _orderByKeys.isNotEmpty); + return QueryWeb( + this._firestore, + this._path, + _webQuery.startAt( + fieldValues: + _orderByKeys.map((key) => documentSnapshot.data[key]).toList(), + ), + orderByKeys: _orderByKeys, + isCollectionGroup: _isCollectionGroup, + ); + } + + @override + QueryPlatform where( + field, { + isEqualTo, + isLessThan, + isLessThanOrEqualTo, + isGreaterThan, + isGreaterThanOrEqualTo, + arrayContains, + List arrayContainsAny, + List whereIn, + bool isNull, + }) { + assert(field is String || field is FieldPath, + 'Supported [field] types are [String] and [FieldPath].'); + assert(_webQuery != null); + dynamic usableField = CodecUtility.valueEncode(field); + if (field == FieldPath.documentId) { + usableField = web.FieldPath.documentId(); + } + web.Query query = _webQuery; + + if (isEqualTo != null) { + query = + query.where(usableField, "==", CodecUtility.valueEncode(isEqualTo)); + } + if (isLessThan != null) { + query = + query.where(usableField, "<", CodecUtility.valueEncode(isLessThan)); + } + if (isLessThanOrEqualTo != null) { + query = query.where( + usableField, "<=", CodecUtility.valueEncode(isLessThanOrEqualTo)); + } + if (isGreaterThan != null) { + query = query.where( + usableField, ">", CodecUtility.valueEncode(isGreaterThan)); + } + if (isGreaterThanOrEqualTo != null) { + query = query.where( + usableField, ">=", CodecUtility.valueEncode(isGreaterThanOrEqualTo)); + } + if (arrayContains != null) { + query = query.where(usableField, "array-contains", + CodecUtility.valueEncode(arrayContains)); + } + if (arrayContainsAny != null) { + assert(arrayContainsAny.length <= 10, + "array contains can have maximum of 10 items"); + query = query.where(usableField, "array-contains-any", + CodecUtility.valueEncode(arrayContainsAny)); + } + if (whereIn != null) { + assert( + whereIn.length <= 10, "array contains can have maximum of 10 items"); + query = query.where(usableField, "in", CodecUtility.valueEncode(whereIn)); + } + if (isNull != null) { + assert( + isNull, + 'isNull can only be set to true. ' + 'Use isEqualTo to filter on non-null values.'); + query = query.where(usableField, "==", null); + } + return QueryWeb(this._firestore, this._path, query, + orderByKeys: _orderByKeys, isCollectionGroup: _isCollectionGroup); + } + + QuerySnapshotPlatform _webQuerySnapshotToQuerySnapshot( + web.QuerySnapshot webSnapshot, + ) { + return QuerySnapshotPlatform( + webSnapshot.docs + .map((webSnapshot) => + fromWebDocumentSnapshotToPlatformDocumentSnapshot( + webSnapshot, this._firestore)) + .toList(), + webSnapshot.docChanges().map(_webChangeToChange).toList(), + _webMetadataToMetada(webSnapshot.metadata)); + } + + DocumentChangePlatform _webChangeToChange(web.DocumentChange webChange) { + return DocumentChangePlatform( + _fromString(webChange.type), + webChange.oldIndex, + webChange.newIndex, + fromWebDocumentSnapshotToPlatformDocumentSnapshot( + webChange.doc, this._firestore)); + } + + DocumentChangeType _fromString(String item) { + switch (item.toLowerCase()) { + case _kChangeTypeAdded: + return DocumentChangeType.added; + case _kChangeTypeModified: + return DocumentChangeType.modified; + case _kChangeTypeRemoved: + return DocumentChangeType.removed; + default: + throw ArgumentError("Invalid type"); + } + } + + SnapshotMetadataPlatform _webMetadataToMetada( + web.SnapshotMetadata webMetadata) { + return SnapshotMetadataPlatform( + webMetadata.hasPendingWrites, + webMetadata.fromCache, + ); + } + + @override + Map get parameters => Map(); + + /// Returns a clean clone of this QueryWeb. + QueryWeb resetQueryDelegate() => + QueryWeb(firestore, pathComponents.join("/"), _webQuery); +} diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/transaction_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/transaction_web.dart new file mode 100644 index 000000000000..e93e0c8ef715 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/transaction_web.dart @@ -0,0 +1,65 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; +import 'package:firebase/firestore.dart' as web; + +import 'package:cloud_firestore_web/src/utils/codec_utility.dart'; +import 'package:cloud_firestore_web/src/utils/document_reference_utils.dart'; +import 'package:cloud_firestore_web/src/document_reference_web.dart'; + +/// A web specific for [Transaction] +class TransactionWeb extends TransactionPlatform { + final web.Transaction _webTransaction; + @override + FirestorePlatform firestore; + + /// Constructor. + TransactionWeb(this._webTransaction, this.firestore) : super(firestore); + + @override + Future delete(DocumentReferencePlatform documentReference) async { + assert(documentReference is DocumentReferenceWeb); + await _webTransaction + .delete((documentReference as DocumentReferenceWeb).delegate); + } + + @override + Future get( + DocumentReferencePlatform documentReference, + ) async { + assert(documentReference is DocumentReferenceWeb); + final webSnapshot = await _webTransaction + .get((documentReference as DocumentReferenceWeb).delegate); + return fromWebDocumentSnapshotToPlatformDocumentSnapshot( + webSnapshot, this.firestore); + } + + @override + Future set( + DocumentReferencePlatform documentReference, + Map data, + ) async { + assert(documentReference is DocumentReferenceWeb); + await _webTransaction.set( + (documentReference as DocumentReferenceWeb).delegate, + CodecUtility.encodeMapData(data)); + } + + @override + Future update( + DocumentReferencePlatform documentReference, + Map data, + ) async { + assert(documentReference is DocumentReferenceWeb); + await _webTransaction.update( + (documentReference as DocumentReferenceWeb).delegate, + data: CodecUtility.encodeMapData(data)); + } + + @override + Future finish() { + return Future.value(); + } +} diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/utils/codec_utility.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/utils/codec_utility.dart new file mode 100644 index 000000000000..1d7cd0c4c0af --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/utils/codec_utility.dart @@ -0,0 +1,88 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; +import 'package:firebase/firestore.dart' as web; + +import 'package:cloud_firestore_web/cloud_firestore_web.dart'; +import 'package:cloud_firestore_web/src/document_reference_web.dart'; +import 'package:cloud_firestore_web/src/field_value_web.dart'; + +/// Class containing static utility methods to encode/decode firestore data. +class CodecUtility { + /// Encodes a Map of values from their proper types to a serialized version. + static Map encodeMapData(Map data) { + if (data == null) { + return null; + } + Map output = Map.from(data); + output.updateAll((key, value) => valueEncode(value)); + return output; + } + + /// Encodes an Array of values from their proper types to a serialized version. + static List encodeArrayData(List data) { + if (data == null) { + return null; + } + return List.from(data).map(valueEncode).toList(); + } + + /// Encodes a value from its proper type to a serialized version. + static dynamic valueEncode(dynamic value) { + if (value is FieldValuePlatform) { + FieldValueWeb delegate = FieldValuePlatform.getDelegate(value); + return delegate.data; + } else if (value is Timestamp) { + return value.toDate(); + } else if (value is GeoPoint) { + return web.GeoPoint(value.latitude, value.longitude); + } else if (value is Blob) { + return web.Blob.fromUint8Array(value.bytes); + } else if (value is DocumentReferenceWeb) { + return value.delegate; + } else if (value is Map) { + return encodeMapData(value); + } else if (value is List) { + return encodeArrayData(value); + } + return value; + } + + /// Decodes the values on an incoming Map to their proper types. + static Map decodeMapData(Map data) { + if (data == null) { + return null; + } + Map output = Map.from(data); + output.updateAll((key, value) => valueDecode(value)); + return output; + } + + /// Decodes the values on an incoming Array to their proper types. + static List decodeArrayData(List data) { + if (data == null) { + return null; + } + return List.from(data).map(valueDecode).toList(); + } + + /// Decodes an incoming value to its proper type. + static dynamic valueDecode(dynamic value) { + if (value is web.GeoPoint) { + return GeoPoint(value.latitude, value.longitude); + } else if (value is DateTime) { + return Timestamp.fromDate(value); + } else if (value is web.Blob) { + return Blob(value.toUint8Array()); + } else if (value is web.DocumentReference) { + return (FirestorePlatform.instance as FirestoreWeb).document(value.path); + } else if (value is Map) { + return decodeMapData(value); + } else if (value is List) { + return decodeArrayData(value); + } + return value; + } +} diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/utils/document_reference_utils.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/utils/document_reference_utils.dart new file mode 100644 index 000000000000..e723c0025375 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/utils/document_reference_utils.dart @@ -0,0 +1,21 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; +import 'package:firebase/firestore.dart' as web; + +import 'package:cloud_firestore_web/src/utils/codec_utility.dart'; + +/// Builds [DocumentSnapshotPlatform] instance form web snapshot instance +DocumentSnapshotPlatform fromWebDocumentSnapshotToPlatformDocumentSnapshot( + web.DocumentSnapshot webSnapshot, FirestorePlatform firestore) { + return DocumentSnapshotPlatform( + webSnapshot.ref.path, + CodecUtility.decodeMapData(webSnapshot.data()), + SnapshotMetadataPlatform( + webSnapshot.metadata.hasPendingWrites, + webSnapshot.metadata.fromCache, + ), + firestore); +} diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/write_batch_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/write_batch_web.dart new file mode 100644 index 000000000000..7d76b20b8fda --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/write_batch_web.dart @@ -0,0 +1,51 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; +import 'package:firebase/firestore.dart' as web; + +import 'package:cloud_firestore_web/src/utils/codec_utility.dart'; +import 'package:cloud_firestore_web/src/document_reference_web.dart'; + +/// A web specific for [WriteBatch] +class WriteBatchWeb extends WriteBatchPlatform { + final web.WriteBatch _delegate; + + /// Constructor. + WriteBatchWeb(this._delegate); + + @override + Future commit() async { + await _delegate.commit(); + } + + @override + void delete(DocumentReferencePlatform document) { + assert(document is DocumentReferenceWeb); + _delegate.delete((document as DocumentReferenceWeb).delegate); + } + + @override + void setData( + DocumentReferencePlatform document, + Map data, { + bool merge = false, + }) { + assert(document is DocumentReferenceWeb); + _delegate.set( + (document as DocumentReferenceWeb).delegate, + CodecUtility.encodeMapData(data), + merge ? web.SetOptions(merge: merge) : null); + } + + @override + void updateData( + DocumentReferencePlatform document, + Map data, + ) { + assert(document is DocumentReferenceWeb); + _delegate.update((document as DocumentReferenceWeb).delegate, + data: CodecUtility.encodeMapData(data)); + } +} diff --git a/packages/cloud_firestore/cloud_firestore_web/pubspec.yaml b/packages/cloud_firestore/cloud_firestore_web/pubspec.yaml new file mode 100644 index 000000000000..1e7fa80cf26b --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/pubspec.yaml @@ -0,0 +1,35 @@ +name: cloud_firestore_web +description: The web implementation of cloud_firestore +homepage: https://github.com/FirebaseExtended/flutterfire/tree/master/packages/cloud_firestore/cloud_firestore_web +version: 0.1.0 + +flutter: + plugin: + platforms: + web: + pluginClass: FirestoreWeb + fileName: firestore_web.dart + +dependencies: + flutter: + sdk: flutter + flutter_web_plugins: + sdk: flutter + firebase: ^7.0.0 + http_parser: ^3.1.3 + meta: ^1.1.7 + firebase_core: ^0.4.3+1 + cloud_firestore_platform_interface: "^1.0.0" + js: ^0.6.1 + +dev_dependencies: + flutter_test: + sdk: flutter + firebase_core_platform_interface: ^1.0.0 + firebase_core_web: ^0.1.1 + mockito: ^4.1.1 + +environment: + sdk: ">=2.1.0 <3.0.0" + flutter: ">=1.12.13+hotfix.4 <2.0.0" + diff --git a/packages/cloud_firestore/cloud_firestore_web/test/codec_utility_test.dart b/packages/cloud_firestore/cloud_firestore_web/test/codec_utility_test.dart new file mode 100644 index 000000000000..ea4c1783cb4b --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/test/codec_utility_test.dart @@ -0,0 +1,194 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +@TestOn("chrome") +import 'dart:typed_data'; +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'dart:js' as js; +import 'package:firebase/firestore.dart' as web; + +import 'package:cloud_firestore_web/src/utils/codec_utility.dart'; +import 'package:cloud_firestore_web/src/document_reference_web.dart'; +import 'package:cloud_firestore_web/src/field_value_factory_web.dart'; + +class MockGeoPoint extends Mock implements GeoPoint {} + +class MockBlob extends Mock implements Blob {} + +class MockDocumentReferenceWeb extends Mock implements DocumentReferenceWeb {} + +class MockWebGeoPoint extends Mock implements web.GeoPoint {} + +class MockWebBlob extends Mock implements web.Blob {} + +void main() { + group("$CodecUtility()", () { + final FieldValuePlatform mockFieldValue = + FieldValuePlatform(FieldValueFactoryWeb().increment(2.0)); + + setUp(() { + js.context['firebase'] = js.JsObject.jsify({ + 'firestore': js.JsObject.jsify({ + 'GeoPoint': + js.allowInterop((latitude, longitude) => MockWebGeoPoint()), + 'Blob': js.JsObject.jsify({ + 'fromUint8Array': js.allowInterop((_) => MockWebBlob()) + }) + }) + }); + }); + + test("encodeMapData", () { + expect(CodecUtility.encodeMapData(null), isNull); + //FieldValuePlatform + CodecUtility.encodeMapData({'test': mockFieldValue}); + + final timeStamp = Timestamp.now(); + final result = CodecUtility.encodeMapData({'test': timeStamp}); + expect(result['test'], isInstanceOf()); + + //GeoPoint + final mockGeoPoint = MockGeoPoint(); + CodecUtility.encodeMapData({'test': mockGeoPoint}); + verify(mockGeoPoint.latitude); + verify(mockGeoPoint.longitude); + + //Blob + final mockBlob = MockBlob(); + CodecUtility.encodeMapData({'test': mockBlob}); + verify(mockBlob.bytes); + + //DocumentReferenceWeb + final mockDocumentReferenceWeb = MockDocumentReferenceWeb(); + CodecUtility.encodeMapData({'test': mockDocumentReferenceWeb}); + verify(mockDocumentReferenceWeb.delegate); + + //Map + reset(mockDocumentReferenceWeb); + CodecUtility.encodeMapData({ + 'test': {'test2': mockDocumentReferenceWeb} + }); + verify(mockDocumentReferenceWeb.delegate); + + //List + reset(mockDocumentReferenceWeb); + CodecUtility.encodeMapData({ + 'test': [mockDocumentReferenceWeb] + }); + verify(mockDocumentReferenceWeb.delegate); + }); + + test("encodeArrayData", () { + expect(CodecUtility.encodeArrayData(null), isNull); + + //FieldValuePlatform + CodecUtility.encodeArrayData([mockFieldValue]); + + final timeStamp = Timestamp.now(); + final result = CodecUtility.encodeArrayData([timeStamp]); + expect(result.first, isInstanceOf()); + + //GeoPoint + final mockGeoPoint = MockGeoPoint(); + CodecUtility.encodeArrayData([mockGeoPoint]); + verify(mockGeoPoint.latitude); + verify(mockGeoPoint.longitude); + + //Blob + final mockBlob = MockBlob(); + CodecUtility.encodeArrayData([mockBlob]); + verify(mockBlob.bytes); + + //DocumentReferenceWeb + final mockDocumentReferenceWeb = MockDocumentReferenceWeb(); + CodecUtility.encodeArrayData([mockDocumentReferenceWeb]); + verify(mockDocumentReferenceWeb.delegate); + + //Map + reset(mockDocumentReferenceWeb); + CodecUtility.encodeArrayData([ + {'test2': mockDocumentReferenceWeb} + ]); + verify(mockDocumentReferenceWeb.delegate); + + //List + reset(mockDocumentReferenceWeb); + CodecUtility.encodeArrayData([ + [mockDocumentReferenceWeb] + ]); + verify(mockDocumentReferenceWeb.delegate); + }); + + test("decodeMapData", () { + expect(CodecUtility.decodeMapData(null), isNull); + + //Blob + final mockWebBlob = MockWebBlob(); + when(mockWebBlob.toUint8Array()).thenReturn(Uint8List(0)); + expect(CodecUtility.decodeMapData({'test': mockWebBlob})['test'], + isInstanceOf()); + verify(mockWebBlob.toUint8Array()); + + final date = DateTime.now(); + expect(CodecUtility.decodeMapData({'test': date})['test'], + isInstanceOf()); + + //GeoPoint + final mockWebGeoPoint = MockWebGeoPoint(); + expect(CodecUtility.decodeMapData({'test': mockWebGeoPoint})['test'], + isInstanceOf()); + + //Map + expect( + CodecUtility.decodeMapData({ + 'test': {'test1': mockWebGeoPoint} + })['test']['test1'], + isInstanceOf()); + + //List + expect( + CodecUtility.decodeMapData({ + 'test': [mockWebGeoPoint] + })['test'] + .first, + isInstanceOf()); + }); + + test("decodeArrayData", () { + expect(CodecUtility.decodeArrayData(null), isNull); + + //Blob + final mockWebBlob = MockWebBlob(); + when(mockWebBlob.toUint8Array()).thenReturn(Uint8List(0)); + expect(CodecUtility.decodeArrayData([mockWebBlob]).first, + isInstanceOf()); + verify(mockWebBlob.toUint8Array()); + + final date = DateTime.now(); + expect(CodecUtility.decodeArrayData([date]).first, + isInstanceOf()); + + //GeoPoint + final mockWebGeoPoint = MockWebGeoPoint(); + expect(CodecUtility.decodeArrayData([mockWebGeoPoint]).first, + isInstanceOf()); + + //Map + expect( + CodecUtility.decodeArrayData([ + {'test1': mockWebGeoPoint} + ]).first['test1'], + isInstanceOf()); + + //List + expect( + CodecUtility.decodeArrayData([ + [mockWebGeoPoint] + ]).first.first, + isInstanceOf()); + }); + }); +} diff --git a/packages/cloud_firestore/cloud_firestore_web/test/collection_reference_web_test.dart b/packages/cloud_firestore/cloud_firestore_web/test/collection_reference_web_test.dart new file mode 100644 index 000000000000..6b2c8c19973a --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/test/collection_reference_web_test.dart @@ -0,0 +1,152 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +@TestOn('chrome') +import 'dart:js' as js; + +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; +import 'package:cloud_firestore_web/src/collection_reference_web.dart'; +import 'package:cloud_firestore_web/src/document_reference_web.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'test_common.dart'; + +void main() { + group("$CollectionReferenceWeb()", () { + final mockDocumentReference = MockWebDocumentReference(); + final mockCollectionReference = MockWebCollectionReference(); + final anotherMockDocumentReference = MockWebDocumentReference(); + CollectionReferenceWeb collectionReference; + setUp(() { + final mockFirestoreWeb = mockFirestore(); + collectionReference = CollectionReferenceWeb(FirestorePlatform.instance, + js.context['firebase']['firestore'](""), [kCollectionId]); + collectionReference.queryDelegate = MockQueryWeb(); + when(mockFirestoreWeb.doc(any)).thenReturn(mockDocumentReference); + + // Used for document creation... + when(mockFirestoreWeb.collection(any)) + .thenReturn(mockCollectionReference); + when(mockCollectionReference.doc(any)).thenReturn(mockDocumentReference); + when(mockCollectionReference.doc(null)) + .thenReturn(anotherMockDocumentReference); + + when(anotherMockDocumentReference.path).thenReturn("test/asdf"); + + when(collectionReference.queryDelegate.resetQueryDelegate()) + .thenReturn(collectionReference.queryDelegate); + }); + + test("parent", () { + expect(collectionReference.parent(), isNull); + expect( + CollectionReferenceWeb( + FirestorePlatform.instance, + js.context['firebase']['firestore'](""), + [kCollectionId, kCollectionId, kCollectionId]).parent(), + isInstanceOf()); + }); + + test("document", () { + final newDocument = collectionReference.document(); + expect(newDocument.path.split("/").length, + collectionReference.pathComponents.length + 1); + final newDocumentWithPath = collectionReference.document("test1"); + expect(newDocumentWithPath.path, + equals("${collectionReference.path}/test1")); + }); + + test("add", () async { + expect(await collectionReference.add({}), + isInstanceOf()); + }); + + test("buildArguments", () async { + collectionReference.buildArguments(); + verify(collectionReference.queryDelegate.buildArguments()); + }); + + test("getDocuments", () async { + await collectionReference.getDocuments(); + verify(collectionReference.queryDelegate.getDocuments()); + }); + + test("reference", () async { + collectionReference.reference(); + verify(collectionReference.queryDelegate.reference()); + }); + + test("snapshots", () async { + collectionReference.snapshots(includeMetadataChanges: true); + verify(collectionReference.queryDelegate + .snapshots(includeMetadataChanges: true)); + collectionReference.snapshots(includeMetadataChanges: false); + verify(collectionReference.queryDelegate + .snapshots(includeMetadataChanges: false)); + }); + + test("where", () async { + collectionReference.where("test"); + verify(collectionReference.queryDelegate.where("test")); + }); + + test("startAt", () async { + collectionReference.startAt([]); + verify(collectionReference.queryDelegate.startAt([])); + }); + + test("startAfter", () async { + collectionReference.startAfter([]); + verify(collectionReference.queryDelegate.startAfter([])); + }); + + test("endBefore", () async { + collectionReference.endBefore([]); + verify(collectionReference.queryDelegate.endBefore([])); + }); + + test("endAt", () async { + collectionReference.endAt([]); + verify(collectionReference.queryDelegate.endAt([])); + }); + + test("limit", () async { + collectionReference.limit(1); + verify(collectionReference.queryDelegate.limit(1)); + }); + + test("orderBy", () async { + collectionReference.orderBy("test"); + verify(collectionReference.queryDelegate.orderBy("test")); + collectionReference.orderBy("test", descending: true); + verify( + collectionReference.queryDelegate.orderBy("test", descending: true)); + }); + + test("startAfterDocument", () async { + final mockSnapshot = MockDocumentSnapshot(); + collectionReference.startAfterDocument(mockSnapshot); + verify( + collectionReference.queryDelegate.startAfterDocument(mockSnapshot)); + }); + + test("startAtDocument", () async { + final mockSnapshot = MockDocumentSnapshot(); + collectionReference.startAtDocument(mockSnapshot); + verify(collectionReference.queryDelegate.startAtDocument(mockSnapshot)); + }); + + test("endBeforeDocument", () async { + final mockSnapshot = MockDocumentSnapshot(); + collectionReference.endBeforeDocument(mockSnapshot); + verify(collectionReference.queryDelegate.endBeforeDocument(mockSnapshot)); + }); + + test("endAtDocument", () async { + final mockSnapshot = MockDocumentSnapshot(); + collectionReference.endAtDocument(mockSnapshot); + verify(collectionReference.queryDelegate.endAtDocument(mockSnapshot)); + }); + }); +} diff --git a/packages/cloud_firestore/cloud_firestore_web/test/document_reference_web_test.dart b/packages/cloud_firestore/cloud_firestore_web/test/document_reference_web_test.dart new file mode 100644 index 000000000000..327b25516a38 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/test/document_reference_web_test.dart @@ -0,0 +1,78 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +@TestOn('chrome') +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; +import 'package:cloud_firestore_web/src/document_reference_web.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:firebase/firestore.dart' as web; +import 'test_common.dart'; + +const _kPath = "test/document"; + +void main() { + group("$DocumentReferenceWeb()", () { + final mockWebDocumentReferences = MockWebDocumentReference(); + DocumentReferenceWeb documentRefernce; + setUp(() { + final mockWebFirestore = mockFirestore(); + when(mockWebFirestore.doc(any)).thenReturn(mockWebDocumentReferences); + documentRefernce = DocumentReferenceWeb( + mockWebFirestore, FirestorePlatform.instance, _kPath.split("/")); + }); + + test("setData", () { + documentRefernce.setData({"test": "test"}); + expect( + verify(mockWebDocumentReferences.set( + any, captureThat(isInstanceOf()))) + .captured + .last + .merge, + isFalse); + documentRefernce.setData({"test": "test"}, merge: true); + expect( + verify(mockWebDocumentReferences.set( + any, captureThat(isInstanceOf()))) + .captured + .last + .merge, + isTrue); + }); + + test("updateData", () { + documentRefernce.updateData({"test": "test"}); + verify(mockWebDocumentReferences.update(data: anyNamed("data"))); + }); + + test("get", () { + final mockWebSnapshot = MockWebDocumentSnapshot(); + when(mockWebSnapshot.ref).thenReturn(mockWebDocumentReferences); + when(mockWebSnapshot.metadata).thenReturn(MockWebSnapshotMetaData()); + when(mockWebDocumentReferences.get()) + .thenAnswer((_) => Future.value(mockWebSnapshot)); + documentRefernce.get(); + verify(mockWebDocumentReferences.get()); + }); + + test("delete", () { + documentRefernce.delete(); + verify(mockWebDocumentReferences.delete()); + }); + + test("snapshots", () { + when(mockWebDocumentReferences.onSnapshot) + .thenAnswer((_) => Stream.empty()); + when(mockWebDocumentReferences.onMetadataChangesSnapshot) + .thenAnswer((_) => Stream.empty()); + documentRefernce.snapshots(); + verify(mockWebDocumentReferences.onSnapshot); + documentRefernce.snapshots(includeMetadataChanges: false); + verify(mockWebDocumentReferences.onSnapshot); + documentRefernce.snapshots(includeMetadataChanges: true); + verify(mockWebDocumentReferences.onMetadataChangesSnapshot); + }); + }); +} diff --git a/packages/cloud_firestore/cloud_firestore_web/test/field_value_factory_web_test.dart b/packages/cloud_firestore/cloud_firestore_web/test/field_value_factory_web_test.dart new file mode 100644 index 000000000000..566c2ab39938 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/test/field_value_factory_web_test.dart @@ -0,0 +1,52 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +@TestOn("chrome") +import 'package:firebase/firestore.dart' as web show FieldValue; +import 'package:cloud_firestore_web/src/field_value_factory_web.dart'; +import 'package:cloud_firestore_web/src/field_value_web.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group("$FieldValueFactoryWeb()", () { + final factory = FieldValueFactoryWeb(); + + test("arrayRemove", () { + final FieldValueWeb actual = factory.arrayRemove([]); + expect(actual.data, isInstanceOf()); + }); + + test("arrayUnion", () { + final FieldValueWeb actual = factory.arrayUnion([]); + expect(actual.data, isInstanceOf()); + }); + + test("delete", () { + final FieldValueWeb actual = factory.delete(); + expect(actual.data, isInstanceOf()); + }); + + test("increment", () { + final FieldValueWeb actualInt = factory.increment(1); + expect(actualInt.data, isInstanceOf()); + + final FieldValueWeb actualDouble = factory.increment(1.25); + expect(actualDouble.data, isInstanceOf()); + }); + + test( + "increment throws when attempting to increment something that is not a number", + () { + expect(() { + dynamic malformed = "nope"; + factory.increment(malformed); + }, throwsA(isA())); + }); + + test("serverTimestamp", () { + final FieldValueWeb actual = factory.serverTimestamp(); + expect(actual.data, isInstanceOf()); + }); + }); +} diff --git a/packages/cloud_firestore/cloud_firestore_web/test/query_web_test.dart b/packages/cloud_firestore/cloud_firestore_web/test/query_web_test.dart new file mode 100644 index 000000000000..8f57c3264eae --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/test/query_web_test.dart @@ -0,0 +1,186 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +@TestOn("chrome") +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; +import 'package:cloud_firestore_web/cloud_firestore_web.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:firebase/firestore.dart' as web; +import 'package:cloud_firestore_web/src/query_web.dart'; +import 'test_common.dart'; + +class MockWebQuery extends Mock implements web.Query {} + +class MockFirestoreWeb extends Mock implements FirestoreWeb {} + +class MockWebQuerySnapshot extends Mock implements web.QuerySnapshot {} + +class MockWebSnapshotMetadata extends Mock implements web.SnapshotMetadata {} + +class MockWebDocumentChange extends Mock implements web.DocumentChange {} + +const _path = "test/query"; + +void main() { + group("$QueryWeb()", () { + final firestore = MockFirestoreWeb(); + final MockWebQuery mockWebQuery = MockWebQuery(); + QueryWeb query; + + setUp(() { + reset(mockWebQuery); + query = QueryWeb(firestore, _path, mockWebQuery); + }); + + test("snapshots", () { + when(mockWebQuery.onSnapshot).thenAnswer((_) => Stream.empty()); + when(mockWebQuery.onSnapshotMetadata).thenAnswer((_) => Stream.empty()); + query.snapshots(); + verify(mockWebQuery.onSnapshot); + query.snapshots(includeMetadataChanges: false); + verify(mockWebQuery.onSnapshot); + query.snapshots(includeMetadataChanges: true); + verify(mockWebQuery.onSnapshotMetadata); + }); + + test("getDocuments", () async { + final mockMetaData = MockWebSnapshotMetadata(); + when(mockMetaData.fromCache).thenReturn(true); + when(mockMetaData.hasPendingWrites).thenReturn(false); + + final mockDocumentReference = MockWebDocumentReference(); + when(mockDocumentReference.path).thenReturn("test/reference"); + + final mockDocumentSnapshot = MockWebDocumentSnapshot(); + when(mockDocumentSnapshot.ref).thenReturn(mockDocumentReference); + when(mockDocumentSnapshot.data()).thenReturn(Map()); + when(mockDocumentSnapshot.metadata).thenReturn(mockMetaData); + + final mockDocumentChange = MockWebDocumentChange(); + when(mockDocumentChange.type).thenReturn("added"); + when(mockDocumentChange.oldIndex).thenReturn(0); + when(mockDocumentChange.newIndex).thenReturn(1); + when(mockDocumentChange.doc).thenReturn(mockDocumentSnapshot); + + final mockQuerySnapshot = MockWebQuerySnapshot(); + when(mockQuerySnapshot.docs).thenReturn([]); + when(mockQuerySnapshot.docChanges()).thenReturn([mockDocumentChange]); + when(mockQuerySnapshot.metadata).thenReturn(mockMetaData); + + when(mockWebQuery.get()) + .thenAnswer((_) => Future.value(mockQuerySnapshot)); + final actual = await query.getDocuments(); + verify(mockWebQuery.get()); + expect( + actual.documentChanges.first.type, equals(DocumentChangeType.added)); + + when(mockDocumentChange.type).thenReturn("modified"); + expect((await query.getDocuments()).documentChanges.first.type, + equals(DocumentChangeType.modified)); + + when(mockDocumentChange.type).thenReturn("removed"); + expect((await query.getDocuments()).documentChanges.first.type, + equals(DocumentChangeType.removed)); + }); + + test("endAt", () { + query.endAt([]); + verify(mockWebQuery.endAt(fieldValues: anyNamed("fieldValues"))); + }); + + test("endAtDocument", () { + final mockDocumentSnapshot = MockDocumentSnapshot(); + when(mockDocumentSnapshot.data).thenReturn({ + 'test': 1, + }); + query.orderBy("test"); + query.endAtDocument(mockDocumentSnapshot); + verify(mockWebQuery.endAt( + fieldValues: argThat(equals([1]), named: "fieldValues"))); + }); + + test("endBefore", () { + query.endBefore([]); + verify(mockWebQuery.endBefore(fieldValues: anyNamed("fieldValues"))); + }); + + test("endBeforeDocument", () { + final mockDocumentSnapshot = MockDocumentSnapshot(); + when(mockDocumentSnapshot.data).thenReturn({ + 'test': 1, + }); + query.orderBy("test"); + query.endBeforeDocument(mockDocumentSnapshot); + verify(mockWebQuery.endBefore( + fieldValues: argThat(equals([1]), named: "fieldValues"))); + }); + + test("startAfter", () { + query.startAfter([]); + verify(mockWebQuery.startAfter(fieldValues: anyNamed("fieldValues"))); + }); + + test("startAfterDocument", () { + final mockDocumentSnapshot = MockDocumentSnapshot(); + when(mockDocumentSnapshot.data).thenReturn({ + 'test': 1, + }); + query.orderBy("test"); + query.startAfterDocument(mockDocumentSnapshot); + verify(mockWebQuery.startAfter( + fieldValues: argThat(equals([1]), named: "fieldValues"))); + }); + + test("startAt", () { + query.startAt([]); + verify(mockWebQuery.startAt(fieldValues: anyNamed("fieldValues"))); + }); + + test("startAtDocument", () { + final mockDocumentSnapshot = MockDocumentSnapshot(); + when(mockDocumentSnapshot.data).thenReturn({ + 'test': 1, + }); + query.orderBy("test"); + query.startAtDocument(mockDocumentSnapshot); + verify(mockWebQuery.startAt( + fieldValues: argThat(equals([1]), named: "fieldValues"))); + }); + + test("limit", () { + query.limit(1); + verify(mockWebQuery.limit(1)); + }); + + test("where", () { + query.where("test", isNull: true); + verify(mockWebQuery.where("test", "==", null)); + + query.where("test", whereIn: [1, 2, 3]); + verify(mockWebQuery.where("test", "in", [1, 2, 3])); + + query.where("test", arrayContainsAny: [1, 2, 3]); + verify(mockWebQuery.where("test", "array-contains-any", [1, 2, 3])); + + query.where("test", arrayContains: [1, 2, 3]); + verify(mockWebQuery.where("test", "array-contains", [1, 2, 3])); + + query.where("test", isGreaterThanOrEqualTo: 1); + verify(mockWebQuery.where("test", ">=", 1)); + + query.where("test", isGreaterThan: 1); + verify(mockWebQuery.where("test", ">", 1)); + + query.where("test", isLessThan: 1); + verify(mockWebQuery.where("test", "<", 1)); + + query.where("test", isLessThanOrEqualTo: 1); + verify(mockWebQuery.where("test", "<=", 1)); + + query.where("test", isEqualTo: 1); + verify(mockWebQuery.where("test", "==", 1)); + }); + }); +} diff --git a/packages/cloud_firestore/cloud_firestore_web/test/test_common.dart b/packages/cloud_firestore/cloud_firestore_web/test/test_common.dart new file mode 100644 index 000000000000..d95186e6551f --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/test/test_common.dart @@ -0,0 +1,56 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:js' as js; +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; +import 'package:cloud_firestore_web/cloud_firestore_web.dart'; +import 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart'; +import 'package:firebase_core_web/firebase_core_web.dart'; +import 'package:mockito/mockito.dart'; +import 'package:firebase/firestore.dart' as web; + +import 'package:cloud_firestore_web/src/document_reference_web.dart'; +import 'package:cloud_firestore_web/src/query_web.dart'; + +const kCollectionId = "test"; + +class MockWebDocumentSnapshot extends Mock implements web.DocumentSnapshot {} + +class MockWebSnapshotMetaData extends Mock implements web.SnapshotMetadata {} + +class MockFirestoreWeb extends Mock implements web.Firestore {} + +class MockWebTransaction extends Mock implements web.Transaction {} + +class MockWebWriteBatch extends Mock implements web.WriteBatch {} + +class MockDocumentReference extends Mock implements DocumentReferenceWeb {} + +class MockFirestore extends Mock implements FirestoreWeb {} + +class MockWebDocumentReference extends Mock implements web.DocumentReference {} + +class MockWebCollectionReference extends Mock + implements web.CollectionReference {} + +class MockQueryWeb extends Mock implements QueryWeb {} + +class MockDocumentSnapshot extends Mock implements DocumentSnapshotPlatform {} + +web.Firestore mockFirestore() { + final mockFirestoreWeb = MockFirestoreWeb(); + final js.JsObject firebaseMock = js.JsObject.jsify({ + 'firestore': js.allowInterop((_) => mockFirestoreWeb), + 'app': js.allowInterop((String name) { + return js.JsObject.jsify({ + 'name': name, + 'options': {'appId': '123'}, + }); + }) + }); + js.context['firebase'] = firebaseMock; + FirebaseCorePlatform.instance = FirebaseCoreWeb(); + FirestorePlatform.instance = FirestoreWeb(); + return mockFirestoreWeb; +} diff --git a/packages/cloud_firestore/cloud_firestore_web/test/transaction_web_test.dart b/packages/cloud_firestore/cloud_firestore_web/test/transaction_web_test.dart new file mode 100644 index 000000000000..e7f243862cbe --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/test/transaction_web_test.dart @@ -0,0 +1,56 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +@TestOn("chrome") +import 'package:cloud_firestore_web/src/transaction_web.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'test_common.dart'; + +void main() { + group("$TransactionWeb()", () { + final mockWebTransaction = MockWebTransaction(); + final mockWebDocumentReference = MockWebDocumentReference(); + final mockDocumentReference = MockDocumentReference(); + final mockFirestore = MockFirestore(); + final transaction = TransactionWeb(mockWebTransaction, mockFirestore); + + setUp(() { + when(mockDocumentReference.delegate).thenReturn(mockWebDocumentReference); + }); + + test("delete", () async { + await transaction.delete(mockDocumentReference); + verify(mockWebTransaction.delete(mockWebDocumentReference)); + }); + + test("get", () async { + final mockWebSnapshot = MockWebDocumentSnapshot(); + final mockWebDocumentReference = MockWebDocumentReference(); + final mockWebSnapshotMetaData = MockWebSnapshotMetaData(); + when(mockWebSnapshotMetaData.hasPendingWrites).thenReturn(true); + when(mockWebSnapshotMetaData.fromCache).thenReturn(true); + when(mockWebDocumentReference.path).thenReturn("test/path"); + when(mockWebSnapshot.ref).thenReturn(mockWebDocumentReference); + when(mockWebSnapshot.data()).thenReturn(Map()); + when(mockWebSnapshot.metadata).thenReturn(mockWebSnapshotMetaData); + + when(mockWebTransaction.get(any)) + .thenAnswer((_) => Future.value(mockWebSnapshot)); + await transaction.get(mockDocumentReference); + verify(mockWebTransaction.get(any)); + }); + + test("set", () async { + await transaction.set(mockDocumentReference, {}); + verify(mockWebTransaction.set(mockWebDocumentReference, {})); + }); + + test("update", () async { + await transaction.update(mockDocumentReference, {}); + verify(mockWebTransaction.update(mockWebDocumentReference, + data: argThat(equals({}), named: "data"))); + }); + }); +} diff --git a/packages/cloud_firestore/cloud_firestore_web/test/write_batch_web_test.dart b/packages/cloud_firestore/cloud_firestore_web/test/write_batch_web_test.dart new file mode 100644 index 000000000000..b947386de664 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/test/write_batch_web_test.dart @@ -0,0 +1,38 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +@TestOn("chrome") +import 'package:cloud_firestore_web/src/write_batch_web.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'test_common.dart'; + +void main() { + group("$WriteBatchWeb()", () { + final mockWebTransaction = MockWebWriteBatch(); + final mockWebDocumentReference = MockWebDocumentReference(); + final mockDocumentReference = MockDocumentReference(); + final transaction = WriteBatchWeb(mockWebTransaction); + + setUp(() { + when(mockDocumentReference.delegate).thenReturn(mockWebDocumentReference); + }); + + test("delete", () async { + await transaction.delete(mockDocumentReference); + verify(mockWebTransaction.delete(mockWebDocumentReference)); + }); + + test("setData", () async { + await transaction.setData(mockDocumentReference, {}); + verify(mockWebTransaction.set(mockWebDocumentReference, {}, null)); + }); + + test("updateData", () async { + await transaction.updateData(mockDocumentReference, {}); + verify(mockWebTransaction.update(mockWebDocumentReference, + data: argThat(equals({}), named: "data"))); + }); + }); +}