Jonatas

add supercontroller and improve upload time

@@ -83,7 +83,7 @@ @@ -83,7 +83,7 @@
83 - GetX is not a bloated. It has a multitude of features that allow you to start programming without worrying about anything, but each of these features are in separate containers, and are only started after use. If you only use State Management, only State Management will be compiled. If you only use routes, nothing from the state management will be compiled. 83 - GetX is not a bloated. It has a multitude of features that allow you to start programming without worrying about anything, but each of these features are in separate containers, and are only started after use. If you only use State Management, only State Management will be compiled. If you only use routes, nothing from the state management will be compiled.
84 84
85 - Getx has a huge ecosystem, a large community, a large number of collaborators, and will be maintained as long as the Flutter exists. Getx too is capable of running with the same code on Android, iOS, Web, Mac, Linux, Windows, and on your server. 85 - Getx has a huge ecosystem, a large community, a large number of collaborators, and will be maintained as long as the Flutter exists. Getx too is capable of running with the same code on Android, iOS, Web, Mac, Linux, Windows, and on your server.
86 -**It is possible to fully reuse your code made on the frontend on your backend with [Get Server](https://github.com/jonataslaw/get_server)**. 86 + **It is possible to fully reuse your code made on the frontend on your backend with [Get Server](https://github.com/jonataslaw/get_server)**.
87 87
88 **In addition, the entire development process can be completely automated, both on the server and on the front end with [Get CLI](https://github.com/jonataslaw/get_cli)**. 88 **In addition, the entire development process can be completely automated, both on the server and on the front end with [Get CLI](https://github.com/jonataslaw/get_cli)**.
89 89
@@ -341,6 +341,13 @@ Just append `.tr` to the specified key and it will be translated, using the curr @@ -341,6 +341,13 @@ Just append `.tr` to the specified key and it will be translated, using the curr
341 Text('title'.tr); 341 Text('title'.tr);
342 ``` 342 ```
343 343
  344 +#### Using translation with singular and plural
  345 +
  346 +```dart
  347 +var products = [];
  348 +Text('singularKey'.trPlural('pluralKey', products.length, Args));
  349 +```
  350 +
344 ### Locales 351 ### Locales
345 352
346 Pass parameters to `GetMaterialApp` to define the locale and translations. 353 Pass parameters to `GetMaterialApp` to define the locale and translations.
@@ -394,9 +401,11 @@ Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark()); @@ -394,9 +401,11 @@ Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark());
394 When `.darkmode` is activated, it will switch to the _light theme_, and when the _light theme_ becomes active, it will change to _dark theme_. 401 When `.darkmode` is activated, it will switch to the _light theme_, and when the _light theme_ becomes active, it will change to _dark theme_.
395 402
396 ## GetConnect 403 ## GetConnect
  404 +
397 GetConnect is an easy way to communicate from your back to your front with http or websockets 405 GetConnect is an easy way to communicate from your back to your front with http or websockets
398 406
399 ### Default configuration 407 ### Default configuration
  408 +
400 You can simply extend GetConnect and use the GET/POST/PUT/DELETE/SOCKET methods to communicate with your Rest API or websockets. 409 You can simply extend GetConnect and use the GET/POST/PUT/DELETE/SOCKET methods to communicate with your Rest API or websockets.
401 410
402 ```dart 411 ```dart
@@ -419,7 +428,9 @@ class UserProvider extends GetConnect { @@ -419,7 +428,9 @@ class UserProvider extends GetConnect {
419 } 428 }
420 } 429 }
421 ``` 430 ```
  431 +
422 ### Custom configuration 432 ### Custom configuration
  433 +
423 GetConnect is highly customizable You can define base Url, as answer modifiers, as Requests modifiers, define an authenticator, and even the number of attempts in which it will try to authenticate itself, in addition to giving the possibility to define a standard decoder that will transform all your requests into your Models without any additional configuration. 434 GetConnect is highly customizable You can define base Url, as answer modifiers, as Requests modifiers, define an authenticator, and even the number of attempts in which it will try to authenticate itself, in addition to giving the possibility to define a standard decoder that will transform all your requests into your Models without any additional configuration.
424 435
425 ```dart 436 ```dart
@@ -485,6 +496,7 @@ final middlewares = [ @@ -485,6 +496,7 @@ final middlewares = [
485 GetMiddleware(priority: -8), 496 GetMiddleware(priority: -8),
486 ]; 497 ];
487 ``` 498 ```
  499 +
