blob: 27a329dcfb1d4bc3e92d9b4ae714a5ba7fe96e1d [file]
// Copyright 2017 The Chromium Authors. 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:async';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/services.dart';
import 'package:test/test.dart';
void main() {
group('$FirebaseDatabase', () {
const MethodChannel channel = const MethodChannel(
'plugins.flutter.io/firebase_database',
);
int mockHandleId = 0;
final List<MethodCall> log = <MethodCall>[];
final FirebaseDatabase database = FirebaseDatabase.instance;
setUp(() async {
channel.setMockMethodCallHandler((MethodCall methodCall) async {
log.add(methodCall);
switch (methodCall.method) {
case 'Query#observe':
return mockHandleId++;
case 'FirebaseDatabase#setPersistenceEnabled':
return true;
case 'FirebaseDatabase#setPersistenceCacheSizeBytes':
return true;
default:
return null;
}
});
log.clear();
});
test('setPersistenceEnabled', () async {
expect(await database.setPersistenceEnabled(false), true);
expect(await database.setPersistenceEnabled(true), true);
expect(
log,
equals(<MethodCall>[
const MethodCall('FirebaseDatabase#setPersistenceEnabled', false),
const MethodCall('FirebaseDatabase#setPersistenceEnabled', true),
]),
);
});
test('setPersistentCacheSizeBytes', () async {
expect(await database.setPersistenceCacheSizeBytes(42), true);
expect(
log,
equals(<MethodCall>[
const MethodCall('FirebaseDatabase#setPersistenceCacheSizeBytes', 42),
]),
);
});
test('goOnline', () async {
await database.goOnline();
expect(
log,
equals(<MethodCall>[
const MethodCall('FirebaseDatabase#goOnline'),
]),
);
});
test('goOffline', () async {
await database.goOffline();
expect(
log,
equals(<MethodCall>[
const MethodCall('FirebaseDatabase#goOffline'),
]),
);
});
test('purgeOutstandingWrites', () async {
await database.purgeOutstandingWrites();
expect(
log,
equals(<MethodCall>[
const MethodCall('FirebaseDatabase#purgeOutstandingWrites'),
]),
);
});
group('$DatabaseReference', () {
test('set', () async {
final dynamic value = <String, dynamic>{'hello': 'world'};
final int priority = 42;
await database.reference().child('foo').set(value);
await database.reference().child('bar').set(value, priority: priority);
expect(
log,
equals(<MethodCall>[
new MethodCall(
'DatabaseReference#set',
<String, dynamic>{
'path': 'foo',
'value': value,
'priority': null
},
),
new MethodCall(
'DatabaseReference#set',
<String, dynamic>{
'path': 'bar',
'value': value,
'priority': priority
},
),
]),
);
});
test('update', () async {
final dynamic value = <String, dynamic>{'hello': 'world'};
await database.reference().child("foo").update(value);
expect(
log,
equals(<MethodCall>[
new MethodCall(
'DatabaseReference#update',
<String, dynamic>{'path': 'foo', 'value': value},
),
]),
);
});
test('setPriority', () async {
final int priority = 42;
await database.reference().child('foo').setPriority(priority);
expect(
log,
equals(<MethodCall>[
new MethodCall(
'DatabaseReference#setPriority',
<String, dynamic>{'path': 'foo', 'priority': priority},
),
]),
);
});
});
group('$Query', () {
// TODO(jackson): Write more tests for queries
test('keepSynced, simple query', () async {
final String path = 'foo';
final Query query = database.reference().child(path);
await query.keepSynced(true);
expect(
log,
equals(<MethodCall>[
new MethodCall(
'Query#keepSynced',
<String, dynamic>{
'path': path,
'parameters': <String, dynamic>{},
'value': true
},
),
]),
);
});
test('keepSynced, complex query', () async {
final int startAt = 42;
final String path = 'foo';
final String childKey = 'bar';
final bool endAt = true;
final String endAtKey = 'baz';
final Query query = database
.reference()
.child(path)
.orderByChild(childKey)
.startAt(startAt)
.endAt(endAt, key: endAtKey);
await query.keepSynced(false);
final Map<String, dynamic> expectedParameters = <String, dynamic>{
'orderBy': 'child',
'orderByChildKey': childKey,
'startAt': startAt,
'endAt': endAt,
'endAtKey': endAtKey,
};
expect(
log,
equals(<MethodCall>[
new MethodCall(
'Query#keepSynced',
<String, dynamic>{
'path': path,
'parameters': expectedParameters,
'value': false
},
),
]),
);
});
test('observing value events', () async {
mockHandleId = 87;
final String path = 'foo';
final Query query = database.reference().child(path);
Future<Null> simulateEvent(String value) async {
await BinaryMessages.handlePlatformMessage(
channel.name,
channel.codec.encodeMethodCall(
new MethodCall('Event', <String, dynamic>{
'handle': 87,
'snapshot': <String, dynamic>{
'key': path,
'value': value,
},
}),
),
(_) {},
);
}
final AsyncQueue<Event> events = new AsyncQueue<Event>();
// Subscribe and allow subscription to complete.
final StreamSubscription<Event> subscription =
query.onValue.listen(events.add);
await new Future<Null>.delayed(const Duration(seconds: 0));
await simulateEvent('1');
await simulateEvent('2');
final Event event1 = await events.remove();
final Event event2 = await events.remove();
expect(event1.snapshot.key, path);
expect(event1.snapshot.value, '1');
expect(event2.snapshot.key, path);
expect(event2.snapshot.value, '2');
// Cancel subscription and allow cancellation to complete.
subscription.cancel();
await new Future<Null>.delayed(const Duration(seconds: 0));
expect(
log,
equals(<MethodCall>[
new MethodCall(
'Query#observe',
<String, dynamic>{
'path': path,
'parameters': <String, dynamic>{},
'eventType': '_EventType.value'
},
),
new MethodCall(
'Query#removeObserver',
<String, dynamic>{
'path': path,
'parameters': <String, dynamic>{},
'handle': 87,
},
),
]),
);
});
});
});
}
/// Queue whose remove operation is asynchronous, awaiting a corresponding add.
class AsyncQueue<T> {
Map<int, Completer<T>> _completers = <int, Completer<T>>{};
int _nextToRemove = 0;
int _nextToAdd = 0;
void add(T element) {
_completer(_nextToAdd++).complete(element);
}
Future<T> remove() {
final Future<T> result = _completer(_nextToRemove++).future;
return result;
}
Completer<T> _completer(int index) {
if (_completers.containsKey(index)) {
return _completers.remove(index);
} else {
return _completers[index] = new Completer<T>();
}
}
}