Jonny Borges

fix microtask and routes

... ... @@ -7,14 +7,11 @@ import 'package:get/get.dart';
// import 'package:get_test/get_test.dart';
import 'package:matcher/matcher.dart' as m;
import '../lib/pages/home/domain/adapters/repository_adapter.dart';
import '../lib/pages/home/domain/entity/cases_model.dart';
import '../lib/pages/home/presentation/controllers/home_controller.dart';
class MockRepositorySuccess implements IHomeRepository {
@override
Future<CasesModel> getCases() async {
return CasesModel(
... ...
typedef ValueUpdater<T> = T Function();
/// This allows a value of type T or T?
/// to be treated as a value of type T?.
///
... ...
... ... @@ -47,27 +47,6 @@ extension Inst on GetInterface {
// );
// }
/// async version of `Get.put()`.
/// Awaits for the resolution of the Future from `builder()` parameter and
/// stores the Instance returned.
Future<S> putAsync<S>(
AsyncInstanceBuilderCallback<S> builder, {
String? tag,
bool permanent = false,
}) async {
return put<S>(await builder(), tag: tag, permanent: permanent);
}
/// Injects an instance `<S>` in memory to be globally accessible.
///
/// No need to define the generic type `<S>` as it's inferred from
/// the [dependency]
///
/// - [dependency] The Instance to be injected.
/// - [tag] optionally, use a [tag] as an "id" to create multiple records of
/// the same Type<[S]>
/// - [permanent] keeps the Instance in memory, not following
/// `Get.smartManagement` rules.
S put<S>(
S dependency, {
String? tag,
... ...
... ... @@ -1253,8 +1253,6 @@ extension GetNavigationExt on GetInterface {
set parameters(Map<String, String?> newParameters) =>
_getxController.parameters = newParameters;
bool get testMode => _getxController.testMode;
set testMode(bool isTest) => _getxController.testMode = isTest;
... ...
... ... @@ -19,7 +19,8 @@ class GetInformationParser extends RouteInformationParser<RouteDecoder> {
if (location == '/') {
//check if there is a corresponding page
//if not, relocate to initialRoute
if (!(Get.rootController.routerDelegate as GetDelegate).registeredRoutes
if (!(Get.rootController.routerDelegate as GetDelegate)
.registeredRoutes
.any((element) => element.name == '/')) {
location = initialRoute;
}
... ...
... ... @@ -45,7 +45,7 @@ class GetPage<T> extends Page<T> {
final List<GetPage> children;
final List<GetMiddleware>? middlewares;
// final PathDecoded path;
final PathDecoded path;
final GetPage? unknownRoute;
final bool showCupertinoParallax;
... ... @@ -82,7 +82,7 @@ class GetPage<T> extends Page<T> {
PreventDuplicateHandlingMode.reorderRoutes,
this.completer,
LocalKey? key,
}) : // path = _nameToRegex(name),
}) : path = _nameToRegex(name),
assert(name.startsWith('/'),
'It is necessary to start route name [$name] with a slash: /$name'),
super(
... ... @@ -168,26 +168,26 @@ class GetPage<T> extends Page<T> {
return _page;
}
// static PathDecoded _nameToRegex(String path) {
// var keys = <String?>[];
static PathDecoded _nameToRegex(String path) {
var keys = <String?>[];
// String _replace(Match pattern) {
// var buffer = StringBuffer('(?:');
String _replace(Match pattern) {
var buffer = StringBuffer('(?:');
// if (pattern[1] != null) buffer.write('.');
// buffer.write('([\\w%+-._~!\$&\'()*,;=:@]+))');
// if (pattern[3] != null) buffer.write('?');
if (pattern[1] != null) buffer.write('.');
buffer.write('([\\w%+-._~!\$&\'()*,;=:@]+))');
if (pattern[3] != null) buffer.write('?');
// keys.add(pattern[2]);
// return "$buffer";
// }
keys.add(pattern[2]);
return "$buffer";
}
// var stringPath = '$path/?'
// .replaceAllMapped(RegExp(r'(\.)?:(\w+)(\?)?'), _replace)
// .replaceAll('//', '/');
var stringPath = '$path/?'
.replaceAllMapped(RegExp(r'(\.)?:(\w+)(\?)?'), _replace)
.replaceAll('//', '/');
// return PathDecoded(RegExp('^$stringPath\$'), keys);
// }
return PathDecoded(RegExp('^$stringPath\$'), keys);
}
@override
bool operator ==(Object other) {
... ... @@ -205,20 +205,20 @@ class GetPage<T> extends Page<T> {
}
}
// @immutable
// class PathDecoded {
// final RegExp regex;
// final List<String?> keys;
// const PathDecoded(this.regex, this.keys);
@immutable
class PathDecoded {
final RegExp regex;
final List<String?> keys;
const PathDecoded(this.regex, this.keys);
// @override
// int get hashCode => regex.hashCode;
@override
int get hashCode => regex.hashCode;
// @override
// bool operator ==(Object other) {
// if (identical(this, other)) return true;
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
// return other is PathDecoded &&
// other.regex == regex; // && listEquals(other.keys, keys);
// }
// }
return other is PathDecoded &&
other.regex == regex; // && listEquals(other.keys, keys);
}
}
... ...
... ... @@ -26,31 +26,28 @@ class GetDelegate extends RouterDelegate<RouteDecoder>
List<RouteDecoder> get activePages => _activePages;
final _routeTree = ParseRouteTree();
final _routeTree = ParseRouteTree(routes: []);
final List<GetPage> _routes = [];
List<GetPage> get registeredRoutes => _routes;
List<GetPage> get registeredRoutes => _routeTree.routes;
void addPages(List<GetPage> getPages) {
_routes.addRoutes(getPages);
_routeTree.addRoutes(getPages);
}
void clearRouteTree() {
_routes.clear();
_routeTree.routes.clear();
}
void addPage(GetPage getPage) {
_routes.addRoute(getPage);
_routeTree.addRoute(getPage);
}
void removePage(GetPage getPage) {
_routes.removeRoute(getPage);
_routeTree.removeRoute(getPage);
}
RouteDecoder? matchRoute(String name, {PageSettings? arguments}) {
final settings = _buildPageSettings(name, arguments);
return _getRouteDecoder(settings);
RouteDecoder matchRoute(String name, {PageSettings? arguments}) {
return _routeTree.matchRoute(name, arguments: arguments);
}
// GlobalKey<NavigatorState> get navigatorKey => Get.key;
... ... @@ -141,9 +138,7 @@ class GetDelegate extends RouterDelegate<RouteDecoder>
}
Map<String, String> get parameters {
return currentConfiguration?.route?.parameters ??
// currentConfiguration?.pageSettings?.params ??
{};
return currentConfiguration?.pageSettings?.params ?? {};
}
PageSettings? get pageSettings {
... ... @@ -365,7 +360,7 @@ class GetDelegate extends RouterDelegate<RouteDecoder>
}) async {
routeName = _cleanRouteName("/${page.runtimeType}");
// if (preventDuplicateHandlingMode ==
// PreventDuplicateHandlingMode.Recreate) {
//PreventDuplicateHandlingMode.Recreate) {
// routeName = routeName + page.hashCode.toString();
// }
... ... @@ -384,14 +379,14 @@ class GetDelegate extends RouterDelegate<RouteDecoder>
preventDuplicateHandlingMode: preventDuplicateHandlingMode,
);
_routes.addRoute(getPage);
_routeTree.addRoute(getPage);
final args = _buildPageSettings(routeName, arguments);
final route = _getRouteDecoder<T>(args);
final result = await _push<T>(
route!,
rebuildStack: rebuildStack,
);
_routes.removeRoute(getPage);
_routeTree.removeRoute(getPage);
return result;
}
... ... @@ -628,7 +623,7 @@ class GetDelegate extends RouterDelegate<RouteDecoder>
Future<T?> _replace<T>(PageSettings arguments, GetPage<T> page) async {
final index = _activePages.length > 1 ? _activePages.length - 1 : 0;
_routes.addRoute(page);
_routeTree.addRoute(page);
final activePage = _getRouteDecoder(arguments);
... ... @@ -638,7 +633,7 @@ class GetDelegate extends RouterDelegate<RouteDecoder>
notifyListeners();
final result = await activePage.route?.completer?.future as Future<T?>?;
_routes.removeRoute(page);
_routeTree.removeRoute(page);
return result;
}
... ... @@ -683,8 +678,7 @@ class GetDelegate extends RouterDelegate<RouteDecoder>
page = uri.toString();
}
final decoder =
_routeTree.matchRoute(registeredRoutes, page, arguments: arguments);
final decoder = _routeTree.matchRoute(page, arguments: arguments);
final route = decoder.route;
if (route == null) return null;
... ... @@ -707,7 +701,7 @@ class GetDelegate extends RouterDelegate<RouteDecoder>
completer: _activePages.isEmpty ? null : Completer(),
arguments: arguments,
parameters: parameters,
// key: ValueKey(arguments.name),
key: ValueKey(arguments.name),
);
return decoder;
... ...
... ... @@ -13,10 +13,6 @@ class Dependencies {
return find<S>();
}
Future<S> putAsync<S>(AsyncInstanceBuilderCallback<S> builder,
{String? tag, bool permanent = false}) async =>
Get.putAsync<S>(builder, tag: tag, permanent: permanent);
void create<S>(InstanceBuilderCallback<S> builder,
{String? tag, bool permanent = true}) =>
Get.create<S>(builder, tag: tag, permanent: permanent);
... ...
import 'dart:math';
import 'package:flutter/foundation.dart';
import '../../../route_manager.dart';
... ... @@ -18,11 +16,10 @@ class RouteDecoder {
final args = PageSettings(uri);
final decoder = (Get.rootController.routerDelegate as GetDelegate)
.matchRoute(location, arguments: args);
decoder!.route = decoder.route?.copy(
decoder.route = decoder.route?.copy(
completer: null,
arguments: args,
parameters: decoder.parameters,
parameters: args.params,
);
return decoder;
}
... ... @@ -81,62 +78,96 @@ class RouteDecoder {
}
class ParseRouteTree {
RouteDecoder matchRoute(List<GetPage> routes, String name,
{PageSettings? arguments}) {
final args = arguments ?? PageSettings(Uri.parse(name));
final treeBranch = routes
.where((e) => RouteParser.hasMatch(
pushedRoute: name, routeName: e.name, withChildren: true))
.map((e) {
final parameters =
RouteParser.parse(pushedRoute: name, routeName: e.name).parameters;
final routeParams = e.parameters;
if (routeParams != null) {
parameters.addAll(routeParams);
}
if (args.params.isNotEmpty) {
parameters.addAll(args.params);
}
args.params.clear();
args.params.addAll(parameters);
return e.copy(
settings: args,
parameters: parameters,
ParseRouteTree({
required this.routes,
});
final List<GetPage> routes;
RouteDecoder matchRoute(String name, {PageSettings? arguments}) {
final uri = Uri.parse(name);
// /home/profile/123 => home,profile,123 => /,/home,/home/profile,/home/profile/123
final split = uri.path.split('/').where((element) => element.isNotEmpty);
var curPath = '/';
final cumulativePaths = <String>[
'/',
];
for (var item in split) {
if (curPath.endsWith('/')) {
curPath += item;
} else {
curPath += '/$item';
}
cumulativePaths.add(curPath);
}
final treeBranch = cumulativePaths
.map((e) => MapEntry(e, _findRoute(e)))
.where((element) => element.value != null)
///Prevent page be disposed
.map((e) => MapEntry(e.key, e.value!.copy(key: ValueKey(e.key))))
.toList();
final params = Map<String, String>.from(uri.queryParameters);
if (treeBranch.isNotEmpty) {
//route is found, do further parsing to get nested query params
final lastRoute = treeBranch.last;
final parsedParams = _parseParams(name, lastRoute.value.path);
if (parsedParams.isNotEmpty) {
params.addAll(parsedParams);
}
//copy parameters to all pages.
final mappedTreeBranch = treeBranch
.map(
(e) => e.value.copy(
parameters: {
if (e.value.parameters != null) ...e.value.parameters!,
...params,
},
name: e.key,
),
)
.toList();
arguments?.params.clear();
arguments?.params.addAll(params);
return RouteDecoder(
mappedTreeBranch,
arguments,
);
}).toList();
}
arguments?.params.clear();
arguments?.params.addAll(params);
//route not found
return RouteDecoder(
treeBranch,
treeBranch.map((e) => e.value).toList(),
arguments,
);
}
}
extension FirstWhereOrNullExt<T> on List<GetPage<T>> {
void addRoutes(List<GetPage<T>> getPages) {
void addRoutes<T>(List<GetPage<T>> getPages) {
for (final route in getPages) {
addRoute(route);
}
}
void removeRoutes(List<GetPage<T>> getPages) {
void removeRoutes<T>(List<GetPage<T>> getPages) {
for (final route in getPages) {
removeRoute(route);
}
}
void removeRoute(GetPage<T> route) {
remove(route);
void removeRoute<T>(GetPage<T> route) {
routes.remove(route);
for (var page in _flattenPage(route)) {
removeRoute(page);
}
}
void addRoute(GetPage<T> route) {
add(route);
void addRoute<T>(GetPage<T> route) {
routes.add(route);
// Add Page children.
for (var page in _flattenPage(route)) {
... ... @@ -144,15 +175,14 @@ extension FirstWhereOrNullExt<T> on List<GetPage<T>> {
}
}
List<GetPage<T>> _flattenPage(GetPage<T> route) {
final result = <GetPage<T>>[];
List<GetPage> _flattenPage(GetPage route) {
final result = <GetPage>[];
if (route.children.isEmpty) {
return result;
}
var parentPathOld = route.name;
final parentPath = route.name;
for (var page in route.children) {
final parentPath2 = (parentPathOld + page.name).replaceAll(r'//', '/');
// Add Parent middlewares to children
final parentMiddlewares = [
if (page.middlewares != null) ...page.middlewares!,
... ... @@ -160,146 +190,71 @@ extension FirstWhereOrNullExt<T> on List<GetPage<T>> {
];
result.add(
_addChild(
page as GetPage<T>,
parentPath2,
page,
parentPath,
parentMiddlewares,
),
);
final children = _flattenPage(page);
// for (var child in children) {
// final parentPath = (parentPath2 + page.name).replaceAll(r'//', '/');
// result.add(_addChild(
// child,
// parentPath,
// [
// ...parentMiddlewares,
// if (child.middlewares != null) ...child.middlewares!,
// ],
// ));
// }
for (var child in children) {
result.add(_addChild(
child,
parentPath,
[
...parentMiddlewares,
if (child.middlewares != null) ...child.middlewares!,
],
));
}
}
return result;
}
/// Change the Path for a [GetPage]
GetPage<T> _addChild(
GetPage<T> origin, String parentPath, List<GetMiddleware> middlewares) {
GetPage _addChild(
GetPage origin, String parentPath, List<GetMiddleware> middlewares) {
return origin.copy(
middlewares: middlewares,
name: parentPath,
key: ValueKey(parentPath),
name: (parentPath + origin.name).replaceAll(r'//', '/'),
// key:
);
}
// GetPage<T>? _findRoute(String name) {
// final value = firstWhereOrNull(
// (route) => route.path.regex.hasMatch(name),
// );
// return value;
// }
}
extension FirstWhereExt<T> on List<T> {
/// The first element satisfying [test], or `null` if there are none.
T? firstWhereOrNull(bool Function(T element) test) {
for (var element in this) {
if (test(element)) return element;
}
return null;
}
}
class RouteParser {
static RouteParser parse({required String pushedRoute, required routeName}) {
final data = RouteParser(pushedRoute: pushedRoute, routeName: routeName);
final minLength =
min(data.originalPathSegments.length, data.newPathSegments.length);
for (var i = 0; i < minLength; i++) {
final originalPathSegment = data.originalPathSegments[i];
final newPathSegment = Uri.parse(data.newPathSegments[i]);
if (originalPathSegment.startsWith(':')) {
final key = originalPathSegment.replaceFirst(':', '');
data.parameters[key] = newPathSegment.toString();
data.matchingSegments.add(newPathSegment);
continue;
}
if (newPathSegment.path == originalPathSegment) {
data.matchingSegments.add(newPathSegment);
data.parameters.addAll(data.newRouteUri.queryParameters);
continue;
} else {
break;
}
}
GetPage? _findRoute(String name) {
final value = routes.firstWhereOrNull(
(route) => route.path.regex.hasMatch(name),
);
return data;
return value;
}
static bool hasMatch({
required String pushedRoute,
required routeName,
bool withChildren = false,
}) {
final data = RouteParser(pushedRoute: pushedRoute, routeName: routeName);
final matches = <bool>[];
final minLength =
min(data.originalPathSegments.length, data.newPathSegments.length);
if ((!withChildren &&
data.newPathSegments.length > data.originalPathSegments.length) ||
data.newPathSegments.length < data.originalPathSegments.length) {
matches.add(false);
Map<String, String> _parseParams(String path, PathDecoded routePath) {
final params = <String, String>{};
var idx = path.indexOf('?');
if (idx > -1) {
path = path.substring(0, idx);
final uri = Uri.tryParse(path);
if (uri != null) {
params.addAll(uri.queryParameters);
}
for (var i = 0; i < minLength; i++) {
final originalPathSegment = data.originalPathSegments[i];
final newPathSegment = Uri.parse(data.newPathSegments[i]);
if (originalPathSegment.startsWith(':')) {
matches.add(true);
continue;
}
var paramsMatch = routePath.regex.firstMatch(path);
if (newPathSegment.path == originalPathSegment) {
matches.add(true);
continue;
} else {
matches.add(false);
break;
for (var i = 0; i < routePath.keys.length; i++) {
var param = Uri.decodeQueryComponent(paramsMatch![i + 1]!);
params[routePath.keys[i]!] = param;
}
return params;
}
}
return matches.every((element) => element);
extension FirstWhereOrNullExt<T> on List<T> {
/// The first element satisfying [test], or `null` if there are none.
T? firstWhereOrNull(bool Function(T element) test) {
for (var element in this) {
if (test(element)) return element;
}
RouteParser({required String routeName, required String pushedRoute})
: _cleanRouteName = '/' +
routeName
.replaceAll(RegExp(r'^\s+|\s+$'), '')
.replaceAll(RegExp(r'^\/+|\/+$'), ''),
newRouteUri = Uri.parse(pushedRoute) {
originalRouteUri = Uri(path: _cleanRouteName);
return null;
}
late final Uri originalRouteUri;
final Uri newRouteUri;
final Map<String, String> parameters = <String, String>{};
final List<Uri> matchingSegments = <Uri>[];
final String _cleanRouteName;
List<String> get newPathSegments => newRouteUri.pathSegments;
List<String> get originalPathSegments => originalRouteUri.pathSegments;
String get matchingPath => '/' + matchingSegments.join('/');
@override
String toString() =>
'RouteParser(originalRouteUri: $originalRouteUri, newRouteUri: $newRouteUri, _cleanRouteName: $_cleanRouteName)';
}
... ...
... ... @@ -234,9 +234,8 @@ class PageRedirect {
if (settings == null && route != null) {
settings = route;
}
final match = context.navigation
.matchRoute((settings!.arguments as PageSettings).name);
Get.parameters = match!.parameters;
final match = context.navigation.matchRoute(settings!.name!);
Get.parameters = match.parameters;
// No Match found
if (match.route == null) {
... ...
... ... @@ -147,8 +147,7 @@ class GetRouterOutlet extends RouterOutlet<GetDelegate, RouteDecoder> {
return ret;
},
emptyPage: (delegate) =>
delegate.matchRoute(initialRoute)?.route ??
delegate.notFoundRoute,
delegate.matchRoute(initialRoute).route ?? delegate.notFoundRoute,
key: Get.nestedKey(anchorRoute)?.navigatorKey,
delegate: delegate,
);
... ...
... ... @@ -68,18 +68,6 @@ mixin StateMixin<T> on ListNotifier {
if (status != this.status) {
this.status = status;
}
// var _canUpdate = false;
// if (status != null) {
// _status = status;
// _canUpdate = true;
// }
// if (newState != _value) {
// _value = newState;
// _canUpdate = true;
// }
// if (_canUpdate) {
// refresh();
// }
}
void futurize(Future<T> Function() Function() body,
... ...
import 'dart:async';
import 'dart:collection';
import 'package:flutter/foundation.dart';
... ... @@ -24,8 +23,8 @@ class ListNotifierGroup = ListNotifier with ListNotifierGroupMixin;
mixin ListNotifierSingleMixin on Listenable {
List<GetStateUpdate>? _updaters = <GetStateUpdate>[];
int _version = 0;
int _microtaskVersion = 0;
// final int _version = 0;
// final int _microtaskVersion = 0;
@override
Disposer addListener(GetStateUpdate listener) {
... ... @@ -61,16 +60,18 @@ mixin ListNotifierSingleMixin on Listenable {
}
void _notifyUpdate() {
if (_microtaskVersion == _version) {
_microtaskVersion++;
scheduleMicrotask(() {
_version++;
_microtaskVersion = _version;
for (var element in _updaters!) {
// if (_microtaskVersion == _version) {
// _microtaskVersion++;
// scheduleMicrotask(() {
// _version++;
// _microtaskVersion = _version;
final list = _updaters?.toList() ?? [];
for (var element in list) {
element();
}
});
}
// });
// }
}
bool get isDisposed => _updaters == null;
... ...
... ... @@ -2,7 +2,6 @@ import 'dart:async';
import 'package:flutter/widgets.dart';
import '../../../get_core/src/typedefs.dart';
import 'list_notifier.dart';
typedef ValueBuilderUpdateCallback<T> = void Function(T snapshot);
... ...
export 'src/equality/equality.dart';
export 'src/extensions/export.dart';
export 'src/get_utils/get_utils.dart';
export 'src/platform/platform.dart';
... ...
library equality;
import 'dart:collection';
mixin Equality {
List get props;
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
const DeepCollectionEquality().equals(props, other.props);
}
@override
int get hashCode {
return runtimeType.hashCode ^ const DeepCollectionEquality().hash(props);
}
}
const int _hashMask = 0x7fffffff;
/// A generic equality relation on objects.
abstract class IEquality<E> {
const factory IEquality() = DefaultEquality<E>;
/// Compare two elements for being equal.
///
/// This should be a proper equality relation.
bool equals(E e1, E e2);
/// Get a hashcode of an element.
///
/// The hashcode should be compatible with [equals], so that if
/// `equals(a, b)` then `hash(a) == hash(b)`.
int hash(E e);
/// Test whether an object is a valid argument to [equals] and [hash].
///
/// Some implementations may be restricted to only work on specific types
/// of objects.
bool isValidKey(Object? o);
}
class DefaultEquality<E> implements IEquality<E> {
const DefaultEquality();
@override
bool equals(Object? e1, Object? e2) => e1 == e2;
@override
int hash(Object? e) => e.hashCode;
@override
bool isValidKey(Object? o) => true;
}
/// Equality of objects that compares only the identity of the objects.
class IdentityEquality<E> implements IEquality<E> {
const IdentityEquality();
@override
bool equals(E e1, E e2) => identical(e1, e2);
@override
int hash(E e) => identityHashCode(e);
@override
bool isValidKey(Object? o) => true;
}
class DeepCollectionEquality implements IEquality {
final IEquality _base = const DefaultEquality<Never>();
final bool _unordered = false;
const DeepCollectionEquality();
@override
bool equals(e1, e2) {
if (e1 is Set) {
return e2 is Set && SetEquality(this).equals(e1, e2);
}
if (e1 is Map) {
return e2 is Map && MapEquality(keys: this, values: this).equals(e1, e2);
}
if (e1 is List) {
return e2 is List && ListEquality(this).equals(e1, e2);
}
if (e1 is Iterable) {
return e2 is Iterable && IterableEquality(this).equals(e1, e2);
}
return _base.equals(e1, e2);
}
@override
int hash(Object? o) {
if (o is Set) return SetEquality(this).hash(o);
if (o is Map) return MapEquality(keys: this, values: this).hash(o);
if (!_unordered) {
if (o is List) return ListEquality(this).hash(o);
if (o is Iterable) return IterableEquality(this).hash(o);
} else if (o is Iterable) {
return UnorderedIterableEquality(this).hash(o);
}
return _base.hash(o);
}
@override
bool isValidKey(Object? o) =>
o is Iterable || o is Map || _base.isValidKey(o);
}
/// Equality on lists.
///
/// Two lists are equal if they have the same length and their elements
/// at each index are equal.
class ListEquality<E> implements IEquality<List<E>> {
final IEquality<E> _elementEquality;
const ListEquality(
[IEquality<E> elementEquality = const DefaultEquality<Never>()])
: _elementEquality = elementEquality;
@override
bool equals(List<E>? list1, List<E>? list2) {
if (identical(list1, list2)) return true;
if (list1 == null || list2 == null) return false;
var length = list1.length;
if (length != list2.length) return false;
for (var i = 0; i < length; i++) {
if (!_elementEquality.equals(list1[i], list2[i])) return false;
}
return true;
}
@override
int hash(List<E>? list) {
if (list == null) return null.hashCode;
// Jenkins's one-at-a-time hash function.
// This code is almost identical to the one in IterableEquality, except
// that it uses indexing instead of iterating to get the elements.
var hash = 0;
for (var i = 0; i < list.length; i++) {
var c = _elementEquality.hash(list[i]);
hash = (hash + c) & _hashMask;
hash = (hash + (hash << 10)) & _hashMask;
hash ^= (hash >> 6);
}
hash = (hash + (hash << 3)) & _hashMask;
hash ^= (hash >> 11);
hash = (hash + (hash << 15)) & _hashMask;
return hash;
}
@override
bool isValidKey(Object? o) => o is List<E>;
}
/// Equality on maps.
///
/// Two maps are equal if they have the same number of entries, and if the
/// entries of the two maps are pairwise equal on both key and value.
class MapEquality<K, V> implements IEquality<Map<K, V>> {
final IEquality<K> _keyEquality;
final IEquality<V> _valueEquality;
const MapEquality(
{IEquality<K> keys = const DefaultEquality<Never>(),
IEquality<V> values = const DefaultEquality<Never>()})
: _keyEquality = keys,
_valueEquality = values;
@override
bool equals(Map<K, V>? map1, Map<K, V>? map2) {
if (identical(map1, map2)) return true;
if (map1 == null || map2 == null) return false;
var length = map1.length;
if (length != map2.length) return false;
Map<_MapEntry, int> equalElementCounts = HashMap();
for (var key in map1.keys) {
var entry = _MapEntry(this, key, map1[key]);
var count = equalElementCounts[entry] ?? 0;
equalElementCounts[entry] = count + 1;
}
for (var key in map2.keys) {
var entry = _MapEntry(this, key, map2[key]);
var count = equalElementCounts[entry];
if (count == null || count == 0) return false;
equalElementCounts[entry] = count - 1;
}
return true;
}
@override
int hash(Map<K, V>? map) {
if (map == null) return null.hashCode;
var hash = 0;
for (var key in map.keys) {
var keyHash = _keyEquality.hash(key);
var valueHash = _valueEquality.hash(map[key] as V);
hash = (hash + 3 * keyHash + 7 * valueHash) & _hashMask;
}
hash = (hash + (hash << 3)) & _hashMask;
hash ^= (hash >> 11);
hash = (hash + (hash << 15)) & _hashMask;
return hash;
}
@override
bool isValidKey(Object? o) => o is Map<K, V>;
}
class _MapEntry {
final MapEquality equality;
final Object? key;
final Object? value;
_MapEntry(this.equality, this.key, this.value);
@override
int get hashCode =>
(3 * equality._keyEquality.hash(key) +
7 * equality._valueEquality.hash(value)) &
_hashMask;
@override
bool operator ==(Object other) =>
other is _MapEntry &&
equality._keyEquality.equals(key, other.key) &&
equality._valueEquality.equals(value, other.value);
}
/// Equality on iterables.
///
/// Two iterables are equal if they have the same elements in the same order.
class IterableEquality<E> implements IEquality<Iterable<E>> {
final IEquality<E?> _elementEquality;
const IterableEquality(
[IEquality<E> elementEquality = const DefaultEquality<Never>()])
: _elementEquality = elementEquality;
@override
bool equals(Iterable<E>? elements1, Iterable<E>? elements2) {
if (identical(elements1, elements2)) return true;
if (elements1 == null || elements2 == null) return false;
var it1 = elements1.iterator;
var it2 = elements2.iterator;
while (true) {
var hasNext = it1.moveNext();
if (hasNext != it2.moveNext()) return false;
if (!hasNext) return true;
if (!_elementEquality.equals(it1.current, it2.current)) return false;
}
}
@override
int hash(Iterable<E>? elements) {
if (elements == null) return null.hashCode;
// Jenkins's one-at-a-time hash function.
var hash = 0;
for (var element in elements) {
var c = _elementEquality.hash(element);
hash = (hash + c) & _hashMask;
hash = (hash + (hash << 10)) & _hashMask;
hash ^= (hash >> 6);
}
hash = (hash + (hash << 3)) & _hashMask;
hash ^= (hash >> 11);
hash = (hash + (hash << 15)) & _hashMask;
return hash;
}
@override
bool isValidKey(Object? o) => o is Iterable<E>;
}
/// Equality of sets.
///
/// Two sets are considered equal if they have the same number of elements,
/// and the elements of one set can be paired with the elements
/// of the other set, so that each pair are equal.
class SetEquality<E> extends _UnorderedEquality<E, Set<E>> {
const SetEquality(
[IEquality<E> elementEquality = const DefaultEquality<Never>()])
: super(elementEquality);
@override
bool isValidKey(Object? o) => o is Set<E>;
}
abstract class _UnorderedEquality<E, T extends Iterable<E>>
implements IEquality<T> {
final IEquality<E> _elementEquality;
const _UnorderedEquality(this._elementEquality);
@override
bool equals(T? elements1, T? elements2) {
if (identical(elements1, elements2)) return true;
if (elements1 == null || elements2 == null) return false;
var counts = HashMap<E, int>(
equals: _elementEquality.equals,
hashCode: _elementEquality.hash,
isValidKey: _elementEquality.isValidKey);
var length = 0;
for (var e in elements1) {
var count = counts[e] ?? 0;
counts[e] = count + 1;
length++;
}
for (var e in elements2) {
var count = counts[e];
if (count == null || count == 0) return false;
counts[e] = count - 1;
length--;
}
return length == 0;
}
@override
int hash(T? elements) {
if (elements == null) return null.hashCode;
var hash = 0;
for (E element in elements) {
var c = _elementEquality.hash(element);
hash = (hash + c) & _hashMask;
}
hash = (hash + (hash << 3)) & _hashMask;
hash ^= (hash >> 11);
hash = (hash + (hash << 15)) & _hashMask;
return hash;
}
}
/// Equality of the elements of two iterables without considering order.
///
/// Two iterables are considered equal if they have the same number of elements,
/// and the elements of one set can be paired with the elements
/// of the other iterable, so that each pair are equal.
class UnorderedIterableEquality<E> extends _UnorderedEquality<E, Iterable<E>> {
const UnorderedIterableEquality(
[IEquality<E> elementEquality = const DefaultEquality<Never>()])
: super(elementEquality);
@override
bool isValidKey(Object? o) => o is Iterable<E>;
}
... ...
... ... @@ -29,12 +29,7 @@ class Api implements Service {
}
void main() {
test('Get.putAsync test', () async {
await Get.putAsync<String>(Mock.test);
expect('test', Get.find<String>());
Get.reset();
});
TestWidgetsFlutterBinding.ensureInitialized();
test('Get.put test', () async {
final instance = Get.put<Controller>(Controller());
expect(instance, Get.find<Controller>());
... ...
... ... @@ -4,7 +4,7 @@ import 'package:get/get.dart';
void main() {
test('Parse Page with children', () {
// final testParams = {'hi': 'value'};
final testParams = {'hi': 'value'};
final pageTree = GetPage(
name: '/city',
page: () => Container(),
... ... @@ -40,7 +40,7 @@ void main() {
name: '/pen',
transition: Transition.cupertino,
page: () => Container(),
// parameters: testParams,
parameters: testParams,
),
GetPage(
name: '/paper',
... ... @@ -58,63 +58,20 @@ void main() {
),
],
);
final routes = <GetPage>[];
final tree = ParseRouteTree();
routes.addRoute(pageTree);
final tree = ParseRouteTree(routes: <GetPage>[]);
tree.addRoute(pageTree);
// tree.addRoute(pageTree);
final searchRoute = '/city/work/office/pen';
final match = tree.matchRoute(routes, searchRoute);
final match = tree.matchRoute(searchRoute);
expect(match, isNotNull);
expect(match.route!.name, searchRoute);
final testRouteParam = match.parameters;
print(testRouteParam);
// for (final tParam in testParams.entries) {
// expect(testRouteParam[tParam.key], tParam.value);
// }
});
test('Parse ', () {
final testParams = {'hi': 'value'};
final pageTree = GetPage(
name: '/city',
parameters: testParams,
page: () => Container(),
);
final routes = <GetPage>[];
final tree = ParseRouteTree();
routes.addRoute(pageTree);
// tree.addRoute(pageTree);
final searchRoute = '/city?abc=1234';
final hasMatch = RouteParser.hasMatch(
pushedRoute: searchRoute, routeName: pageTree.name);
expect(hasMatch, true);
final parsed =
RouteParser.parse(pushedRoute: searchRoute, routeName: pageTree.name);
final match = tree.matchRoute(routes, searchRoute);
expect(match, isNotNull);
expect(parsed.newRouteUri.toString(), searchRoute);
final testRouteParam = match.route?.parameters;
final testRouteParam = match.route!.parameters!;
for (final tParam in testParams.entries) {
expect(testRouteParam![tParam.key], tParam.value);
expect(testRouteParam[tParam.key], tParam.value);
}
final hasMatch2 = RouteParser.hasMatch(
pushedRoute: '/home/123/ana', routeName: '/home/:id/:name');
print(hasMatch2);
expect(hasMatch2, true);
final parsed2 = RouteParser.parse(
pushedRoute: '/home/123/ana/profile',
routeName: '/home/:id/:name/profile');
print(parsed2.parameters);
});
test('Parse Page without children', () {
... ... @@ -157,14 +114,14 @@ void main() {
transition: Transition.rightToLeft),
];
final tree = ParseRouteTree();
final tree = ParseRouteTree(routes: pageTree);
// for (var p in pageTree) {
// tree.addRoute(p);
// }
final searchRoute = '/city/work/office/pen';
final match = tree.matchRoute(pageTree, searchRoute);
final match = tree.matchRoute(searchRoute);
expect(match, isNotNull);
expect(match.route!.name, searchRoute);
});
... ... @@ -182,8 +139,6 @@ void main() {
],
));
print(Get.parameters);
expect(Get.parameters['name'], 'juan');
Get.toNamed('/second/1234');
... ...
... ... @@ -3,8 +3,8 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:get/get.dart';
void main() {
final controller = Get.put(Controller());
testWidgets("GetxController smoke test", (tester) async {
final controller = Get.put(Controller());
await tester.pumpWidget(
MaterialApp(
home: Column(
... ...