488 those middlewares will be run in this order **-8 => 2 => 4 => 5** 500 those middlewares will be run in this order **-8 => 2 => 4 => 5**
489 501
490 ### Redirect 502 ### Redirect
@@ -917,10 +929,10 @@ You have two options to build it. @@ -917,10 +929,10 @@ You have two options to build it.
917 929
918 - with `builder` method you return the widget to build. 930 - with `builder` method you return the widget to build.
919 - with methods `desktop`, `tablet`,`phone`, `watch`. the specific 931 - with methods `desktop`, `tablet`,`phone`, `watch`. the specific
920 -method will be built when the screen type matches the method  
921 -when the screen is [ScreenType.Tablet] the `tablet` method  
922 -will be exuded and so on.  
923 -**Note:** If you use this method please set the property `alwaysUseBuilder` to `false` 932 + method will be built when the screen type matches the method
  933 + when the screen is [ScreenType.Tablet] the `tablet` method
  934 + will be exuded and so on.
  935 + **Note:** If you use this method please set the property `alwaysUseBuilder` to `false`
924 936
925 With `settings` property you can set the width limit for the screen types. 937 With `settings` property you can set the width limit for the screen types.
926 938
@@ -1098,4 +1110,3 @@ Any contribution is welcome! @@ -1098,4 +1110,3 @@ Any contribution is welcome!
1098 - [GetX Flutter Firebase Auth Example](https://medium.com/@jeffmcmorris/getx-flutter-firebase-auth-example-b383c1dd1de2) - Article by Jeff McMorris. 1110 - [GetX Flutter Firebase Auth Example](https://medium.com/@jeffmcmorris/getx-flutter-firebase-auth-example-b383c1dd1de2) - Article by Jeff McMorris.
1099 - [Flutter State Management with GetX – Complete App](https://www.appwithflutter.com/flutter-state-management-with-getx/) - by App With Flutter. 1111 - [Flutter State Management with GetX – Complete App](https://www.appwithflutter.com/flutter-state-management-with-getx/) - by App With Flutter.
1100 - [Flutter Routing with Animation using Get Package](https://www.appwithflutter.com/flutter-routing-using-get-package/) - by App With Flutter. 1112 - [Flutter Routing with Animation using Get Package](https://www.appwithflutter.com/flutter-routing-using-get-package/) - by App With Flutter.
1101 -  
@@ -139,6 +139,7 @@ class GetHttpClient { @@ -139,6 +139,7 @@ class GetHttpClient {
139 url: uri, 139 url: uri,
140 headers: headers, 140 headers: headers,
141 bodyBytes: bodyStream, 141 bodyBytes: bodyStream,
  142 + contentLength: bodyBytes.length,
142 followRedirects: followRedirects, 143 followRedirects: followRedirects,
143 maxRedirects: maxRedirects, 144 maxRedirects: maxRedirects,
144 decoder: decoder, 145 decoder: decoder,
@@ -32,15 +32,15 @@ class HttpRequestImpl extends HttpRequestBase { @@ -32,15 +32,15 @@ class HttpRequestImpl extends HttpRequestBase {
32 32
33 @override 33 @override
34 Future<Response<T>> send<T>(Request<T> request) async { 34 Future<Response<T>> send<T>(Request<T> request) async {
35 - var requestBody = await request.bodyBytes.toBytes();  
36 - var stream = BodyBytesStream.fromBytes(requestBody ?? const []); 35 + var stream = request.bodyBytes.asBroadcastStream();
  36 + //var stream = BodyBytesStream.fromBytes(requestBody ?? const []);
37 37
38 try { 38 try {
39 var ioRequest = (await _httpClient.openUrl(request.method, request.url)) 39 var ioRequest = (await _httpClient.openUrl(request.method, request.url))
40 ..followRedirects = request.followRedirects 40 ..followRedirects = request.followRedirects
41 ..persistentConnection = request.persistentConnection 41 ..persistentConnection = request.persistentConnection
42 ..maxRedirects = request.maxRedirects 42 ..maxRedirects = request.maxRedirects
43 - ..contentLength = requestBody.length ?? -1; 43 + ..contentLength = request.contentLength ?? -1;
44 request.headers.forEach(ioRequest.headers.set); 44 request.headers.forEach(ioRequest.headers.set);
45 45
46 var response = await stream.pipe(ioRequest) as io.HttpClientResponse; 46 var response = await stream.pipe(ioRequest) as io.HttpClientResponse;
@@ -20,6 +20,8 @@ class Request<T> { @@ -20,6 +20,8 @@ class Request<T> {
20 /// ex: `GET`,`POST`,`PUT`,`DELETE` 20 /// ex: `GET`,`POST`,`PUT`,`DELETE`
21 final String method; 21 final String method;
22 22
  23 + final int contentLength;
  24 +
23 /// The BodyBytesStream of body from this [Request] 25 /// The BodyBytesStream of body from this [Request]
24 final BodyBytesStream bodyBytes; 26 final BodyBytesStream bodyBytes;
25 27
@@ -38,6 +40,7 @@ class Request<T> { @@ -38,6 +40,7 @@ class Request<T> {
38 @required this.bodyBytes, 40 @required this.bodyBytes,
39 @required this.url, 41 @required this.url,
40 @required this.headers, 42 @required this.headers,
  43 + @required this.contentLength,
41 @required this.followRedirects, 44 @required this.followRedirects,
42 @required this.maxRedirects, 45 @required this.maxRedirects,
43 @required this.files, 46 @required this.files,
@@ -52,6 +55,7 @@ class Request<T> { @@ -52,6 +55,7 @@ class Request<T> {
52 BodyBytesStream bodyBytes, 55 BodyBytesStream bodyBytes,
53 bool followRedirects = true, 56 bool followRedirects = true,
54 int maxRedirects = 4, 57 int maxRedirects = 4,
  58 + int contentLength,
55 FormData files, 59 FormData files,
56 bool persistentConnection = true, 60 bool persistentConnection = true,
57 Decoder<T> decoder, 61 Decoder<T> decoder,
@@ -70,6 +74,7 @@ class Request<T> { @@ -70,6 +74,7 @@ class Request<T> {
70 headers: Map.from(headers ??= <String, String>{}), 74 headers: Map.from(headers ??= <String, String>{}),
71 followRedirects: followRedirects, 75 followRedirects: followRedirects,
72 maxRedirects: maxRedirects, 76 maxRedirects: maxRedirects,
  77 + contentLength: contentLength,
73 files: files, 78 files: files,
74 persistentConnection: persistentConnection, 79 persistentConnection: persistentConnection,
75 decoder: decoder, 80 decoder: decoder,
@@ -5,6 +5,7 @@ export 'src/extension_navigation.dart'; @@ -5,6 +5,7 @@ export 'src/extension_navigation.dart';
5 export 'src/root/get_cupertino_app.dart'; 5 export 'src/root/get_cupertino_app.dart';
6 export 'src/root/get_material_app.dart'; 6 export 'src/root/get_material_app.dart';
7 export 'src/root/internacionalization.dart'; 7 export 'src/root/internacionalization.dart';
  8 +export 'src/root/root_controller.dart';
8 export 'src/routes/custom_transition.dart'; 9 export 'src/routes/custom_transition.dart';
9 export 'src/routes/default_route.dart'; 10 export 'src/routes/default_route.dart';
10 export 'src/routes/get_route.dart'; 11 export 'src/routes/get_route.dart';
@@ -5,6 +5,7 @@ export 'src/rx_flutter/rx_getx_widget.dart'; @@ -5,6 +5,7 @@ export 'src/rx_flutter/rx_getx_widget.dart';
5 export 'src/rx_flutter/rx_notifier.dart'; 5 export 'src/rx_flutter/rx_notifier.dart';
6 export 'src/rx_flutter/rx_obx_widget.dart'; 6 export 'src/rx_flutter/rx_obx_widget.dart';
7 export 'src/rx_flutter/rx_ticket_provider_mixin.dart'; 7 export 'src/rx_flutter/rx_ticket_provider_mixin.dart';
  8 +export 'src/simple/get_controllers.dart';
8 export 'src/simple/get_responsive.dart'; 9 export 'src/simple/get_responsive.dart';
9 export 'src/simple/get_state.dart'; 10 export 'src/simple/get_state.dart';
10 export 'src/simple/get_view.dart'; 11 export 'src/simple/get_view.dart';
@@ -24,7 +24,6 @@ abstract class DisposableInterface extends GetLifeCycle { @@ -24,7 +24,6 @@ abstract class DisposableInterface extends GetLifeCycle {
24 /// navigation events, like snackbar, dialogs, or a new route, or 24 /// navigation events, like snackbar, dialogs, or a new route, or
25 /// async request. 25 /// async request.
26 @override 26 @override
27 - @mustCallSuper  
28 void onReady() { 27 void onReady() {
29 super.onReady(); 28 super.onReady();
30 } 29 }
  1 +// ignore: prefer_mixin
  2 +import 'package:flutter/widgets.dart';
  3 +
  4 +import '../rx_flutter/rx_disposable.dart';
  5 +import '../rx_flutter/rx_notifier.dart';
  6 +import 'list_notifier.dart';
  7 +
  8 +// ignore: prefer_mixin
  9 +abstract class GetxController extends DisposableInterface with ListNotifier {
  10 + /// Rebuilds [GetBuilder] each time you call [update()];
  11 + /// Can take a List of [ids], that will only update the matching
  12 + /// `GetBuilder( id: )`,
  13 + /// [ids] can be reused among `GetBuilders` like group tags.
  14 + /// The update will only notify the Widgets, if [condition] is true.
  15 + void update([List<String> ids, bool condition = true]) {
  16 + if (!condition) {
  17 + return;
  18 + }
  19 + if (ids == null) {
  20 + refresh();
  21 + } else {
  22 + for (final id in ids) {
  23 + refreshGroup(id);
  24 + }
  25 + }
  26 + }
  27 +}
  28 +
  29 +abstract class RxController extends DisposableInterface {}
  30 +
  31 +abstract class SuperController<T> extends FullLifeCycleController
  32 + with FullLifeCycle, StateMixin<T> {}
  33 +
  34 +abstract class FullLifeCycleController extends GetxController
  35 + with
  36 + // ignore: prefer_mixin
  37 + WidgetsBindingObserver {}
  38 +
  39 +mixin FullLifeCycle on FullLifeCycleController {
  40 + @mustCallSuper
  41 + @override
  42 + void onInit() {
  43 + super.onInit();
  44 + WidgetsBinding.instance.addObserver(this);
  45 + }
  46 +
  47 + @mustCallSuper
  48 + @override
  49 + void onClose() {
  50 + WidgetsBinding.instance.removeObserver(this);
  51 + super.onClose();
  52 + }
  53 +
  54 + @mustCallSuper
  55 + @override
  56 + void didChangeAppLifecycleState(AppLifecycleState state) {
  57 + switch (state) {
  58 + case AppLifecycleState.resumed:
  59 + onResumed();
  60 + break;
  61 + case AppLifecycleState.inactive:
  62 + onInactive();
  63 + break;
  64 + case AppLifecycleState.paused:
  65 + onPaused();
  66 + break;
  67 + case AppLifecycleState.detached:
  68 + onDetached();
  69 + break;
  70 + }
  71 + }
  72 +
  73 + void onResumed();
  74 + void onPaused();
  75 + void onInactive();
  76 + void onDetached();
  77 +}
1 import 'package:flutter/material.dart'; 1 import 'package:flutter/material.dart';
2 import '../../../get_instance/src/get_instance.dart'; 2 import '../../../get_instance/src/get_instance.dart';
  3 +import '../../../instance_manager.dart';
3 import '../../get_state_manager.dart'; 4 import '../../get_state_manager.dart';
4 import 'list_notifier.dart'; 5 import 'list_notifier.dart';
5 6
@@ -22,30 +23,26 @@ mixin GetStateUpdaterMixin<T extends StatefulWidget> on State<T> { @@ -22,30 +23,26 @@ mixin GetStateUpdaterMixin<T extends StatefulWidget> on State<T> {
22 } 23 }
23 } 24 }
24 25
25 -// ignore: prefer_mixin  
26 -class GetxController extends DisposableInterface with ListNotifier {  
27 - /// Rebuilds [GetBuilder] each time you call [update()];  
28 - /// Can take a List of [ids], that will only update the matching  
29 - /// `GetBuilder( id: )`,  
30 - /// [ids] can be reused among `GetBuilders` like group tags.  
31 - /// The update will only notify the Widgets, if [condition] is true.  
32 - void update([List<String> ids, bool condition = true]) {  
33 - if (!condition) {  
34 - return;  
35 - }  
36 - if (ids == null) {  
37 - refresh();  
38 - } else {  
39 - for (final id in ids) {  
40 - refreshGroup(id);  
41 - }  
42 - }  
43 - }  
44 -}  
45 -  
46 typedef GetControllerBuilder<T extends DisposableInterface> = Widget Function( 26 typedef GetControllerBuilder<T extends DisposableInterface> = Widget Function(
47 T controller); 27 T controller);
48 28
  29 +class _InheritedGetxController<T extends GetxController>
  30 + extends InheritedWidget {
  31 + final T model;
  32 + final int version;
  33 +
  34 + _InheritedGetxController({
  35 + Key key,
  36 + @required Widget child,
  37 + @required this.model,
  38 + }) : version = model.notifierVersion,
  39 + super(key: key, child: child);
  40 +
  41 + @override
  42 + bool updateShouldNotify(_InheritedGetxController<T> oldWidget) =>
  43 + (oldWidget.version != version);
  44 +}
  45 +
49 class GetBuilder<T extends GetxController> extends StatefulWidget { 46 class GetBuilder<T extends GetxController> extends StatefulWidget {
50 final GetControllerBuilder<T> builder; 47 final GetControllerBuilder<T> builder;
51 final bool global; 48 final bool global;
@@ -73,6 +70,25 @@ class GetBuilder<T extends GetxController> extends StatefulWidget { @@ -73,6 +70,25 @@ class GetBuilder<T extends GetxController> extends StatefulWidget {
73 }) : assert(builder != null), 70 }) : assert(builder != null),
74 super(key: key); 71 super(key: key);
75 72
  73 + static T of<T extends GetxController>(
  74 + BuildContext context, {
  75 + bool rebuild = false,
  76 + }) {
  77 + var widget = rebuild
  78 + ? context
  79 + .dependOnInheritedWidgetOfExactType<_InheritedGetxController<T>>()
  80 + : context
  81 + .getElementForInheritedWidgetOfExactType<
  82 + _InheritedGetxController<T>>()
  83 + ?.widget;
  84 +
  85 + if (widget == null) {
  86 + throw 'Error: Could not find the correct dependency.';
  87 + } else {
  88 + return (widget as _InheritedGetxController<T>).model;
  89 + }
  90 + }
  91 +
76 @override 92 @override
77 _GetBuilderState<T> createState() => _GetBuilderState<T>(); 93 _GetBuilderState<T> createState() => _GetBuilderState<T>();
78 } 94 }
@@ -156,5 +172,23 @@ class _GetBuilderState<T extends GetxController> extends State<GetBuilder<T>> @@ -156,5 +172,23 @@ class _GetBuilderState<T extends GetxController> extends State<GetBuilder<T>>
156 } 172 }
157 173
158 @override 174 @override
159 - Widget build(BuildContext context) => widget.builder(controller); 175 + Widget build(BuildContext context) {
  176 + return _InheritedGetxController<T>(
  177 + model: controller,
  178 + child: widget.builder(controller),
  179 + );
  180 + }
  181 +}
  182 +
  183 +extension FindExt on BuildContext {
  184 + T find<T extends GetxController>() {
  185 + return GetBuilder.of<T>(this, rebuild: false);
  186 + }
  187 +}
  188 +
  189 +extension ObserverEtx on BuildContext {
  190 + T obs<T extends GetxController>() {
  191 + return GetBuilder.of<T>(this, rebuild: true);
  192 + }
160 } 193 }
  194 +
@@ -72,7 +72,10 @@ class _GetCache<S extends GetLifeCycleBase> extends WidgetCache<GetWidget<S>> { @@ -72,7 +72,10 @@ class _GetCache<S extends GetLifeCycleBase> extends WidgetCache<GetWidget<S>> {
72 @override 72 @override
73 void onInit() { 73 void onInit() {
74 _isCreator = Get.isPrepared<S>(tag: widget.tag); 74 _isCreator = Get.isPrepared<S>(tag: widget.tag);
  75 + if (Get.isPrepared<S>()) {
75 _controller = Get.find<S>(tag: widget.tag); 76 _controller = Get.find<S>(tag: widget.tag);
  77 + }
  78 +
76 GetWidget._cache[widget] = _controller; 79 GetWidget._cache[widget] = _controller;
77 super.onInit(); 80 super.onInit();
78 } 81 }
@@ -21,7 +21,7 @@ class GetWidgetCacheElement extends ComponentElement { @@ -21,7 +21,7 @@ class GetWidgetCacheElement extends ComponentElement {
21 21
22 @override 22 @override
23 void mount(Element parent, dynamic newSlot) { 23 void mount(Element parent, dynamic newSlot) {
24 - cache.onInit(); 24 + cache?.onInit();
25 super.mount(parent, newSlot); 25 super.mount(parent, newSlot);
26 } 26 }
27 27
@@ -90,6 +90,11 @@ class ListNotifier implements Listenable { @@ -90,6 +90,11 @@ class ListNotifier implements Listenable {
90 return _updaters.isNotEmpty; 90 return _updaters.isNotEmpty;
91 } 91 }
92 92
  93 + int get listeners {
  94 + assert(_debugAssertNotDisposed());
  95 + return _updaters.length;
  96 + }
  97 +
93 @override 98 @override
94 void removeListener(VoidCallback listener) { 99 void removeListener(VoidCallback listener) {
95 assert(_debugAssertNotDisposed()); 100 assert(_debugAssertNotDisposed());
@@ -40,15 +40,19 @@ extension Trans on String { @@ -40,15 +40,19 @@ extension Trans on String {
40 } 40 }
41 } 41 }
42 42
43 - String trArgs([List<String> args]) { 43 + String trArgs([List<String> args = const []]) {
44 var key = tr; 44 var key = tr;
45 - if (args != null) { 45 + if (args.isNotEmpty) {
46 for (final arg in args) { 46 for (final arg in args) {
47 key = key.replaceFirst(RegExp(r'%s'), arg.toString()); 47 key = key.replaceFirst(RegExp(r'%s'), arg.toString());
48 } 48 }
49 } 49 }
50 return key; 50 return key;
51 } 51 }
  52 +
  53 + String trPlural([String pluralKey, int i, List<String> args = const []]) {
  54 + return i > 1 ? pluralKey.trArgs(args) : trArgs(args);
  55 + }
52 } 56 }
53 57
54 class _IntlHost { 58 class _IntlHost {
@@ -535,11 +535,7 @@ class GetUtils { @@ -535,11 +535,7 @@ class GetUtils {
535 /// Remove all whitespace inside string 535 /// Remove all whitespace inside string
536 /// Example: your name => yourname 536 /// Example: your name => yourname
537 static String removeAllWhitespace(String value) { 537 static String removeAllWhitespace(String value) {
538 - if (isNullOrBlank(value)) {  
539 - return null;  
540 - }  
541 -  
542 - return value.replaceAll(' ', ''); 538 + return value?.replaceAll(' ', '');
543 } 539 }
544 540
545 /// Camelcase string 541 /// Camelcase string
@@ -549,7 +545,8 @@ class GetUtils { @@ -549,7 +545,8 @@ class GetUtils {
549 return null; 545 return null;
550 } 546 }
551 547
552 - final separatedWords = value.split(' '); 548 + final separatedWords =
  549 + value.split(RegExp(r'[!@#<>?":`~;[\]\\|=+)(*&^%-\s_]+'));
553 var newString = ''; 550 var newString = '';
554 551
555 for (final word in separatedWords) { 552 for (final word in separatedWords) {
@@ -690,14 +690,17 @@ void main() { @@ -690,14 +690,17 @@ void main() {
690 }); 690 });
691 691
692 test('var.removeAllWhitespace', () { 692 test('var.removeAllWhitespace', () {
  693 + String nullString;
693 expect('foo bar'.removeAllWhitespace, 'foobar'); 694 expect('foo bar'.removeAllWhitespace, 'foobar');
694 expect('foo'.removeAllWhitespace, 'foo'); 695 expect('foo'.removeAllWhitespace, 'foo');
695 - expect(''.removeAllWhitespace, null); 696 + expect(''.removeAllWhitespace, '');
  697 + expect(nullString.removeAllWhitespace, null);
696 }); 698 });
697 699
698 test('var.camelCase', () { 700 test('var.camelCase', () {
699 expect('foo bar'.camelCase, 'fooBar'); 701 expect('foo bar'.camelCase, 'fooBar');
700 expect('the fox jumped in the water'.camelCase, 'theFoxJumpedInTheWater'); 702 expect('the fox jumped in the water'.camelCase, 'theFoxJumpedInTheWater');
  703 + expect('foo_bar'.camelCase, 'fooBar');
701 expect(''.camelCase, null); 704 expect(''.camelCase, null);
702 }); 705 });
703 706