removed old implementation
This commit is contained in:
@@ -1,51 +0,0 @@
|
||||
import 'package:fl_query/src/core/core.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
Uuid uuid = Uuid();
|
||||
|
||||
QueryKey queryKey() {
|
||||
return QueryKey("query_${uuid.v4()}");
|
||||
}
|
||||
|
||||
class SpyFn<T extends Function()> {
|
||||
int _calls = 0;
|
||||
late T _customFn;
|
||||
int get calls => _calls;
|
||||
T get customFn => _customFn;
|
||||
|
||||
SpyFn();
|
||||
|
||||
SpyFn.withFn(this._customFn);
|
||||
|
||||
fn([Function()? cb]) {
|
||||
_calls++;
|
||||
return () => cb?.call();
|
||||
}
|
||||
|
||||
fn1([Function()? cb]) {
|
||||
_calls++;
|
||||
return (p0) => cb?.call();
|
||||
}
|
||||
|
||||
fn2([Function()? cb]) {
|
||||
_calls++;
|
||||
return (p0, p1) => cb?.call();
|
||||
}
|
||||
|
||||
fn3([Function()? cb]) {
|
||||
_calls++;
|
||||
return (p0, p1, p2) => cb?.call();
|
||||
}
|
||||
|
||||
fn4([Function()? cb]) {
|
||||
_calls++;
|
||||
return (p0, p1, p2, p3) => cb?.call();
|
||||
}
|
||||
|
||||
fn5([Function()? cb]) {
|
||||
_calls++;
|
||||
return (p0, p1, p2, p3, p4) => cb?.call();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> sleep(int ms) => Future.delayed(Duration(milliseconds: ms));
|
||||
@@ -1,68 +0,0 @@
|
||||
import 'package:fl_query/src/core/notify_manager.dart';
|
||||
import 'package:test/expect.dart';
|
||||
import 'package:test/scaffolding.dart';
|
||||
|
||||
class SpyNotifyManager extends NotifyManager {
|
||||
SpyNotifyManager() : super();
|
||||
|
||||
int flushCall = 0;
|
||||
|
||||
@override
|
||||
void flush() {
|
||||
super.flush();
|
||||
flushCall++;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
group("NotifyManager", () {
|
||||
test(
|
||||
"Should call _notifyFn in schedule When no callback is batched",
|
||||
() async {
|
||||
final NotifyManager notifyManager = NotifyManager();
|
||||
int called = 0;
|
||||
notifyManager.schedule(() => called++);
|
||||
await Future.delayed(Duration(milliseconds: 1));
|
||||
expect(called, equals(1));
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
"Should call default _batchNotifyFn even When multiple level deep callbacks are registered",
|
||||
() async {
|
||||
final NotifyManager notifyManager = NotifyManager();
|
||||
int level1 = 0;
|
||||
int level2 = 0;
|
||||
int level3 = 0;
|
||||
callback() async {
|
||||
await Future.delayed(Duration(milliseconds: 20));
|
||||
level3++;
|
||||
}
|
||||
|
||||
notifyManager.batch(() {
|
||||
notifyManager.batch(() {
|
||||
notifyManager.schedule(callback);
|
||||
level2++;
|
||||
});
|
||||
level1++;
|
||||
});
|
||||
await Future.delayed(Duration(milliseconds: 30));
|
||||
expect(level1, equals(1));
|
||||
expect(level2, equals(1));
|
||||
expect(level3, equals(1));
|
||||
},
|
||||
timeout: Timeout(Duration(minutes: 2)),
|
||||
);
|
||||
|
||||
test("Should flush When Exception is thrown in a batched callback", () {
|
||||
final SpyNotifyManager notifyManager = SpyNotifyManager();
|
||||
try {
|
||||
notifyManager.batch(() {
|
||||
throw Exception("Damn an exception");
|
||||
});
|
||||
} catch (e) {}
|
||||
|
||||
expect(notifyManager.flushCall, equals(1));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:fl_query/src/core/online_manager.dart';
|
||||
import 'package:internet_connection_checker/internet_connection_checker.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:test/expect.dart';
|
||||
import 'package:test/scaffolding.dart';
|
||||
import './online_manager_test.mocks.dart';
|
||||
|
||||
@GenerateMocks([InternetConnectionChecker])
|
||||
void main() {
|
||||
group('OnlineManager', () {
|
||||
late OnlineManager onlineManagerTest;
|
||||
late StreamController<InternetConnectionStatus> statusController;
|
||||
late MockInternetConnectionChecker connectionChecker;
|
||||
setUp(() {
|
||||
statusController = StreamController<InternetConnectionStatus>.broadcast();
|
||||
statusController.add(InternetConnectionStatus.connected);
|
||||
connectionChecker = MockInternetConnectionChecker();
|
||||
when(connectionChecker.hasConnection)
|
||||
.thenAnswer((_) => Future.value(true));
|
||||
when(connectionChecker.hasListeners)
|
||||
.thenReturn(statusController.hasListener);
|
||||
when(connectionChecker.onStatusChange)
|
||||
.thenAnswer((_) => statusController.stream);
|
||||
onlineManagerTest = OnlineManager(connectionChecker);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
statusController.close();
|
||||
});
|
||||
|
||||
test(
|
||||
'isOnline Should return true When InternetConnectionChecker.hasConnection is true',
|
||||
() async {
|
||||
bool online = await onlineManagerTest.isOnline();
|
||||
expect(online, isTrue);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
"setEventListener Should use _online property When setOnline sets _online = false",
|
||||
() async {
|
||||
int count = 0;
|
||||
|
||||
setup(void Function(bool?) setOnline) {
|
||||
Timer(Duration(milliseconds: 20), () {
|
||||
count++;
|
||||
setOnline(false);
|
||||
});
|
||||
return () {};
|
||||
}
|
||||
|
||||
onlineManagerTest.setEventListener(setup);
|
||||
await Future.delayed(Duration(milliseconds: 30));
|
||||
expect(count, equals(1));
|
||||
onlineManagerTest.isOnline().then((online) {
|
||||
expect(online, isFalse);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'setEventListener Should call previous remove handler When replacing an event listener',
|
||||
() {
|
||||
int cb1calls = 0;
|
||||
int cb2calls = 0;
|
||||
onlineManagerTest.setEventListener((_) => () => cb1calls++);
|
||||
onlineManagerTest.setEventListener((_) => () => cb2calls++);
|
||||
expect(cb1calls, equals(1));
|
||||
expect(cb2calls, equals(0));
|
||||
},
|
||||
);
|
||||
test(
|
||||
'Should replace default window listener When a new event listener is set',
|
||||
() {
|
||||
// Should set the default event listener with window event listeners
|
||||
final unsubscribe = onlineManagerTest.subscribe();
|
||||
verify(connectionChecker.onStatusChange.listen).called(1);
|
||||
// Should replace the window default event listener by a new one
|
||||
// and it should call window.removeEventListener twice
|
||||
onlineManagerTest.setEventListener((online) {
|
||||
return () => null;
|
||||
});
|
||||
expect(connectionChecker.hasListeners, isFalse);
|
||||
unsubscribe();
|
||||
},
|
||||
);
|
||||
|
||||
test('Should cancel StreamSubscription When last listener unsubscribes',
|
||||
() {
|
||||
final unsubscribe1 = onlineManager.subscribe(() => null);
|
||||
final unsubscribe2 = onlineManager.subscribe(() => null);
|
||||
|
||||
verify(connectionChecker.onStatusChange.listen).called(1);
|
||||
unsubscribe1();
|
||||
expect(connectionChecker.hasListeners, isTrue);
|
||||
unsubscribe2();
|
||||
expect(connectionChecker.hasListeners, isFalse);
|
||||
}, skip: true);
|
||||
|
||||
test('should keep setup function even if last listener unsubscribes', () {
|
||||
int count = 0;
|
||||
onlineManager.setEventListener((_) => () => count++);
|
||||
|
||||
final unsubscribe1 = onlineManagerTest.subscribe(() => null);
|
||||
expect(count, equals(1));
|
||||
unsubscribe1();
|
||||
|
||||
final unsubscribe2 = onlineManager.subscribe(() => null);
|
||||
expect(count, equals(2));
|
||||
unsubscribe2();
|
||||
}, skip: true);
|
||||
});
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
// Mocks generated by Mockito 5.1.0 from annotations
|
||||
// in fl_query/test/src/core/online_manager_test.dart.
|
||||
// Do not manually edit this file.
|
||||
|
||||
import 'dart:async' as _i3;
|
||||
|
||||
import 'package:internet_connection_checker/internet_connection_checker.dart'
|
||||
as _i2;
|
||||
import 'package:mockito/mockito.dart' as _i1;
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: avoid_redundant_argument_values
|
||||
// ignore_for_file: avoid_setters_without_getters
|
||||
// ignore_for_file: comment_references
|
||||
// ignore_for_file: implementation_imports
|
||||
// ignore_for_file: invalid_use_of_visible_for_testing_member
|
||||
// ignore_for_file: prefer_const_constructors
|
||||
// ignore_for_file: unnecessary_parenthesis
|
||||
// ignore_for_file: camel_case_types
|
||||
|
||||
class _FakeDuration_0 extends _i1.Fake implements Duration {}
|
||||
|
||||
class _FakeAddressCheckResult_1 extends _i1.Fake
|
||||
implements _i2.AddressCheckResult {}
|
||||
|
||||
/// A class which mocks [InternetConnectionChecker].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockInternetConnectionChecker extends _i1.Mock
|
||||
implements _i2.InternetConnectionChecker {
|
||||
MockInternetConnectionChecker() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
List<_i2.AddressCheckOptions> get addresses =>
|
||||
(super.noSuchMethod(Invocation.getter(#addresses),
|
||||
returnValue: <_i2.AddressCheckOptions>[])
|
||||
as List<_i2.AddressCheckOptions>);
|
||||
@override
|
||||
set addresses(List<_i2.AddressCheckOptions>? _addresses) =>
|
||||
super.noSuchMethod(Invocation.setter(#addresses, _addresses),
|
||||
returnValueForMissingStub: null);
|
||||
@override
|
||||
Duration get checkInterval =>
|
||||
(super.noSuchMethod(Invocation.getter(#checkInterval),
|
||||
returnValue: _FakeDuration_0()) as Duration);
|
||||
@override
|
||||
set checkInterval(Duration? _checkInterval) =>
|
||||
super.noSuchMethod(Invocation.setter(#checkInterval, _checkInterval),
|
||||
returnValueForMissingStub: null);
|
||||
@override
|
||||
_i3.Future<bool> get hasConnection =>
|
||||
(super.noSuchMethod(Invocation.getter(#hasConnection),
|
||||
returnValue: Future<bool>.value(false)) as _i3.Future<bool>);
|
||||
@override
|
||||
_i3.Future<_i2.InternetConnectionStatus> get connectionStatus =>
|
||||
(super.noSuchMethod(Invocation.getter(#connectionStatus),
|
||||
returnValue: Future<_i2.InternetConnectionStatus>.value(
|
||||
_i2.InternetConnectionStatus.connected))
|
||||
as _i3.Future<_i2.InternetConnectionStatus>);
|
||||
@override
|
||||
_i3.Stream<_i2.InternetConnectionStatus> get onStatusChange =>
|
||||
(super.noSuchMethod(Invocation.getter(#onStatusChange),
|
||||
returnValue: Stream<_i2.InternetConnectionStatus>.empty())
|
||||
as _i3.Stream<_i2.InternetConnectionStatus>);
|
||||
@override
|
||||
bool get hasListeners =>
|
||||
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false)
|
||||
as bool);
|
||||
@override
|
||||
bool get isActivelyChecking =>
|
||||
(super.noSuchMethod(Invocation.getter(#isActivelyChecking),
|
||||
returnValue: false) as bool);
|
||||
@override
|
||||
_i3.Future<_i2.AddressCheckResult> isHostReachable(
|
||||
_i2.AddressCheckOptions? options) =>
|
||||
(super.noSuchMethod(Invocation.method(#isHostReachable, [options]),
|
||||
returnValue: Future<_i2.AddressCheckResult>.value(
|
||||
_FakeAddressCheckResult_1()))
|
||||
as _i3.Future<_i2.AddressCheckResult>);
|
||||
}
|
||||
@@ -1,300 +0,0 @@
|
||||
import 'package:fl_query/src/core/core.dart';
|
||||
import 'package:fl_query/src/core/query_cache.dart';
|
||||
import 'package:fl_query/src/core/retryer.dart';
|
||||
import 'package:test/expect.dart';
|
||||
import 'package:test/scaffolding.dart';
|
||||
|
||||
import '../../helpers/utils.dart';
|
||||
|
||||
void main() {
|
||||
group('QueryCache', () {
|
||||
late QueryClient queryClient;
|
||||
late QueryCache queryCache;
|
||||
|
||||
setUp(() {
|
||||
queryClient = new QueryClient();
|
||||
queryCache = queryClient.getQueryCache();
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
queryClient.clear();
|
||||
});
|
||||
group('subscribe', () {
|
||||
test('Should pass the correct query', () async {
|
||||
final QueryKey key = queryKey();
|
||||
var _event;
|
||||
subscriber(event) {
|
||||
_event ??= event;
|
||||
}
|
||||
|
||||
final unsubscribe = queryCache.subscribe(subscriber);
|
||||
final Map<String, dynamic> data = {'foo': "foo"};
|
||||
queryClient.setQueryData(key, (_) => data);
|
||||
final query = queryCache.find(key);
|
||||
await Future.delayed(Duration(milliseconds: 1));
|
||||
expect(_event, isA<QueryCacheNotifyEvent>());
|
||||
expect(
|
||||
(_event as QueryCacheNotifyEvent).type,
|
||||
QueryCacheNotifyEventType.queryAdded,
|
||||
);
|
||||
expect(
|
||||
(_event as QueryCacheNotifyEvent).query,
|
||||
same(query),
|
||||
);
|
||||
unsubscribe();
|
||||
});
|
||||
|
||||
test('Should notify listeners When new query is added', () async {
|
||||
final key = queryKey();
|
||||
late bool called;
|
||||
callback(_) {
|
||||
called = true;
|
||||
}
|
||||
|
||||
queryCache.subscribe(callback);
|
||||
queryClient.prefetchQuery(
|
||||
queryKey: key,
|
||||
queryFn: (_) async => {'data': "data"},
|
||||
);
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
|
||||
expect(called, isTrue);
|
||||
});
|
||||
|
||||
test('Should include the queryCache and query When notifying listeners',
|
||||
() async {
|
||||
final key = queryKey();
|
||||
QueryCacheNotifyEvent? _event;
|
||||
callback(event) => _event ??= event;
|
||||
queryCache.subscribe(callback);
|
||||
queryClient.prefetchQuery(
|
||||
queryKey: key,
|
||||
queryFn: (_) => {'data': "data"},
|
||||
);
|
||||
final query = queryCache.find(key);
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
expect(_event, isA<QueryCacheNotifyEvent>());
|
||||
expect(_event?.type, QueryCacheNotifyEventType.queryAdded);
|
||||
expect(_event?.query, same(query));
|
||||
});
|
||||
|
||||
test('Should notify subscribers When new query with initialData is added',
|
||||
() async {
|
||||
final key = queryKey();
|
||||
late bool called;
|
||||
callback(_) => called = true;
|
||||
queryCache.subscribe(callback);
|
||||
queryClient
|
||||
.prefetchQuery<Map<String, dynamic>, dynamic, Map<String, dynamic>>(
|
||||
queryKey: key,
|
||||
queryFn: (_) => {'data': "Data"},
|
||||
options: FetchQueryOptions(initialData: {"data": "initial-data"}),
|
||||
);
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
expect(called, isTrue);
|
||||
});
|
||||
});
|
||||
|
||||
group('find', () {
|
||||
test('Should filter correctly', () async {
|
||||
final key = queryKey();
|
||||
await queryClient.prefetchQuery(
|
||||
queryKey: key,
|
||||
queryFn: (_) => {"data": 'data1'},
|
||||
);
|
||||
final query = queryCache.find(key);
|
||||
expect(query, isNotNull);
|
||||
});
|
||||
|
||||
test(
|
||||
'Should filter correctly When called with exact set to false',
|
||||
() async {
|
||||
final key = queryKey();
|
||||
await queryClient.prefetchQuery(
|
||||
queryKey: key,
|
||||
queryFn: (_) => {"data": 'data1'},
|
||||
);
|
||||
final query = queryCache.find(key, QueryFilters(exact: false));
|
||||
expect(query, isNotNull);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('findAll', () {
|
||||
test('Should filter correctly', () async {
|
||||
final key1 = queryKey();
|
||||
final key2 = queryKey();
|
||||
final key3 = QueryKey.fromList(['posts', "1"]);
|
||||
await queryClient.prefetchQuery(
|
||||
queryKey: key1,
|
||||
queryFn: (_) => {"data": 'data1'},
|
||||
);
|
||||
await queryClient.prefetchQuery(
|
||||
queryKey: key2,
|
||||
queryFn: (_) => {"data": 'data2'},
|
||||
);
|
||||
await queryClient.prefetchQuery(
|
||||
queryKey: key3,
|
||||
queryFn: (_) => {"data": 'data4'},
|
||||
);
|
||||
await queryClient.invalidateQueries(queryKeys: key2);
|
||||
final query1 = queryCache.find(key1);
|
||||
final query2 = queryCache.find(key2);
|
||||
final query4 = queryCache.find(key3);
|
||||
|
||||
expect(queryCache.findAll(key1), equals([query1]));
|
||||
expect(queryCache.findAll(), equals([query1, query2, query4]));
|
||||
expect(
|
||||
queryCache.findAll(key1, QueryFilters(active: false)),
|
||||
equals([query1]),
|
||||
);
|
||||
expect(
|
||||
queryCache.findAll(key1, QueryFilters(active: true)), equals([]));
|
||||
expect(queryCache.findAll(key1, QueryFilters(stale: true)), equals([]));
|
||||
expect(
|
||||
queryCache.findAll(key1, QueryFilters(stale: false)),
|
||||
equals([query1]),
|
||||
);
|
||||
expect(
|
||||
queryCache.findAll(key1, QueryFilters(stale: false, active: true)),
|
||||
equals([]),
|
||||
);
|
||||
expect(
|
||||
queryCache.findAll(key1, QueryFilters(active: false, stale: false)),
|
||||
equals([query1]),
|
||||
);
|
||||
expect(
|
||||
queryCache.findAll(
|
||||
key1,
|
||||
QueryFilters(active: false, stale: false, exact: true),
|
||||
),
|
||||
equals([query1]),
|
||||
);
|
||||
|
||||
expect(queryCache.findAll(key2), equals([query2]));
|
||||
expect(
|
||||
queryCache.findAll(key2, QueryFilters(stale: null)),
|
||||
equals([query2]),
|
||||
);
|
||||
expect(
|
||||
queryCache.findAll(key2, QueryFilters(stale: true)),
|
||||
equals([query2]),
|
||||
);
|
||||
expect(
|
||||
queryCache.findAll(key2, QueryFilters(stale: false)),
|
||||
equals([]),
|
||||
);
|
||||
|
||||
expect(
|
||||
queryCache.findAll(
|
||||
null,
|
||||
QueryFilters(predicate: (query) => query == query4),
|
||||
),
|
||||
equals([query4]),
|
||||
);
|
||||
expect(queryCache.findAll(QueryKey('posts')), equals([query4]));
|
||||
});
|
||||
|
||||
test('Should return all the queries When no filters are defined',
|
||||
() async {
|
||||
final key1 = queryKey();
|
||||
final key2 = queryKey();
|
||||
await queryClient.prefetchQuery(
|
||||
queryKey: key1,
|
||||
queryFn: (_) => {"data": 'data1'},
|
||||
);
|
||||
await queryClient.prefetchQuery(
|
||||
queryKey: key2,
|
||||
queryFn: (_) {
|
||||
return {"data": 'data2'};
|
||||
},
|
||||
);
|
||||
expect(queryCache.findAll().length, 2);
|
||||
});
|
||||
});
|
||||
|
||||
group('QueryCacheConfig.onError', () {
|
||||
test('should be called when a query errors', () async {
|
||||
final key = queryKey();
|
||||
var errorArg;
|
||||
var queryArg;
|
||||
onError(error, query) {
|
||||
errorArg = error;
|
||||
queryArg = query;
|
||||
}
|
||||
|
||||
final testCache = new QueryCache(onError: onError);
|
||||
final testClient = new QueryClient(queryCache: testCache);
|
||||
await testClient
|
||||
.prefetchQuery<Map<String, dynamic>, dynamic, Map<String, dynamic>>(
|
||||
queryKey: key, queryFn: (_) => Future.error('error'));
|
||||
final query = testCache.find(key);
|
||||
expect(errorArg, equals("error"));
|
||||
expect(queryArg, equals(query));
|
||||
});
|
||||
});
|
||||
|
||||
group('QueryCacheConfig.onSuccess', () {
|
||||
test('should be called when a query is successful', () async {
|
||||
final key = queryKey();
|
||||
var dataArg;
|
||||
var queryArg;
|
||||
onData(data, query) {
|
||||
dataArg = data;
|
||||
queryArg = query;
|
||||
}
|
||||
|
||||
final testCache = new QueryCache(onData: onData);
|
||||
final testClient = new QueryClient(queryCache: testCache);
|
||||
await testClient
|
||||
.prefetchQuery<Map<String, dynamic>, dynamic, Map<String, dynamic>>(
|
||||
queryKey: key,
|
||||
queryFn: (_) => Future.value({"data": 5}),
|
||||
);
|
||||
final query = testCache.find(key);
|
||||
expect(dataArg, equals({"data": 5}));
|
||||
expect(queryArg, equals(query));
|
||||
});
|
||||
});
|
||||
group('QueryCache.add', () {
|
||||
test('should not try to add a query already added to the cache',
|
||||
() async {
|
||||
final key = queryKey();
|
||||
final hash = key.key;
|
||||
|
||||
await queryClient.prefetchQuery(
|
||||
queryKey: key, queryFn: (_) => {"data": 'data1'});
|
||||
|
||||
// Directly add the query from the cache
|
||||
// to simulate a race condition
|
||||
final query = queryCache.queriesMap[hash] as Query;
|
||||
|
||||
// No error should be thrown when trying to add the query
|
||||
queryCache.add(query);
|
||||
expect(queryCache.queries.length, 1);
|
||||
|
||||
// Clean-up to avoid an error when queryClient.clear()
|
||||
queryCache.remove(query);
|
||||
});
|
||||
});
|
||||
|
||||
group('QueryCache.remove', () {
|
||||
test('should not try to remove a query already removed from the cache',
|
||||
() async {
|
||||
final key = queryKey();
|
||||
final hash = key.key;
|
||||
|
||||
await queryClient.prefetchQuery(
|
||||
queryKey: key, queryFn: (_) => {"data": 'data1'});
|
||||
|
||||
// Directly remove the query from the cache
|
||||
// to simulate a race condition
|
||||
final query = queryCache.queriesMap[hash] as Query;
|
||||
queryCache.queriesMap.remove(hash);
|
||||
|
||||
// No error should be thrown when trying to remove the query
|
||||
expect(() => queryCache.remove(query), isNot(throwsException));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,912 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:fl_query/src/core/core.dart';
|
||||
import 'package:fl_query/src/core/query_observer.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../../helpers/utils.dart';
|
||||
|
||||
typedef QueryFn = FutureOr<Map<String, dynamic>> Function(
|
||||
QueryFunctionContext<dynamic>);
|
||||
|
||||
void main() {
|
||||
group('QueryObserver', () {
|
||||
late QueryClient queryClient;
|
||||
|
||||
setUp(() {
|
||||
queryClient = QueryClient();
|
||||
queryClient.mount();
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
queryClient.clear();
|
||||
});
|
||||
|
||||
test('should trigger a fetch when subscribed', () async {
|
||||
final key = queryKey();
|
||||
;
|
||||
int calls = 0;
|
||||
queryFn(context) {
|
||||
calls++;
|
||||
return {"data": "data1"};
|
||||
}
|
||||
|
||||
final observer = QueryObserver(
|
||||
queryClient,
|
||||
QueryObserverOptions(queryKey: key, queryFn: queryFn),
|
||||
);
|
||||
final unsubscribe = observer.subscribe();
|
||||
await Future.delayed(Duration(milliseconds: 1));
|
||||
unsubscribe();
|
||||
expect(calls, 1);
|
||||
});
|
||||
|
||||
test('should notify when switching query', () async {
|
||||
final key1 = queryKey();
|
||||
final key2 = queryKey();
|
||||
final List<QueryObserverResult> results = [];
|
||||
final observer = QueryObserver(
|
||||
queryClient,
|
||||
QueryObserverOptions(
|
||||
queryKey: key1,
|
||||
queryFn: (_) => {"data": 1},
|
||||
),
|
||||
);
|
||||
final unsubscribe = observer.subscribe((result) {
|
||||
results.add(result);
|
||||
});
|
||||
await Future.delayed(Duration(milliseconds: 1));
|
||||
observer.setOptions(
|
||||
QueryObserverOptions(queryKey: key2, queryFn: (_) => {"data": 2}),
|
||||
);
|
||||
await Future.delayed(Duration(milliseconds: 2));
|
||||
unsubscribe();
|
||||
expect(results.length, 4);
|
||||
expect(results[0].data, isNull);
|
||||
expect(results[0].status, QueryStatus.loading);
|
||||
expect(results[1].data, {"data": 1});
|
||||
expect(results[1].status, QueryStatus.success);
|
||||
expect(results[2].data, isNull);
|
||||
expect(results[2].status, QueryStatus.loading);
|
||||
expect(results[3].data, {"data": 2});
|
||||
expect(results[3].status, QueryStatus.success);
|
||||
});
|
||||
|
||||
test('should be able to fetch with a selector', () async {
|
||||
final key = queryKey();
|
||||
;
|
||||
final observer = QueryObserver<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryClient,
|
||||
QueryObserverOptions<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryKey: key,
|
||||
queryFn: (_) => {"count": 1},
|
||||
select: (data) => ({"myCount": data?["count"]}),
|
||||
));
|
||||
QueryObserverResult? observerResult;
|
||||
final unsubscribe = observer.subscribe((result) {
|
||||
observerResult = result;
|
||||
});
|
||||
await Future.delayed(Duration(milliseconds: 1));
|
||||
unsubscribe();
|
||||
expect(
|
||||
observerResult?.data,
|
||||
equals({"myCount": 1}),
|
||||
);
|
||||
});
|
||||
|
||||
test('should be able to fetch with a selector using the fetch method',
|
||||
() async {
|
||||
final key = queryKey();
|
||||
;
|
||||
final observer = QueryObserver<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryClient,
|
||||
QueryObserverOptions<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryKey: key,
|
||||
queryFn: (_) => {"count": 1},
|
||||
select: (data) => ({"myCount": data?["count"]}),
|
||||
));
|
||||
final observerResult = await observer.refetch();
|
||||
expect(observerResult?.data, equals({"myCount": 1}));
|
||||
});
|
||||
|
||||
test('should run the selector again if the data changed', () async {
|
||||
final key = queryKey();
|
||||
;
|
||||
int count = 0;
|
||||
final observer = QueryObserver<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryClient,
|
||||
QueryObserverOptions<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryKey: key,
|
||||
queryFn: (_) => Map.from({"count": count}),
|
||||
select: (data) {
|
||||
count++;
|
||||
return Map.from({"myCount": data?["count"]});
|
||||
},
|
||||
));
|
||||
final observerResult1 = await observer.refetch();
|
||||
final observerResult2 = await observer.refetch();
|
||||
expect(count, 2);
|
||||
expect(observerResult1?.data, equals({"myCount": 0}));
|
||||
expect(observerResult2?.data, equals({"myCount": 1}));
|
||||
});
|
||||
|
||||
test('should run the selector again if the selector changed', () async {
|
||||
final key = queryKey();
|
||||
;
|
||||
int count = 0;
|
||||
final List<QueryObserverResult> results = [];
|
||||
final queryFn = (_) => ({"count": 1});
|
||||
select1(data) {
|
||||
count++;
|
||||
return {"myCount": data?["count"]};
|
||||
}
|
||||
|
||||
select2(_data) {
|
||||
count++;
|
||||
return {"myCount": 99};
|
||||
}
|
||||
|
||||
final observer = new QueryObserver(
|
||||
queryClient,
|
||||
QueryObserverOptions(
|
||||
queryKey: key,
|
||||
queryFn: queryFn,
|
||||
select: select1,
|
||||
));
|
||||
final unsubscribe = observer.subscribe((result) {
|
||||
results.add(result);
|
||||
});
|
||||
await Future.delayed(Duration(milliseconds: 1));
|
||||
observer.setOptions(QueryObserverOptions(
|
||||
queryKey: key,
|
||||
queryFn: queryFn,
|
||||
select: select2,
|
||||
));
|
||||
await Future.delayed(Duration(milliseconds: 1));
|
||||
//! Currently causing an extra call for refetch
|
||||
//! select shouldn't be called when refetch is called
|
||||
await observer.refetch();
|
||||
unsubscribe();
|
||||
expect(count, 2);
|
||||
expect(results.length, 5);
|
||||
expect(results.first.status, QueryStatus.loading);
|
||||
expect(results.first.isFetching, isTrue);
|
||||
expect(results.first.data, isNull);
|
||||
expect(results[1].status, QueryStatus.success);
|
||||
expect(results[1].isFetching, false);
|
||||
expect(results[1].data, {"myCount": 1});
|
||||
expect(results[2].status, QueryStatus.success);
|
||||
expect(results[2].isFetching, false);
|
||||
expect(results[2].data, {"myCount": 99});
|
||||
expect(results[3].status, QueryStatus.success);
|
||||
expect(results[3].isFetching, true);
|
||||
expect(results[3].data, {"myCount": 99});
|
||||
expect(results.last.status, QueryStatus.success);
|
||||
expect(results.last.isFetching, false);
|
||||
expect(results.last.data, {"myCount": 99});
|
||||
});
|
||||
test(
|
||||
'should not run the selector again if the data and selector did not change',
|
||||
() async {
|
||||
final key = queryKey();
|
||||
;
|
||||
int count = 0;
|
||||
final List<QueryObserverResult> results = [];
|
||||
final queryFn = (_) => {"count": 1};
|
||||
select(data) {
|
||||
count++;
|
||||
return {"myCount": data["count"]};
|
||||
}
|
||||
|
||||
final observer = new QueryObserver(
|
||||
queryClient,
|
||||
QueryObserverOptions(
|
||||
queryKey: key,
|
||||
queryFn: queryFn,
|
||||
select: select,
|
||||
));
|
||||
final unsubscribe = observer.subscribe((result) {
|
||||
results.add(result);
|
||||
});
|
||||
await Future.delayed(Duration(milliseconds: 1));
|
||||
observer.setOptions(QueryObserverOptions(
|
||||
queryKey: key,
|
||||
queryFn: queryFn,
|
||||
select: select,
|
||||
));
|
||||
await Future.delayed(Duration(milliseconds: 1));
|
||||
await observer.refetch();
|
||||
unsubscribe();
|
||||
expect(count, 1);
|
||||
expect(results.length, 4);
|
||||
expect(results.first.status, QueryStatus.loading);
|
||||
expect(results.first.isFetching, isTrue);
|
||||
expect(results.first.data, isNull);
|
||||
expect(results[1].status, QueryStatus.success);
|
||||
expect(results[1].isFetching, false);
|
||||
expect(results[1].data, {"myCount": 1});
|
||||
expect(results[2].status, QueryStatus.success);
|
||||
expect(results[2].isFetching, true);
|
||||
expect(results[2].data, {"myCount": 1});
|
||||
expect(results.last.status, QueryStatus.success);
|
||||
expect(results.last.isFetching, false);
|
||||
expect(results.last.data, {"myCount": 1});
|
||||
});
|
||||
|
||||
test('should not run the selector again if the data did not change',
|
||||
() async {
|
||||
final key = queryKey();
|
||||
;
|
||||
int count = 0;
|
||||
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryClient,
|
||||
QueryObserverOptions<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryKey: key,
|
||||
queryFn: (_) => {"count": 1},
|
||||
select: (data) {
|
||||
count++;
|
||||
return {"myCount": data?["count"]};
|
||||
},
|
||||
),
|
||||
);
|
||||
final observerResult1 = await observer.refetch();
|
||||
final observerResult2 = await observer.refetch();
|
||||
expect(count, 1);
|
||||
expect(observerResult1?.data, equals({"myCount": 1}));
|
||||
expect(observerResult2?.data, equals({"myCount": 1}));
|
||||
});
|
||||
|
||||
test('should always run the selector again if selector throws an error',
|
||||
() async {
|
||||
final key = queryKey();
|
||||
;
|
||||
final List<QueryObserverResult> results = [];
|
||||
select(data) {
|
||||
throw new Exception('selector error');
|
||||
}
|
||||
|
||||
queryFn(_) => ({"count": 1});
|
||||
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryClient,
|
||||
QueryObserverOptions<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryKey: key,
|
||||
queryFn: queryFn,
|
||||
select: select,
|
||||
));
|
||||
final unsubscribe = observer.subscribe((result) {
|
||||
results.add(result);
|
||||
});
|
||||
await Future.delayed(Duration(milliseconds: 1));
|
||||
await observer.refetch();
|
||||
unsubscribe();
|
||||
expect(results.length, 5);
|
||||
expect(results.first.status, QueryStatus.loading);
|
||||
expect(results.first.isFetching, isTrue);
|
||||
expect(results.first.data, isNull);
|
||||
expect(results[1].status, QueryStatus.error);
|
||||
expect(results[1].isFetching, false);
|
||||
expect(results[1].data, isNull);
|
||||
expect(results[2].status, QueryStatus.error);
|
||||
expect(results[2].isFetching, true);
|
||||
expect(results[2].data, isNull);
|
||||
expect(results[3].status, QueryStatus.error);
|
||||
expect(results[3].isFetching, false);
|
||||
expect(results[3].data, isNull);
|
||||
expect(results.last.status, QueryStatus.error);
|
||||
expect(results.last.isFetching, false);
|
||||
expect(results.last.data, isNull);
|
||||
});
|
||||
|
||||
test('should structurally share the selector', () async {
|
||||
final key = queryKey();
|
||||
;
|
||||
int count = 0;
|
||||
final observer = new QueryObserver(
|
||||
queryClient,
|
||||
QueryObserverOptions<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryKey: key,
|
||||
queryFn: (_) => {"count": ++count},
|
||||
select: (data) {
|
||||
return {"myCount": 1};
|
||||
},
|
||||
));
|
||||
final observerResult1 = await observer.refetch();
|
||||
final observerResult2 = await observer.refetch();
|
||||
expect(count, 2);
|
||||
expect(observerResult1?.data, isNotNull);
|
||||
expect(observerResult1?.data, equals(observerResult2?.data));
|
||||
});
|
||||
|
||||
test('should not trigger a fetch when subscribed and disabled', () async {
|
||||
int count = 0;
|
||||
final key = queryKey();
|
||||
;
|
||||
final observer = new QueryObserver(
|
||||
queryClient,
|
||||
QueryObserverOptions(
|
||||
queryKey: key,
|
||||
queryFn: (_) {
|
||||
count++;
|
||||
return {"data": null};
|
||||
},
|
||||
enabled: false,
|
||||
));
|
||||
final unsubscribe = observer.subscribe();
|
||||
await Future.delayed(Duration(milliseconds: 1));
|
||||
unsubscribe();
|
||||
expect(count, 0);
|
||||
});
|
||||
|
||||
test('should not trigger a fetch when not subscribed', () async {
|
||||
int count = 0;
|
||||
final key = queryKey();
|
||||
;
|
||||
new QueryObserver(
|
||||
queryClient,
|
||||
QueryObserverOptions(
|
||||
queryKey: key,
|
||||
queryFn: (_) {
|
||||
count++;
|
||||
return {"data": null};
|
||||
},
|
||||
));
|
||||
await Future.delayed(Duration(milliseconds: 1));
|
||||
expect(count, 0);
|
||||
});
|
||||
|
||||
test('should be able to watch a query without defining a query function',
|
||||
() async {
|
||||
int count = 0;
|
||||
int subscribeCount = 0;
|
||||
final key = queryKey();
|
||||
;
|
||||
queryFn(_) {
|
||||
count++;
|
||||
return {"data": null};
|
||||
}
|
||||
|
||||
final observer = QueryObserver<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryClient,
|
||||
QueryObserverOptions<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryKey: key,
|
||||
queryFn: queryFn,
|
||||
enabled: false,
|
||||
));
|
||||
|
||||
final unsubscribe = observer.subscribe((_) {
|
||||
subscribeCount++;
|
||||
});
|
||||
await queryClient.fetchQuery(queryKey: key, queryFn: queryFn);
|
||||
unsubscribe();
|
||||
expect(count, 1);
|
||||
expect(subscribeCount, 2);
|
||||
});
|
||||
|
||||
test('should accept unresolved query config in update function', () async {
|
||||
final key = queryKey();
|
||||
;
|
||||
final observer = QueryObserver<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryClient,
|
||||
QueryObserverOptions<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryKey: key,
|
||||
enabled: false,
|
||||
));
|
||||
|
||||
final List<QueryObserverResult> results = [];
|
||||
final unsubscribe = observer.subscribe((x) {
|
||||
results.add(x);
|
||||
});
|
||||
observer.setOptions(
|
||||
QueryObserverOptions(
|
||||
enabled: false,
|
||||
staleTime: Duration(milliseconds: 10),
|
||||
),
|
||||
);
|
||||
int count = 0;
|
||||
|
||||
queryFn(_) {
|
||||
count++;
|
||||
return {"data": null};
|
||||
}
|
||||
|
||||
await queryClient.fetchQuery(queryKey: key, queryFn: queryFn);
|
||||
await sleep(100);
|
||||
unsubscribe();
|
||||
expect(count, 1);
|
||||
expect(results.length, 3);
|
||||
expect(results[0].isStale, true);
|
||||
expect(results[1].isStale, false);
|
||||
expect(results[2].isStale, true);
|
||||
});
|
||||
|
||||
test('should be able to handle multiple subscribers', () async {
|
||||
final key = queryKey();
|
||||
|
||||
int count = 0;
|
||||
|
||||
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryClient,
|
||||
QueryObserverOptions<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryKey: key,
|
||||
enabled: false,
|
||||
));
|
||||
final List<QueryObserverResult> results1 = [];
|
||||
final List<QueryObserverResult> results2 = [];
|
||||
final unsubscribe1 = observer.subscribe((x) {
|
||||
results1.add(x);
|
||||
});
|
||||
final unsubscribe2 = observer.subscribe((x) {
|
||||
results2.add(x);
|
||||
});
|
||||
await queryClient
|
||||
.fetchQuery<Map<String, dynamic>, dynamic, Map<String, dynamic>>(
|
||||
queryKey: key,
|
||||
queryFn: (_) {
|
||||
count++;
|
||||
return {"data": false};
|
||||
},
|
||||
);
|
||||
await sleep(50);
|
||||
unsubscribe1();
|
||||
unsubscribe2();
|
||||
expect(count, 1);
|
||||
expect(results1.length, 2);
|
||||
expect(results2.length, 2);
|
||||
expect(results1[0].data?["data"], isNull);
|
||||
expect(results1[1].data?["data"], isFalse);
|
||||
expect(results2[0].data?["data"], isNull);
|
||||
expect(results2[1].data?["data"], isFalse);
|
||||
});
|
||||
|
||||
test('should be able to resolve a promise', () async {
|
||||
final key = queryKey();
|
||||
int count = 0;
|
||||
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryClient,
|
||||
QueryObserverOptions<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryKey: key,
|
||||
enabled: false,
|
||||
));
|
||||
QueryObserverResult<Map<String, dynamic>, dynamic>? value;
|
||||
observer.getNextResult().then((x) {
|
||||
value = x;
|
||||
});
|
||||
queryClient
|
||||
.prefetchQuery<Map<String, dynamic>, dynamic, Map<String, dynamic>>(
|
||||
queryKey: key,
|
||||
queryFn: (_) {
|
||||
count++;
|
||||
return {"data": "a data"};
|
||||
});
|
||||
await sleep(50);
|
||||
expect(count, 1);
|
||||
expect(value?.data?["data"], "a data");
|
||||
});
|
||||
|
||||
test('should be able to resolve a promise with an error', () async {
|
||||
final key = queryKey();
|
||||
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryClient,
|
||||
QueryObserverOptions<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryKey: key,
|
||||
enabled: false,
|
||||
));
|
||||
var error;
|
||||
await observer.getNextResult(true).catchError((e) async {
|
||||
error = e;
|
||||
return e;
|
||||
});
|
||||
await queryClient
|
||||
.prefetchQuery<Map<String, dynamic>, dynamic, Map<String, dynamic>>(
|
||||
queryKey: key, queryFn: (_) => Future.error('reject'));
|
||||
await sleep(50);
|
||||
expect(error, 'reject');
|
||||
}, skip: true);
|
||||
|
||||
test('should stop retry when unsubscribing', () async {
|
||||
int count = 0;
|
||||
final key = queryKey();
|
||||
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryClient,
|
||||
QueryObserverOptions<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryKey: key,
|
||||
queryFn: (_) {
|
||||
count++;
|
||||
return Future.error({"data": 'reject'});
|
||||
},
|
||||
retry: (_, __) => 10,
|
||||
retryDelay: (_, __) => 50,
|
||||
),
|
||||
);
|
||||
final unsubscribe = observer.subscribe();
|
||||
await sleep(70);
|
||||
unsubscribe();
|
||||
await sleep(200);
|
||||
expect(count, 2);
|
||||
}, skip: true);
|
||||
|
||||
test('should clear interval when unsubscribing to a refetchInterval query',
|
||||
() async {
|
||||
final key = queryKey();
|
||||
|
||||
final fetchData =
|
||||
(_) => Future<Map<String, dynamic>>.error({'data': null});
|
||||
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryClient,
|
||||
QueryObserverOptions<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryKey: key,
|
||||
queryFn: fetchData,
|
||||
cacheTime: Duration.zero,
|
||||
refetchInterval: (_, __) => Duration(milliseconds: 1),
|
||||
));
|
||||
final unsubscribe = observer.subscribe();
|
||||
// @ts-expect-error
|
||||
expect(observer.refetchInterval, isNotNull);
|
||||
unsubscribe();
|
||||
// @ts-expect-error
|
||||
expect(observer.refetchInterval, isNull);
|
||||
await sleep(10);
|
||||
expect(queryClient.getQueryCache().find(key), isNull);
|
||||
});
|
||||
|
||||
test(
|
||||
'uses placeholderData as non-cache data when loading a query with no data',
|
||||
() async {
|
||||
final key = queryKey();
|
||||
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryClient,
|
||||
QueryObserverOptions<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryKey: key,
|
||||
queryFn: (_) => {'data': 'data'},
|
||||
placeholderData: {"data": 'placeholder'},
|
||||
));
|
||||
final result = observer.getCurrentResult();
|
||||
expect(result?.status, QueryStatus.success);
|
||||
|
||||
final List<QueryObserverResult> results = [];
|
||||
|
||||
final unsubscribe = observer.subscribe((x) {
|
||||
results.add(x);
|
||||
});
|
||||
|
||||
await sleep(10);
|
||||
unsubscribe();
|
||||
|
||||
expect(results.length, 2);
|
||||
expect(results.first.status, QueryStatus.success);
|
||||
expect(results.first.data, equals({"data": 'placeholder'}));
|
||||
expect(results.last.status, QueryStatus.success);
|
||||
expect(results.last.data, equals({'data': 'data'}));
|
||||
});
|
||||
|
||||
test(
|
||||
'the retryer should not throw an error when reject if the retrier is already resolved',
|
||||
() async {
|
||||
final key = queryKey();
|
||||
int count = 0;
|
||||
|
||||
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryClient,
|
||||
QueryObserverOptions<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryKey: key,
|
||||
queryFn: (_) {
|
||||
count++;
|
||||
return Future.error({'reject': count});
|
||||
},
|
||||
retry: (_, __) => 1,
|
||||
retryDelay: (_, __) => 20,
|
||||
));
|
||||
|
||||
final unsubscribe = observer.subscribe();
|
||||
|
||||
// Simulate a race condition when an unsubscribe and a retry occur.
|
||||
await sleep(20);
|
||||
unsubscribe();
|
||||
|
||||
// A second reject is triggered for the retry
|
||||
// but the retryer has already set isResolved to true
|
||||
// so it does nothing and no error is thrown
|
||||
|
||||
// Should not log an error
|
||||
queryClient.clear();
|
||||
await sleep(40);
|
||||
expect(true, true);
|
||||
// expect(consoleMock).not.toHaveBeenNthCalledWith(1, 'reject 1')
|
||||
|
||||
// consoleMock.mockRestore()
|
||||
}, skip: true);
|
||||
|
||||
test('getCurrentQuery should return the current query', () async {
|
||||
final key = queryKey();
|
||||
|
||||
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryClient,
|
||||
QueryObserverOptions<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryKey: key, queryFn: (_) => {'data': 'data'}));
|
||||
|
||||
expect(observer.getCurrentQuery().queryKey, key);
|
||||
});
|
||||
|
||||
test('should throw an error if throwOnError option is true', () async {
|
||||
final key = queryKey();
|
||||
|
||||
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryClient,
|
||||
QueryObserverOptions<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryKey: key,
|
||||
queryFn: (_) => Future.error({'error': 'error'}),
|
||||
retry: (_, __) => 0,
|
||||
));
|
||||
|
||||
var error = null;
|
||||
try {
|
||||
await observer.refetch(options: RefetchOptions(throwOnError: true));
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
||||
expect(error, equals({'error': 'error'}));
|
||||
});
|
||||
|
||||
test(
|
||||
'should not refetch in background if refetchIntervalInBackground is false',
|
||||
() async {
|
||||
final key = queryKey();
|
||||
final spy = SpyFn();
|
||||
|
||||
// focusManager.setFocused(false)
|
||||
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryClient,
|
||||
QueryObserverOptions<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryKey: key,
|
||||
queryFn: spy.fn1(),
|
||||
refetchIntervalInBackground: false,
|
||||
refetchInterval: (_, __) => Duration(milliseconds: 10),
|
||||
));
|
||||
|
||||
final unsubscribe = observer.subscribe();
|
||||
await sleep(30);
|
||||
|
||||
expect(spy.calls, 1);
|
||||
|
||||
// Clean-up
|
||||
unsubscribe();
|
||||
// focusManager.setFocused(true)
|
||||
});
|
||||
|
||||
test(
|
||||
'should not use replaceEqualDeep for select value when structuralSharing option is true',
|
||||
() async {
|
||||
final key = queryKey();
|
||||
|
||||
final data = {"value": 'data'};
|
||||
final selectedData = {"value": 'data'};
|
||||
|
||||
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryClient,
|
||||
QueryObserverOptions<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryKey: key,
|
||||
queryFn: (_) => data,
|
||||
select: (_) => data,
|
||||
));
|
||||
|
||||
final unsubscribe = observer.subscribe();
|
||||
|
||||
await sleep(10);
|
||||
expect(observer.getCurrentResult()?.data, data);
|
||||
|
||||
observer.setOptions(QueryObserverOptions(
|
||||
queryKey: key,
|
||||
queryFn: (_) => data,
|
||||
structuralSharing: false,
|
||||
select: (_) => selectedData,
|
||||
));
|
||||
|
||||
await observer.refetch(filters: RefetchableQueryFilters(queryKey: key));
|
||||
expect(observer.getCurrentResult()?.data, selectedData);
|
||||
unsubscribe();
|
||||
});
|
||||
|
||||
test('select function error using placeholderdata should log an error', () {
|
||||
final key = queryKey();
|
||||
|
||||
QueryObserver<Map<String, dynamic>, dynamic, Map<String, dynamic>,
|
||||
Map<String, dynamic>>(
|
||||
queryClient,
|
||||
QueryObserverOptions<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryKey: key,
|
||||
queryFn: (_) => {"data": 'data'},
|
||||
placeholderData: {"data": 'placeholderdata'},
|
||||
select: (_) {
|
||||
throw new Exception('error');
|
||||
},
|
||||
));
|
||||
|
||||
expect(true, true);
|
||||
// expect(consoleMock).toHaveBeenNthCalledWith(1, new Error('error'))
|
||||
|
||||
// consoleMock.mockRestore()
|
||||
});
|
||||
|
||||
test(
|
||||
'should not use replaceEqualDeep for select value when structuralSharing option is true and placeholderdata is defined',
|
||||
() {
|
||||
final key = queryKey();
|
||||
|
||||
final data = {"value": 'data'};
|
||||
final selectedData1 = {"value": 'data'};
|
||||
final selectedData2 = {"value": 'data'};
|
||||
final placeholderData1 = {"value": 'data'};
|
||||
final placeholderData2 = {"value": 'data'};
|
||||
|
||||
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryClient,
|
||||
QueryObserverOptions<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryKey: key,
|
||||
queryFn: (_) => data,
|
||||
select: (_) => data,
|
||||
));
|
||||
|
||||
observer.setOptions(QueryObserverOptions(
|
||||
queryKey: key,
|
||||
queryFn: (_) => data,
|
||||
select: (_) {
|
||||
return selectedData1;
|
||||
},
|
||||
placeholderData: placeholderData1,
|
||||
));
|
||||
observer.setOptions(QueryObserverOptions(
|
||||
queryKey: key,
|
||||
queryFn: (_) => data,
|
||||
select: (_) {
|
||||
return selectedData2;
|
||||
},
|
||||
placeholderData: placeholderData2,
|
||||
structuralSharing: false,
|
||||
));
|
||||
|
||||
expect(observer.getCurrentResult()?.data, equals(selectedData2));
|
||||
});
|
||||
|
||||
test(
|
||||
'should not use an undefined value returned by select as placeholderdata',
|
||||
() {
|
||||
final key = queryKey();
|
||||
|
||||
final data = {"value": 'data'};
|
||||
final selectedData = {"value": 'data'};
|
||||
final placeholderData1 = {"value": 'data'};
|
||||
final placeholderData2 = {"value": 'data'};
|
||||
|
||||
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryClient,
|
||||
QueryObserverOptions<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryKey: key,
|
||||
queryFn: (_) => data,
|
||||
select: (_) => data,
|
||||
));
|
||||
|
||||
observer.setOptions(QueryObserverOptions<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryKey: key,
|
||||
queryFn: (_) => data,
|
||||
select: (_) {
|
||||
return selectedData;
|
||||
},
|
||||
placeholderData: placeholderData1,
|
||||
));
|
||||
|
||||
expect(observer.getCurrentResult()?.isPlaceholderData, isTrue);
|
||||
|
||||
observer.setOptions(QueryObserverOptions<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryKey: key,
|
||||
queryFn: (_) => data,
|
||||
select: (_) {
|
||||
return {};
|
||||
},
|
||||
placeholderData: placeholderData2,
|
||||
));
|
||||
|
||||
expect(observer.getCurrentResult()?.isPlaceholderData, isFalse);
|
||||
});
|
||||
|
||||
test(
|
||||
'updateResult should not notify cache listeners if cache option is false',
|
||||
() async {
|
||||
final key = queryKey();
|
||||
|
||||
final data1 = {"value": 'data 1'};
|
||||
final data2 = {"value": 'data 2'};
|
||||
|
||||
await queryClient.prefetchQuery(queryKey: key, queryFn: (_) => data1);
|
||||
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryClient,
|
||||
QueryObserverOptions<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(queryKey: key));
|
||||
await queryClient.prefetchQuery(queryKey: key, queryFn: (_) => data2);
|
||||
|
||||
final spy = SpyFn();
|
||||
final unsubscribe = queryClient.getQueryCache().subscribe(spy.fn1());
|
||||
observer.updateResult(NotifyOptions(cache: false));
|
||||
|
||||
expect(spy.calls, 0);
|
||||
|
||||
unsubscribe();
|
||||
});
|
||||
|
||||
test(
|
||||
'should not notify observer when the stale timeout expires and the current result is stale',
|
||||
() async {
|
||||
final key = queryKey();
|
||||
final queryFn = (_) => {'data': "data"};
|
||||
|
||||
await queryClient.prefetchQuery(queryKey: key, queryFn: queryFn);
|
||||
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryClient,
|
||||
QueryObserverOptions<Map<String, dynamic>, dynamic,
|
||||
Map<String, dynamic>, Map<String, dynamic>>(
|
||||
queryKey: key,
|
||||
queryFn: queryFn,
|
||||
staleTime: Duration(milliseconds: 20),
|
||||
));
|
||||
|
||||
final spy = SpyFn();
|
||||
final unsubscribe = observer.subscribe(spy.fn1());
|
||||
await queryClient.refetchQueries(queryKeys: key);
|
||||
await sleep(10);
|
||||
|
||||
// Force isStale to true
|
||||
// because no use case has been found to reproduce this condition
|
||||
// @ts-ignore
|
||||
// observer.getCurrentResult().isStale = true;
|
||||
await sleep(30);
|
||||
expect(spy.calls, 0);
|
||||
unsubscribe();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,320 +0,0 @@
|
||||
import 'package:fl_query/src/core/utils.dart';
|
||||
import 'package:test/expect.dart';
|
||||
import 'package:test/scaffolding.dart';
|
||||
|
||||
void main() {
|
||||
group('core/utils', () {
|
||||
group('replaceEqualDeep', () {
|
||||
test(
|
||||
'Should return the previous value When the next value is an equal primitive',
|
||||
() {
|
||||
expect(replaceEqualDeep(1, 1), equals(1));
|
||||
expect(replaceEqualDeep('1', '1'), equals('1'));
|
||||
expect(replaceEqualDeep(true, true), true);
|
||||
expect(replaceEqualDeep(false, false), false);
|
||||
expect(replaceEqualDeep(null, null), null);
|
||||
});
|
||||
test(
|
||||
'Should return the next value When the previous value is a different value',
|
||||
() {
|
||||
expect(replaceEqualDeep(1, 0), equals(0));
|
||||
expect(replaceEqualDeep(1, 2), equals(2));
|
||||
expect(replaceEqualDeep('1', '2'), equals('2'));
|
||||
expect(replaceEqualDeep(true, false), equals(false));
|
||||
expect(replaceEqualDeep(false, true), equals(true));
|
||||
});
|
||||
|
||||
test(
|
||||
'Should return the next value When the previous value is a different type',
|
||||
() {
|
||||
final array = [1];
|
||||
final object = {"a": "a"};
|
||||
expect(replaceEqualDeep(0, null), equals(null));
|
||||
expect(replaceEqualDeep(null, 0), equals(0));
|
||||
expect(replaceEqualDeep(2, null), equals(null));
|
||||
expect(replaceEqualDeep(null, 2), equals(2));
|
||||
expect(replaceEqualDeep({}, null), equals(null));
|
||||
expect(replaceEqualDeep([], null), equals(null));
|
||||
expect(replaceEqualDeep(array, object), equals(object));
|
||||
expect(replaceEqualDeep(object, array), equals(array));
|
||||
});
|
||||
|
||||
test(
|
||||
'Should return the previous value When the next value is an equal array',
|
||||
() {
|
||||
final prev = [1, 2];
|
||||
final next = [1, 2];
|
||||
expect(replaceEqualDeep(prev, next), equals(prev));
|
||||
});
|
||||
|
||||
test(
|
||||
'Should return a copy When the previous value is a different array subset',
|
||||
() {
|
||||
final prev = [1, 2];
|
||||
final next = [1, 2, 3];
|
||||
final result = replaceEqualDeep(prev, next);
|
||||
expect(result, equals(next));
|
||||
expect(result, isNot(equals(prev)));
|
||||
});
|
||||
|
||||
test(
|
||||
'Should return the previous value When the next value is an equal empty array',
|
||||
() {
|
||||
final prev = [];
|
||||
final next = [];
|
||||
expect(replaceEqualDeep(prev, next), equals(prev));
|
||||
});
|
||||
|
||||
test(
|
||||
'Should return the previous value When the next value is an equal empty object',
|
||||
() {
|
||||
final prev = {};
|
||||
final next = {};
|
||||
expect(replaceEqualDeep(prev, next), equals(prev));
|
||||
});
|
||||
|
||||
test(
|
||||
'Should return the previous value When the next value is an equal object',
|
||||
() {
|
||||
final prev = {"a": 'a'};
|
||||
final next = {"a": 'a'};
|
||||
expect(replaceEqualDeep(prev, next), equals(prev));
|
||||
});
|
||||
|
||||
test('Should replace different values in objects', () {
|
||||
final prev = {
|
||||
"a": {"b": 'b'},
|
||||
"c": 'c'
|
||||
};
|
||||
final next = {
|
||||
"a": {"b": 'b'},
|
||||
"c": 'd'
|
||||
};
|
||||
final result = replaceEqualDeep(prev, next);
|
||||
expect(result, equals(next));
|
||||
expect(result, isNot(prev));
|
||||
expect(result["a"], equals(prev["a"]));
|
||||
expect(result["c"], equals(next["c"]));
|
||||
});
|
||||
|
||||
test('Should replace different values in arrays', () {
|
||||
final prev = [
|
||||
1,
|
||||
{"a": 'a'},
|
||||
{
|
||||
"b": {"b": 'b'}
|
||||
},
|
||||
[1]
|
||||
];
|
||||
final next = [
|
||||
1,
|
||||
{"a": 'a'},
|
||||
{
|
||||
"b": {"b": 'c'}
|
||||
},
|
||||
[1]
|
||||
];
|
||||
final result = replaceEqualDeep(prev, next);
|
||||
expect(result, equals(next));
|
||||
expect(result, isNot(prev));
|
||||
expect(result[0], prev[0]);
|
||||
expect(result[1], prev[1]);
|
||||
expect((result[2] as Map)["b"]["b"], (next[2] as Map)["b"]["b"]);
|
||||
expect(result[3], prev[3]);
|
||||
});
|
||||
|
||||
test(
|
||||
'Should replace different values in arrays When the next value is a subset',
|
||||
() {
|
||||
final prev = [
|
||||
{"a": 'a'},
|
||||
{"b": 'b'},
|
||||
{"c": 'c'}
|
||||
];
|
||||
final next = [
|
||||
{"a": 'a'},
|
||||
{"b": 'b'}
|
||||
];
|
||||
final result = replaceEqualDeep(prev, next);
|
||||
expect(result, equals(next));
|
||||
expect(result, isNot(prev));
|
||||
expect(result[0], prev[0]);
|
||||
expect(result[1], prev[1]);
|
||||
expect(() => result[2], throwsRangeError);
|
||||
});
|
||||
|
||||
test(
|
||||
'Should replace different values in arrays When the next value is a superset',
|
||||
() {
|
||||
final prev = [
|
||||
{"a": 'a'},
|
||||
{"b": 'b'}
|
||||
];
|
||||
final next = [
|
||||
{"a": 'a'},
|
||||
{"b": 'b'},
|
||||
{"c": 'c'}
|
||||
];
|
||||
final result = replaceEqualDeep(prev, next);
|
||||
expect(result, equals(next));
|
||||
expect(result[0], equals(prev[0]));
|
||||
expect(result[1], equals(prev[1]));
|
||||
expect(result[2], equals(next[2]));
|
||||
});
|
||||
|
||||
test('Should copy objects which are not arrays or objects', () {
|
||||
final prev = [
|
||||
{"a": 'a'},
|
||||
{"b": 'b'},
|
||||
{"c": 'c'},
|
||||
1
|
||||
];
|
||||
final next = [
|
||||
{"a": 'a'},
|
||||
Map(),
|
||||
{"c": 'c'},
|
||||
2
|
||||
];
|
||||
final result = replaceEqualDeep(prev, next);
|
||||
expect(result[0], equals(prev[0]));
|
||||
expect(result[1], equals(next[1]));
|
||||
expect(result[2], equals(prev[2]));
|
||||
expect(result[3], equals(next[3]));
|
||||
});
|
||||
|
||||
test('Should support equal objects which are not arrays or objects', () {
|
||||
final map = new Map();
|
||||
final prev = [
|
||||
map,
|
||||
[1]
|
||||
];
|
||||
final next = [
|
||||
map,
|
||||
[1]
|
||||
];
|
||||
final result = replaceEqualDeep(prev, next);
|
||||
expect(result, equals(prev));
|
||||
});
|
||||
|
||||
test('Should support non equal objects which are not arrays or objects',
|
||||
() {
|
||||
final map1 = new Map();
|
||||
final map2 = new Map();
|
||||
final prev = [
|
||||
map1,
|
||||
[1]
|
||||
];
|
||||
final next = [
|
||||
map2,
|
||||
[1]
|
||||
];
|
||||
final result = replaceEqualDeep(prev, next);
|
||||
expect(result[0], equals(next[0]));
|
||||
expect(result[1], equals(prev[1]));
|
||||
});
|
||||
|
||||
test('Should replace all parent objects if some nested value changes',
|
||||
() {
|
||||
final prev = {
|
||||
"todo": {
|
||||
"id": '1',
|
||||
"meta": {"createdAt": 0},
|
||||
"state": {"done": false},
|
||||
},
|
||||
"otherTodo": {
|
||||
"id": '2',
|
||||
"meta": {"createdAt": 0},
|
||||
"state": {"done": true},
|
||||
},
|
||||
};
|
||||
final next = {
|
||||
"todo": {
|
||||
"id": '1',
|
||||
"meta": {"createdAt": 0},
|
||||
"state": {"done": true},
|
||||
},
|
||||
"otherTodo": {
|
||||
"id": '2',
|
||||
"meta": {"createdAt": 0},
|
||||
"state": {"done": true},
|
||||
},
|
||||
};
|
||||
final result = replaceEqualDeep(prev, next);
|
||||
expect(result, equals(next));
|
||||
expect(result["todo"] == prev["todo"], isFalse);
|
||||
expect(result["todo"] == next["todo"], isFalse);
|
||||
expect(result["todo"]["id"], equals((next["todo"] as Map)["id"]));
|
||||
expect(result["todo"]["meta"], equals((prev["todo"] as Map)["meta"]));
|
||||
expect(
|
||||
result["todo"]["state"],
|
||||
equals((next["todo"] as Map)["state"]),
|
||||
);
|
||||
expect(
|
||||
result["todo"]["state"]["done"],
|
||||
(next["todo"] as Map)["state"]["done"],
|
||||
);
|
||||
expect(result["otherTodo"], prev["otherTodo"]);
|
||||
});
|
||||
|
||||
test('Should replace all parent arrays if some nested value changes', () {
|
||||
final Map<String, List<Map>> prev = {
|
||||
"todos": [
|
||||
{
|
||||
"id": '1',
|
||||
"meta": {"createdAt": 0},
|
||||
"state": {"done": false}
|
||||
},
|
||||
{
|
||||
"id": '2',
|
||||
"meta": {"createdAt": 0},
|
||||
"state": {"done": true}
|
||||
},
|
||||
],
|
||||
};
|
||||
final Map<String, List<Map>> next = {
|
||||
"todos": [
|
||||
{
|
||||
"id": '1',
|
||||
"meta": {"createdAt": 0},
|
||||
"state": {"done": true}
|
||||
},
|
||||
{
|
||||
"id": '2',
|
||||
"meta": {"createdAt": 0},
|
||||
"state": {"done": true}
|
||||
},
|
||||
],
|
||||
};
|
||||
final result = replaceEqualDeep(prev, next);
|
||||
expect(result, equals(next));
|
||||
expect(result["todos"][0], isNot(equals(prev["todos"]?.first)));
|
||||
expect(
|
||||
result["todos"][0]?["id"],
|
||||
equals(next["todos"]?.first["id"]),
|
||||
);
|
||||
expect(
|
||||
result["todos"][0]?["meta"],
|
||||
equals(prev["todos"]?.first["meta"]),
|
||||
);
|
||||
expect(result["todos"][0]?["state"]["done"],
|
||||
next["todos"]?.first["state"]["done"]);
|
||||
expect(result["todos"][1], equals(prev["todos"]?[1]));
|
||||
});
|
||||
});
|
||||
|
||||
group(
|
||||
'matchMutation',
|
||||
() => {
|
||||
// test('should return false if mutationKey options is undefined', () => {
|
||||
// const filters = { mutationKey: 'key1' };
|
||||
// const queryClient = new QueryClient();
|
||||
// const mutation = new Mutation({
|
||||
// mutationId: 1,
|
||||
// mutationCache: queryClient.getMutationCache(),
|
||||
// options: {},
|
||||
// })
|
||||
// expect(matchMutation(filters, mutation)).toBeFalsy()
|
||||
// })
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user