QueryObserver test created
This commit is contained in:
@@ -120,7 +120,7 @@ class QueryFilters {
|
|||||||
bool? exact;
|
bool? exact;
|
||||||
bool? inactive;
|
bool? inactive;
|
||||||
bool Function(Query query)? predicate;
|
bool Function(Query query)? predicate;
|
||||||
bool? queryKey;
|
QueryKey? queryKey;
|
||||||
bool? stale;
|
bool? stale;
|
||||||
bool? fetching;
|
bool? fetching;
|
||||||
|
|
||||||
@@ -162,7 +162,7 @@ class RefetchableQueryFilters<TPageData> extends QueryFilters
|
|||||||
bool? exact,
|
bool? exact,
|
||||||
bool? inactive,
|
bool? inactive,
|
||||||
bool Function(Query query)? predicate,
|
bool Function(Query query)? predicate,
|
||||||
bool? queryKey,
|
QueryKey? queryKey,
|
||||||
bool? stale,
|
bool? stale,
|
||||||
bool? fetching,
|
bool? fetching,
|
||||||
this.refetchPage,
|
this.refetchPage,
|
||||||
@@ -212,7 +212,7 @@ class InvalidateQueryFilters<TPageData>
|
|||||||
bool? exact,
|
bool? exact,
|
||||||
bool? inactive,
|
bool? inactive,
|
||||||
bool Function(Query query)? predicate,
|
bool Function(Query query)? predicate,
|
||||||
bool? queryKey,
|
QueryKey? queryKey,
|
||||||
bool? stale,
|
bool? stale,
|
||||||
bool? fetching,
|
bool? fetching,
|
||||||
bool Function(TPageData lastPage, int index, List<TPageData> allPages)?
|
bool Function(TPageData lastPage, int index, List<TPageData> allPages)?
|
||||||
@@ -452,6 +452,11 @@ class QueryObserverOptions<
|
|||||||
QueryMeta? meta,
|
QueryMeta? meta,
|
||||||
bool? structuralSharing,
|
bool? structuralSharing,
|
||||||
bool? defaulted,
|
bool? defaulted,
|
||||||
|
ShouldRetryFunction<TError>? retry,
|
||||||
|
RetryDelayFunction<TError>? retryDelay,
|
||||||
|
QueryBehavior<TQueryFnData, TError, TQueryData>? behavior,
|
||||||
|
GetPreviousPageParamFunction<TQueryFnData>? getPreviousPageParam,
|
||||||
|
GetNextPageParamFunction<TQueryFnData>? getNextPageParam,
|
||||||
}) : super(
|
}) : super(
|
||||||
queryKey: queryKey,
|
queryKey: queryKey,
|
||||||
queryKeyHashFn: queryKeyHashFn,
|
queryKeyHashFn: queryKeyHashFn,
|
||||||
@@ -464,6 +469,11 @@ class QueryObserverOptions<
|
|||||||
meta: meta,
|
meta: meta,
|
||||||
structuralSharing: structuralSharing,
|
structuralSharing: structuralSharing,
|
||||||
defaulted: defaulted,
|
defaulted: defaulted,
|
||||||
|
behavior: behavior,
|
||||||
|
getNextPageParam: getNextPageParam,
|
||||||
|
getPreviousPageParam: getPreviousPageParam,
|
||||||
|
retry: retry,
|
||||||
|
retryDelay: retryDelay,
|
||||||
);
|
);
|
||||||
|
|
||||||
QueryObserverOptions.fromJson(Map<String, dynamic> json)
|
QueryObserverOptions.fromJson(Map<String, dynamic> json)
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:collection';
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:fl_query/src/core/models.dart';
|
import 'package:fl_query/src/core/models.dart';
|
||||||
@@ -280,7 +282,9 @@ class Query<TQueryFnData extends Map<String, dynamic>, TError,
|
|||||||
data = prevData as TData;
|
data = prevData as TData;
|
||||||
} else if (this.options.structuralSharing != false) {
|
} else if (this.options.structuralSharing != false) {
|
||||||
// Structurally share data between prev and new data if needed
|
// Structurally share data between prev and new data if needed
|
||||||
data = replaceEqualDeep(prevData, data);
|
final merged =
|
||||||
|
Map<String, dynamic>.from(replaceEqualDeep(prevData, data));
|
||||||
|
data = merged as TData;
|
||||||
}
|
}
|
||||||
// Set data and mark it as cached
|
// Set data and mark it as cached
|
||||||
_dispatch(Action(
|
_dispatch(Action(
|
||||||
@@ -360,7 +364,7 @@ class Query<TQueryFnData extends Map<String, dynamic>, TError,
|
|||||||
// Use the options from the first observer with a query function if no function is found.
|
// Use the options from the first observer with a query function if no function is found.
|
||||||
// This can happen when the query is hydrated or created with setQueryData.
|
// This can happen when the query is hydrated or created with setQueryData.
|
||||||
if (this.options.queryFn == null) {
|
if (this.options.queryFn == null) {
|
||||||
var observer =
|
final observer =
|
||||||
_observers.firstWhereOrNull((x) => x.options.queryFn != null);
|
_observers.firstWhereOrNull((x) => x.options.queryFn != null);
|
||||||
if (observer != null) {
|
if (observer != null) {
|
||||||
_setOptions(QueryOptions<TQueryFnData, TError, TData>(
|
_setOptions(QueryOptions<TQueryFnData, TError, TData>(
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ class QueryCache extends Subscribable<QueryCacheListener> {
|
|||||||
|
|
||||||
void notify(QueryCacheNotifyEvent event) {
|
void notify(QueryCacheNotifyEvent event) {
|
||||||
notifyManager.batch(() {
|
notifyManager.batch(() {
|
||||||
for (var listener in listeners) {
|
for (final listener in listeners) {
|
||||||
listener(event);
|
listener(event);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -266,7 +266,7 @@ class QueryClient {
|
|||||||
QueryFunction<TQueryFnData, dynamic>? queryFn,
|
QueryFunction<TQueryFnData, dynamic>? queryFn,
|
||||||
FetchQueryOptions<TQueryFnData, TError, TData>? options,
|
FetchQueryOptions<TQueryFnData, TError, TData>? options,
|
||||||
}) {
|
}) {
|
||||||
var defaultedOptions = this.defaultQueryOptions(
|
final defaultedOptions = this.defaultQueryOptions(
|
||||||
QueryObserverOptions<Map<String, dynamic>, dynamic, Map<String, dynamic>,
|
QueryObserverOptions<Map<String, dynamic>, dynamic, Map<String, dynamic>,
|
||||||
TData>(
|
TData>(
|
||||||
queryFn: queryFn,
|
queryFn: queryFn,
|
||||||
@@ -285,7 +285,7 @@ class QueryClient {
|
|||||||
);
|
);
|
||||||
// returning 0 indicates turing off retry
|
// returning 0 indicates turing off retry
|
||||||
defaultedOptions.retry ??= (_, __) => 0;
|
defaultedOptions.retry ??= (_, __) => 0;
|
||||||
var query = _queryCache.build<Map<String, dynamic>, dynamic, TData>(
|
final query = _queryCache.build<Map<String, dynamic>, dynamic, TData>(
|
||||||
this, defaultedOptions);
|
this, defaultedOptions);
|
||||||
return query.isStaleByTime(defaultedOptions.staleTime)
|
return query.isStaleByTime(defaultedOptions.staleTime)
|
||||||
? query.fetch(defaultedOptions)
|
? query.fetch(defaultedOptions)
|
||||||
@@ -313,7 +313,7 @@ class QueryClient {
|
|||||||
TQueryData extends Map<String, dynamic>>(
|
TQueryData extends Map<String, dynamic>>(
|
||||||
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>? options) {
|
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>? options) {
|
||||||
if (options?.defaulted == true) return options!;
|
if (options?.defaulted == true) return options!;
|
||||||
var defaultedOptions =
|
final defaultedOptions =
|
||||||
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>.fromJson({
|
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>.fromJson({
|
||||||
...(_defaultOptions.queries?.toJson() ?? {}),
|
...(_defaultOptions.queries?.toJson() ?? {}),
|
||||||
...(getQueryDefaults(options?.queryKey)?.toJson() ?? {}),
|
...(getQueryDefaults(options?.queryKey)?.toJson() ?? {}),
|
||||||
|
|||||||
@@ -94,6 +94,9 @@ class QueryObserver<
|
|||||||
Timer? _refetchInterval;
|
Timer? _refetchInterval;
|
||||||
Duration? _currentRefetchInterval;
|
Duration? _currentRefetchInterval;
|
||||||
|
|
||||||
|
@protected
|
||||||
|
Timer? get refetchInterval => _refetchInterval;
|
||||||
|
|
||||||
QueryObserver(
|
QueryObserver(
|
||||||
this._client,
|
this._client,
|
||||||
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>? _options,
|
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>? _options,
|
||||||
@@ -225,9 +228,9 @@ class QueryObserver<
|
|||||||
throw UnimplementedError("COULD NOT IMPLEMENT DUE TO LANGUAGE LIMITATIONS");
|
throw UnimplementedError("COULD NOT IMPLEMENT DUE TO LANGUAGE LIMITATIONS");
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<QueryObserverResult<TData, TError>> getNextResult(
|
Future<QueryObserverResult<TData, TError>> getNextResult([
|
||||||
bool? throwOnError,
|
bool? throwOnError,
|
||||||
) {
|
]) {
|
||||||
final completer = Completer<QueryObserverResult<TData, TError>>();
|
final completer = Completer<QueryObserverResult<TData, TError>>();
|
||||||
var unsubscribe;
|
var unsubscribe;
|
||||||
unsubscribe = subscribe((result) {
|
unsubscribe = subscribe((result) {
|
||||||
@@ -445,7 +448,8 @@ class QueryObserver<
|
|||||||
try {
|
try {
|
||||||
data = options.select?.call(state.data);
|
data = options.select?.call(state.data);
|
||||||
if (options.structuralSharing != false) {
|
if (options.structuralSharing != false) {
|
||||||
data = replaceEqualDeep(prevResult?.data, data);
|
data = Map<String, dynamic>.from(
|
||||||
|
replaceEqualDeep(prevResult?.data, data)) as TData;
|
||||||
}
|
}
|
||||||
if (options.select != null && data != null) {
|
if (options.select != null && data != null) {
|
||||||
_previousSelect = SelectQuery<TQueryData, TData>(
|
_previousSelect = SelectQuery<TQueryData, TData>(
|
||||||
|
|||||||
@@ -6,3 +6,46 @@ Uuid uuid = Uuid();
|
|||||||
QueryKey queryKey() {
|
QueryKey queryKey() {
|
||||||
return QueryKey("query_${uuid.v4()}");
|
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,8 +1,14 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:fl_query/src/core/core.dart';
|
import 'package:fl_query/src/core/core.dart';
|
||||||
|
import 'package:fl_query/src/core/query_observer.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import '../../helpers/utils.dart';
|
import '../../helpers/utils.dart';
|
||||||
|
|
||||||
|
typedef QueryFn = FutureOr<Map<String, dynamic>> Function(
|
||||||
|
QueryFunctionContext<dynamic>);
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group('QueryObserver', () {
|
group('QueryObserver', () {
|
||||||
late QueryClient queryClient;
|
late QueryClient queryClient;
|
||||||
@@ -18,6 +24,7 @@ void main() {
|
|||||||
|
|
||||||
test('should trigger a fetch when subscribed', () async {
|
test('should trigger a fetch when subscribed', () async {
|
||||||
final key = queryKey();
|
final key = queryKey();
|
||||||
|
;
|
||||||
int calls = 0;
|
int calls = 0;
|
||||||
queryFn(context) {
|
queryFn(context) {
|
||||||
calls++;
|
calls++;
|
||||||
@@ -64,5 +71,839 @@ void main() {
|
|||||||
expect(results[3].data, {"data": 2});
|
expect(results[3].data, {"data": 2});
|
||||||
expect(results[3].status, QueryStatus.success);
|
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));
|
||||||
|
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": count},
|
||||||
|
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 = 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> 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 Future.delayed(Duration(milliseconds: 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;
|
||||||
|
observer.getNextResult(true).catchError((e) {
|
||||||
|
error = e;
|
||||||
|
});
|
||||||
|
queryClient
|
||||||
|
.prefetchQuery<Map<String, dynamic>, dynamic, Map<String, dynamic>>(
|
||||||
|
queryKey: key, queryFn: (_) => Future.error('reject'));
|
||||||
|
await sleep(50);
|
||||||
|
expect(error, 'reject');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should stop retry when unsubscribing', () 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({"data": 'reject'});
|
||||||
|
},
|
||||||
|
retry: (_, __) => 10,
|
||||||
|
retryDelay: (_, __) => 50,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final unsubscribe = observer.subscribe();
|
||||||
|
await sleep(70);
|
||||||
|
unsubscribe();
|
||||||
|
await sleep(200);
|
||||||
|
expect(count, 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
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, isNull);
|
||||||
|
unsubscribe();
|
||||||
|
// @ts-expect-error
|
||||||
|
expect(observer.refetchInterval, isNotNull);
|
||||||
|
await sleep(10);
|
||||||
|
expect(queryClient.getQueryCache().find(key), isNotNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
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()
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user