From 3dd457ee0414a5cd03815328f50f73d64a966413 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Wed, 16 Aug 2023 11:18:03 -0700 Subject: [PATCH 01/53] Remove deprecated forwardRef It can't really be made null safe, and we always intended on removing it in the next major. --- lib/react.dart | 2 +- lib/react_client/react_interop.dart | 31 ------------------------ test/factory/common_factory_tests.dart | 21 ---------------- test/forward_ref_test.dart | 33 -------------------------- 4 files changed, 1 insertion(+), 86 deletions(-) diff --git a/lib/react.dart b/lib/react.dart index 2324e01c..c3a8f5c3 100644 --- a/lib/react.dart +++ b/lib/react.dart @@ -21,7 +21,7 @@ import 'package:react/src/react_client/private_utils.dart' show validateJsApiThe export 'package:react/src/context.dart'; export 'package:react/src/prop_validator.dart'; export 'package:react/src/react_client/event_helpers.dart'; -export 'package:react/react_client/react_interop.dart' show forwardRef, forwardRef2, createRef, memo, memo2; +export 'package:react/react_client/react_interop.dart' show forwardRef2, createRef, memo, memo2; export 'package:react/src/react_client/synthetic_event_wrappers.dart' hide NonNativeDataTransfer; export 'package:react/src/react_client/synthetic_data_transfer.dart' show SyntheticDataTransfer; export 'package:react/src/react_client/event_helpers.dart'; diff --git a/lib/react_client/react_interop.dart b/lib/react_client/react_interop.dart index d765bbaa..c93d46f4 100644 --- a/lib/react_client/react_interop.dart +++ b/lib/react_client/react_interop.dart @@ -18,9 +18,6 @@ import 'package:react/react_client/bridge.dart'; import 'package:react/react_client/js_backed_map.dart'; import 'package:react/react_client/component_factory.dart' show ReactDartWrappedComponentFactoryProxy, ReactDartFunctionComponentFactoryProxy, ReactJsComponentFactoryProxy; -import 'package:react/react_client/js_interop_helpers.dart'; -import 'package:react/react_client/zone.dart'; -import 'package:react/src/js_interop_util.dart'; import 'package:react/src/react_client/dart2_interop_workaround_bindings.dart'; typedef ReactJsComponentFactory = ReactElement Function(dynamic props, dynamic children); @@ -234,34 +231,6 @@ ReactComponentFactoryProxy forwardRef2( }) => ReactDartWrappedComponentFactoryProxy.forwardRef(wrapperFunction, displayName: displayName); -@Deprecated('Use forwardRef2') -ReactJsComponentFactoryProxy forwardRef( - Function(Map props, Ref ref) wrapperFunction, { - String displayName = 'Anonymous', -}) { - // ignore: invalid_use_of_visible_for_testing_member - final wrappedComponent = allowInterop((JsMap props, ref) => componentZone.run(() { - final dartProps = JsBackedMap.backedBy(props); - for (final value in dartProps.values) { - if (value is Function) { - // Tag functions that came straight from the JS - // so that we know to pass them through as-is during prop conversion. - isRawJsFunctionFromProps[value] = true; - } - } - - final dartRef = Ref.fromJs(ref as JsRef); - return wrapperFunction(dartProps, dartRef); - })); - defineProperty(wrappedComponent, 'displayName', JsPropertyDescriptor(value: displayName)); - - final hoc = React.forwardRef(wrappedComponent); - // ignore: invalid_use_of_protected_member - setProperty(hoc, 'dartComponentVersion', ReactDartComponentVersion.component2); - - return ReactJsComponentFactoryProxy(hoc, alwaysReturnChildrenAsList: true); -} - /// A [higher order component](https://reactjs.org/docs/higher-order-components.html) for function components /// that behaves similar to the way [`React.PureComponent`](https://reactjs.org/docs/react-api.html#reactpurecomponent) /// does for class-based components. diff --git a/test/factory/common_factory_tests.dart b/test/factory/common_factory_tests.dart index 17f00215..c8282980 100644 --- a/test/factory/common_factory_tests.dart +++ b/test/factory/common_factory_tests.dart @@ -409,27 +409,6 @@ void refTests( }); }); - group('forwardRef function passes a ref through a component to one of its children, when the ref is a:', () { - for (final name in testCaseCollection.allTestCaseNames) { - // Callback refs don't work properly with forwardRef. - // This is part of why forwardRef is deprecated. - if (!name.contains('callback ref')) { - test(name, () { - final testCase = testCaseCollection.createCaseByName(name); - final ForwardRefTestComponent = forwardRef((props, ref) { - return factory({'ref': ref}); - }); - - rtu.renderIntoDocument(ForwardRefTestComponent({ - 'ref': testCase.ref, - })); - final verifyFunction = testCase.isJs ? verifyJsRefValue : verifyRefValue; - verifyFunction(testCase.getCurrent()); - }); - } - } - }); - group('forwardRef2 function passes a ref through a component to one of its children, when the ref is a:', () { for (final name in testCaseCollection.allTestCaseNames) { test(name, () { diff --git a/test/forward_ref_test.dart b/test/forward_ref_test.dart index 011ed576..d8caf1fd 100644 --- a/test/forward_ref_test.dart +++ b/test/forward_ref_test.dart @@ -11,39 +11,6 @@ import 'package:test/test.dart'; import 'factory/common_factory_tests.dart'; main() { - group('forwardRef', () { - group('- common factory behavior -', () { - final ForwardRefTest = react.forwardRef((props, ref) { - props['onDartRender']?.call(props); - return react.div({...props, 'ref': ref}); - }); - - commonFactoryTests( - ForwardRefTest, - // ignore: invalid_use_of_protected_member - dartComponentVersion: ReactDartComponentVersion.component2, - // Dart props passed to forwardRef get converted when they shouldn't, so these tests fail. - // This is part of why forwardRef is deprecated. - skipPropValuesTest: true, - ); - }); - - // Ref behavior is tested functionally for all factory types in commonFactoryTests - - group('sets displayName on the rendered component as expected', () { - test('falling back to "Anonymous" when the displayName argument is not passed to forwardRef', () { - final ForwardRefTestComponent = forwardRef((props, ref) {}); - expect(getProperty(getProperty(ForwardRefTestComponent.type, 'render'), 'displayName'), 'Anonymous'); - }); - - test('when displayName argument is passed to forwardRef', () { - const name = 'ForwardRefTestComponent'; - final ForwardRefTestComponent = forwardRef((props, ref) {}, displayName: name); - expect(getProperty(getProperty(ForwardRefTestComponent.type, 'render'), 'displayName'), name); - }); - }); - }); - group('forwardRef2', () { group('- common factory behavior -', () { final ForwardRef2Test = react.forwardRef2((props, ref) { From cd2eda7f46d729d71bdc048c35a830a61ad025d4 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Wed, 16 Aug 2023 11:21:43 -0700 Subject: [PATCH 02/53] Remove workaround for dart-lang/sdk#27647, fixed in Dart 2 --- lib/react_client/component_factory.dart | 8 +- lib/src/ddc_emulated_function_name_bug.dart | 83 --------------------- 2 files changed, 1 insertion(+), 90 deletions(-) delete mode 100644 lib/src/ddc_emulated_function_name_bug.dart diff --git a/lib/react_client/component_factory.dart b/lib/react_client/component_factory.dart index 9f727e2e..a85f6f8f 100644 --- a/lib/react_client/component_factory.dart +++ b/lib/react_client/component_factory.dart @@ -10,7 +10,6 @@ import 'package:react/react_client.dart'; import 'package:react/react_client/js_backed_map.dart'; import 'package:react/react_client/react_interop.dart'; -import 'package:react/src/ddc_emulated_function_name_bug.dart' as ddc_emulated_function_name_bug; import 'package:react/src/js_interop_util.dart'; import 'package:react/src/typedefs.dart'; import 'package:react/src/react_client/factory_util.dart'; @@ -288,12 +287,7 @@ class ReactDomComponentFactoryProxy extends ReactComponentFactoryProxy { /// E.g. `'div'`, `'a'`, `'h1'` final String name; - ReactDomComponentFactoryProxy(this.name) { - // TODO: Should we remove this once we validate that the bug is gone in Dart 2 DDC? - if (ddc_emulated_function_name_bug.isBugPresent) { - ddc_emulated_function_name_bug.patchName(this); - } - } + ReactDomComponentFactoryProxy(this.name); @override String get type => name; diff --git a/lib/src/ddc_emulated_function_name_bug.dart b/lib/src/ddc_emulated_function_name_bug.dart deleted file mode 100644 index 4fb3b6f4..00000000 --- a/lib/src/ddc_emulated_function_name_bug.dart +++ /dev/null @@ -1,83 +0,0 @@ -/// Provides detection and patching of the bug described in , -/// in which getters/setters with the identifier `name` don't work for emulated function classes, like `UiProps`. -@JS() -library react.ddc_emulated_function_name_bug; - -import 'package:js/js.dart'; - -/// Create a reduced test case of the issue, using an emulated function pattern that is similar to `UiProps`. -/// -/// We can't use `UiProps` itself, since it uses [isBugPresent], and that would cause a cyclic initialization error. -class _NsmEmulatedFunctionWithNameProperty implements Function { - void call(); - - @override - noSuchMethod(i) {} - - String _name; - - // ignore: unnecessary_getters_setters - String get name => _name; - // ignore: unnecessary_getters_setters - set name(String value) => _name = value; -} - -/// Whether this bug, , is present in the current runtime. -/// -/// This performs functional detection of the bug, and will be `true` -/// only in the DDC and only in versions of the DDC where this bug is present. -final bool isBugPresent = (() { - const testValue = 'test value'; - - final testObject = _NsmEmulatedFunctionWithNameProperty(); - - try { - // In the DDC, this throws: - // TypeError: Cannot assign to read only property 'name' of function 'function call(...args) { - // return call.call.apply(call, args); - // }' - testObject.name = testValue; - } catch (_) { - return true; - } - - try { - // We don't expect accessing this to throw, but just in case... - return testObject.name != testValue; - } catch (_) { - return true; - } -})(); - -@JS() -@anonymous -class _PropertyDescriptor {} - -@JS('Object.getPrototypeOf') -external dynamic _getPrototypeOf(dynamic object); - -@JS('Object.getOwnPropertyDescriptor') -external _PropertyDescriptor _getOwnPropertyDescriptor(dynamic object, String propertyName); - -@JS('Object.defineProperty') -external void _defineProperty(dynamic object, String propertyName, _PropertyDescriptor descriptor); - -/// Patches the `name` property on the given [object] to have the expected behavior -/// by copying the property descriptor for `name` from the appropriate prototype. -/// -/// This is a noop if `name` is not a property on the given object. -/// -/// __This functionality is unstable, and should not be used when [isBugPresent] is `false`.__ -/// -/// This method also had undefined behavior on non-`UiProps` instances. -void patchName(dynamic object) { - var current = object; - while ((current = _getPrototypeOf(current)) != null) { - final nameDescriptor = _getOwnPropertyDescriptor(current, 'name'); - - if (nameDescriptor != null) { - _defineProperty(object, 'name', nameDescriptor); - return; - } - } -} From ae23239d2ff798baa5ff4a13a4682760d6ed1db5 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Wed, 16 Aug 2023 14:34:20 -0700 Subject: [PATCH 03/53] Migrate to null safety --- example/js_components/js_components.dart | 4 +- example/test/function_component_test.dart | 10 +- example/test/react_test_components.dart | 24 +- example/test/ref_test.dart | 12 +- example/test/unmount_test.dart | 4 +- lib/hooks.dart | 45 +- lib/react.dart | 106 ++-- lib/react_client/bridge.dart | 20 +- lib/react_client/component_factory.dart | 30 +- lib/react_client/js_backed_map.dart | 12 +- lib/react_client/js_interop_helpers.dart | 2 +- lib/react_client/react_interop.dart | 126 ++--- lib/react_test_utils.dart | 110 ++-- lib/src/context.dart | 18 +- lib/src/js_interop_util.dart | 6 +- lib/src/prop_validator.dart | 13 +- .../react_client/component_registration.dart | 8 +- .../react_client/dart_interop_statics.dart | 22 +- lib/src/react_client/event_helpers.dart | 487 +++++++++--------- lib/src/react_client/factory_util.dart | 4 +- lib/src/react_client/private_utils.dart | 13 +- .../react_client/synthetic_data_transfer.dart | 14 +- .../synthetic_event_wrappers.dart | 2 + lib/src/typedefs.dart | 6 +- pubspec.yaml | 4 +- test/factory/common_factory_tests.dart | 45 +- test/hooks_test.dart | 378 +++++++------- test/js_builds/shared_tests.dart | 9 +- test/lifecycle_test.dart | 150 +++--- test/lifecycle_test/component.dart | 2 +- test/lifecycle_test/component2.dart | 2 +- test/lifecycle_test/util.dart | 34 +- test/react_client/bridge_test.dart | 2 +- test/react_client/event_helpers_test.dart | 26 +- test/react_client/js_backed_map_test.dart | 2 +- .../react_client/js_interop_helpers_test.dart | 4 +- test/react_client/react_interop_test.dart | 2 +- test/react_client_test.dart | 9 +- test/react_component_test.dart | 12 +- test/react_context_test.dart | 118 ++--- test/react_memo_test.dart | 60 +-- test/react_test_utils_test.dart | 78 ++- test/util.dart | 13 +- 43 files changed, 997 insertions(+), 1051 deletions(-) diff --git a/example/js_components/js_components.dart b/example/js_components/js_components.dart index 08132513..f114220a 100644 --- a/example/js_components/js_components.dart +++ b/example/js_components/js_components.dart @@ -18,7 +18,7 @@ main() { var IndexComponent = react.registerComponent2(() => _IndexComponent()); class _IndexComponent extends react.Component2 { - SimpleCustomComponent simpleRef; + SimpleCustomComponent? simpleRef; @override get initialState => { @@ -35,7 +35,7 @@ class _IndexComponent extends react.Component2 { setState({ 'open': true, }); - print(simpleRef.getFoo()); + print(simpleRef!.getFoo()); } @override diff --git a/example/test/function_component_test.dart b/example/test/function_component_test.dart index 1435a583..aefeff0c 100644 --- a/example/test/function_component_test.dart +++ b/example/test/function_component_test.dart @@ -240,7 +240,7 @@ UseRefTestComponent(Map props) { return react.Fragment({}, [ react.p({'key': 'urtKey1'}, ['Current Input: ${inputValue.value}, Previous Input: ${prevInputValueRef.current}']), react.input({'key': 'urtKey2', 'ref': inputRef}), - react.button({'key': 'urtKey3', 'onClick': (_) => inputValue.set(inputRef.current.value)}, ['Update']), + react.button({'key': 'urtKey3', 'onClick': (_) => inputValue.set(inputRef.current!.value!)}, ['Update']), ]); } @@ -377,14 +377,14 @@ UseImperativeHandleTestComponent(Map props) { if (!RegExp(r'^[a-zA-Z]+$').hasMatch(city.value)) { message.set('Invalid form!'); error.set('city'); - cityRef.current.focus(); + cityRef.current!.focus(); return; } if (!RegExp(r'^[a-zA-Z]+$').hasMatch(state.value)) { message.set('Invalid form!'); error.set('state'); - stateRef.current.focus(); + stateRef.current!.focus(); return; } @@ -453,13 +453,13 @@ UseImperativeHandleTestComponent2(Map props) { }, []), react.button({ 'key': 'button1', - 'onClick': (_) => fancyCounterRef.current['increment'](), + 'onClick': (_) => fancyCounterRef.current!['increment'](), }, [ 'Increment by ${diff.value}' ]), react.button({ 'key': 'button2', - 'onClick': (_) => fancyCounterRef.current['decrement'](), + 'onClick': (_) => fancyCounterRef.current!['decrement'](), }, [ 'Decrement by ${diff.value}' ]), diff --git a/example/test/react_test_components.dart b/example/test/react_test_components.dart index 6a0db285..0942add4 100644 --- a/example/test/react_test_components.dart +++ b/example/test/react_test_components.dart @@ -87,7 +87,7 @@ class _CheckBoxComponent extends react.Component { var checkBoxComponent = react.registerComponent(() => _CheckBoxComponent()); class _ClockComponent extends react.Component { - Timer timer; + late Timer timer; @override getInitialState() => {'secondsElapsed': 0}; @@ -297,7 +297,7 @@ int calculateChangedBits(currentValue, nextValue) { var TestNewContext = react.createContext({'renderCount': 0}, calculateChangedBits); class _NewContextProviderComponent extends react.Component2 { - _NewContextRefComponent componentRef; + _NewContextRefComponent? componentRef; @override get initialState => {'renderCount': 0, 'complexMap': false}; @@ -448,7 +448,7 @@ class _Component2TestComponent extends react.Component2 with react.TypedSnapshot } @override - componentDidUpdate(prevProps, prevState, [String snapshot]) { + componentDidUpdate(prevProps, prevState, [String? snapshot]) { if (snapshot != null) { print('Updated DOM and $snapshot'); return; @@ -519,20 +519,20 @@ class _ErrorComponent extends react.Component2 { var ErrorComponent = react.registerComponent(() => _ErrorComponent()); class _CustomException implements Exception { - int code; - String message; - String randomMessage; + final int code; + final String message; + final String randomMessage; - _CustomException(this.message, this.code) { + _CustomException(this.message, this.code) : randomMessage = _getRandomMessage(code); + + static String _getRandomMessage(code) { switch (code) { case 1: - randomMessage = 'The code is a 1'; - break; + return 'The code is a 1'; case 2: - randomMessage = 'The Code is a 2'; - break; + return 'The Code is a 2'; default: - randomMessage = 'Default Error Code'; + return 'Default Error Code'; } } } diff --git a/example/test/ref_test.dart b/example/test/ref_test.dart index 248dd73f..6ab68934 100644 --- a/example/test/ref_test.dart +++ b/example/test/ref_test.dart @@ -85,19 +85,19 @@ class _ParentComponent extends react.Component { } // Callback refs - InputElement _inputCallbackRef; - _ChildComponent _childCallbackRef; + InputElement? _inputCallbackRef; + _ChildComponent? _childCallbackRef; showInputCallbackRefValue(_) { final input = react_dom.findDOMNode(_inputCallbackRef) as InputElement; print(input.value); } showChildCallbackRefValue(_) { - print(_childCallbackRef.somevalue); + print(_childCallbackRef!.somevalue); } incrementChildCallbackRefValue(_) { - _childCallbackRef.incrementValue(); + _childCallbackRef!.incrementValue(); } // Create refs @@ -110,11 +110,11 @@ class _ParentComponent extends react.Component { } showChildCreateRefValue(_) { - print(_childCreateRef.current.somevalue); + print(_childCreateRef.current!.somevalue); } incrementChildCreateRefValue(_) { - _childCreateRef.current.incrementValue(); + _childCreateRef.current!.incrementValue(); } @override diff --git a/example/test/unmount_test.dart b/example/test/unmount_test.dart index 2cb484a2..18b08db8 100644 --- a/example/test/unmount_test.dart +++ b/example/test/unmount_test.dart @@ -23,7 +23,7 @@ void main() { print('What'); final mountedNode = querySelector('#content'); - querySelector('#mount').onClick.listen((_) => react_dom.render(simpleComponent({}), mountedNode)); + querySelector('#mount')!.onClick.listen((_) => react_dom.render(simpleComponent({}), mountedNode)); - querySelector('#unmount').onClick.listen((_) => react_dom.unmountComponentAtNode(mountedNode)); + querySelector('#unmount')!.onClick.listen((_) => react_dom.unmountComponentAtNode(mountedNode)); } diff --git a/lib/hooks.dart b/lib/hooks.dart index c83348f5..8108fe3c 100644 --- a/lib/hooks.dart +++ b/lib/hooks.dart @@ -18,27 +18,27 @@ import 'package:react/react_client/react_interop.dart'; /// Learn more: . class StateHook { /// The first item of the pair returned by [React.useState]. - T _value; + final T _value; /// The second item in the pair returned by [React.useState]. - void Function(dynamic) _setValue; + final void Function(dynamic) _setValue; - StateHook(T initialValue) { + factory StateHook(T initialValue) { final result = React.useState(initialValue); - _value = result[0] as T; - _setValue = result[1] as void Function(dynamic); + return StateHook._(result[0] as T, result[1] as void Function(dynamic)); } /// Constructor for [useStateLazy], calls lazy version of [React.useState] to /// initialize [_value] to the return value of [init]. /// /// See: . - StateHook.lazy(T Function() init) { + factory StateHook.lazy(T Function() init) { final result = React.useState(allowInterop(init)); - _value = result[0] as T; - _setValue = result[1] as void Function(dynamic); + return StateHook._(result[0] as T, result[1] as void Function(dynamic)); } + StateHook._(this._value, this._setValue); + /// The current value of the state. /// /// See: . @@ -140,7 +140,7 @@ StateHook useStateLazy(T Function() init) => StateHook.lazy(init); /// ``` /// /// See: . -void useEffect(dynamic Function() sideEffect, [List dependencies]) { +void useEffect(dynamic Function() sideEffect, [List? dependencies]) { final wrappedSideEffect = allowInterop(() { final result = sideEffect(); if (result is Function) { @@ -166,28 +166,28 @@ void useEffect(dynamic Function() sideEffect, [List dependencies]) { /// Learn more: . class ReducerHook { /// The first item of the pair returned by [React.useReducer]. - TState _state; + final TState _state; /// The second item in the pair returned by [React.useReducer]. - void Function(TAction) _dispatch; + final void Function(TAction) _dispatch; - ReducerHook(TState Function(TState state, TAction action) reducer, TState initialState) { + factory ReducerHook(TState Function(TState state, TAction action) reducer, TState initialState) { final result = React.useReducer(allowInterop(reducer), initialState); - _state = result[0] as TState; - _dispatch = result[1] as void Function(TAction); + return ReducerHook._(result[0] as TState, result[1] as void Function(TAction)); } /// Constructor for [useReducerLazy], calls lazy version of [React.useReducer] to /// initialize [_state] to the return value of [init(initialArg)]. /// /// See: . - ReducerHook.lazy( + factory ReducerHook.lazy( TState Function(TState state, TAction action) reducer, TInit initialArg, TState Function(TInit) init) { final result = React.useReducer(allowInterop(reducer), initialArg, allowInterop(init)); - _state = result[0] as TState; - _dispatch = result[1] as void Function(TAction); + return ReducerHook._(result[0] as TState, result[1] as void Function(TAction)); } + ReducerHook._(this._state, this._dispatch); + /// The current state map of the component. /// /// See: . @@ -392,7 +392,8 @@ T useContext(Context context) => ContextHelpers.unjsifyNewContext(React.us /// ``` /// /// Learn more: . -Ref useRef([T initialValue]) => Ref.useRefInit(initialValue); +// FIXME should we make a non-nullable version of this? +Ref useRef([T? initialValue]) => Ref.useRefInit(initialValue); /// Returns a memoized version of the return value of [createFunction]. /// @@ -425,7 +426,7 @@ Ref useRef([T initialValue]) => Ref.useRefInit(initialValue); /// ``` /// /// Learn more: . -T useMemo(T Function() createFunction, [List dependencies]) => +T useMemo(T Function() createFunction, [List? dependencies]) => React.useMemo(allowInterop(createFunction), dependencies) as T; /// Runs [sideEffect] synchronously after a [DartFunctionComponent] renders, but before the screen is updated. @@ -461,7 +462,7 @@ T useMemo(T Function() createFunction, [List dependencies]) => /// ``` /// /// Learn more: . -void useLayoutEffect(dynamic Function() sideEffect, [List dependencies]) { +void useLayoutEffect(dynamic Function() sideEffect, [List? dependencies]) { final wrappedSideEffect = allowInterop(() { final result = sideEffect(); if (result is Function) { @@ -529,7 +530,7 @@ void useLayoutEffect(dynamic Function() sideEffect, [List dependencies]) /// ``` /// /// Learn more: . -void useImperativeHandle(dynamic ref, dynamic Function() createHandle, [List dependencies]) => +void useImperativeHandle(dynamic ref, dynamic Function() createHandle, [List? dependencies]) => // ref will be a JsRef in forwardRef2, or a Ref in forwardRef. (Or null if no ref is provided) // // For some reason the ref argument to React.forwardRef is usually a JsRef object no matter the input ref type, @@ -610,7 +611,7 @@ void useImperativeHandle(dynamic ref, dynamic Function() createHandle, [List. -dynamic useDebugValue(T value, [dynamic Function(T) format]) { +dynamic useDebugValue(T value, [dynamic Function(T)? format]) { if (format == null) { return React.useDebugValue(value); } diff --git a/lib/react.dart b/lib/react.dart index c3a8f5c3..13c51716 100644 --- a/lib/react.dart +++ b/lib/react.dart @@ -26,7 +26,7 @@ export 'package:react/src/react_client/synthetic_event_wrappers.dart' hide NonNa export 'package:react/src/react_client/synthetic_data_transfer.dart' show SyntheticDataTransfer; export 'package:react/src/react_client/event_helpers.dart'; -typedef PropValidator = Error Function(TProps props, PropValidatorInfo info); +typedef PropValidator = Error? Function(TProps props, PropValidatorInfo info); /// A React component declared using a function that takes in [props] and returns rendered output. /// @@ -52,11 +52,11 @@ typedef ComponentRegistrar = ReactComponentFactoryProxy Function(ComponentFactor typedef ComponentRegistrar2 = ReactDartComponentFactoryProxy2 Function( ComponentFactory componentFactory, { Iterable skipMethods, - Component2BridgeFactory bridgeFactory, + Component2BridgeFactory? bridgeFactory, }); typedef FunctionComponentRegistrar = ReactDartFunctionComponentFactoryProxy - Function(DartFunctionComponent componentFactory, {String displayName}); + Function(DartFunctionComponent componentFactory, {String? displayName}); /// Fragment component that allows the wrapping of children without the necessity of using /// an element that adds an additional layer to the DOM (div, span, etc). @@ -109,7 +109,7 @@ var StrictMode = ReactJsComponentFactoryProxy(React.StrictMode); /// __Deprecated. Use [Component2] instead.__ @Deprecated('7.0.0') abstract class Component { - Map _context; + Map? _context; /// A private field that backs [props], which is exposed via getter/setter so /// it can be overridden in strong mode. @@ -118,7 +118,7 @@ abstract class Component { /// [doesn't work for overriding fields](https://github.com/dart-lang/sdk/issues/27452). /// /// TODO: Switch back to a plain field once this issue is fixed. - Map _props; + late Map _props; /// A private field that backs [state], which is exposed via getter/setter so /// it can be overridden in strong mode. @@ -136,7 +136,7 @@ abstract class Component { /// [doesn't work for overriding fields](https://github.com/dart-lang/sdk/issues/27452). /// /// TODO: Switch back to a plain field once this issue is fixed. - RefMethod _ref; + late RefMethod _ref; /// The React context map of this component, passed down from its ancestors' [getChildContext] value. /// @@ -192,15 +192,13 @@ abstract class Component { @Deprecated('7.0.0') set ref(RefMethod value) => _ref = value; - dynamic _jsRedraw; + late Function _jsRedraw; - dynamic _jsThis; + late Object _jsThis; - // ignore: prefer_final_fields - List _setStateCallbacks = []; + final List _setStateCallbacks = []; - // ignore: prefer_final_fields - List _transactionalSetStateCallbacks = []; + final List _transactionalSetStateCallbacks = []; /// The List of callbacks to be called after the component has been updated from a call to [setState]. List get setStateCallbacks => _setStateCallbacks; @@ -214,11 +212,11 @@ abstract class Component { /// Allows the [ReactJS `displayName` property](https://reactjs.org/docs/react-component.html#displayname) /// to be set for debugging purposes. - String get displayName => runtimeType.toString(); + String? get displayName => runtimeType.toString(); - initComponentInternal(props, _jsRedraw, [RefMethod ref, _jsThis, context]) { + initComponentInternal(props, _jsRedraw, [RefMethod? ref, _jsThis, context]) { this._jsRedraw = _jsRedraw; - this.ref = ref; + this.ref = ref ?? (_) {}; this._jsThis = _jsThis; _initContext(context); _initProps(props); @@ -229,12 +227,12 @@ abstract class Component { /// [context]s typing was loosened from Map to dynamic to support the new context API in [Component2] /// which extends from [Component]. Only "legacy" context APIs are supported in [Component] - which means /// it will still be expected to be a Map. - this.context = Map.from(context as Map ?? const {}); + this.context = Map.from(context as Map? ?? const {}); /// [nextContext]s typing was loosened from Map to dynamic to support the new context API in [Component2] /// which extends from [Component]. Only "legacy" context APIs are supported in [Component] - which means /// it will still be expected to be a Map. - nextContext = Map.from(this.context as Map ?? const {}); + nextContext = Map.from(this.context as Map? ?? const {}); } _initProps(props) { @@ -260,12 +258,12 @@ abstract class Component { /// > /// > This will be completely removed when the JS side of it is slated for removal (ReactJS 18 / react.dart 7.0.0) @Deprecated('7.0.0') - Map nextContext; + Map? nextContext; /// Private reference to the value of [state] for the upcoming render cycle. /// /// Useful for ReactJS lifecycle methods [shouldComponentUpdate], [componentWillUpdate] and [componentDidUpdate]. - Map _nextState; + Map? _nextState; /// Reference to the value of [context] from the previous render cycle, used internally for proxying /// the ReactJS lifecycle method. @@ -279,7 +277,7 @@ abstract class Component { /// > /// > This will be completely removed when the JS side of it is slated for removal (ReactJS 18 / react.dart 7.0.0) @Deprecated('7.0.0') - Map prevContext; + Map? prevContext; /// Reference to the value of [state] from the previous render cycle, used internally for proxying /// the ReactJS lifecycle method and [componentDidUpdate]. @@ -287,7 +285,7 @@ abstract class Component { /// Not available after [componentDidUpdate] is called. /// /// __DO NOT set__ from anywhere outside react-dart lifecycle internals. - Map prevState; + Map? prevState; /// Public getter for [_nextState]. /// @@ -300,7 +298,7 @@ abstract class Component { /// [componentWillUpdate] as well as the context-specific variants. /// /// __DO NOT set__ from anywhere outside react-dart lifecycle internals. - Map nextProps; + Map? nextProps; /// Transfers `Component` [_nextState] to [state], and [state] to [prevState]. /// @@ -313,7 +311,7 @@ abstract class Component { void transferComponentState() { prevState = state; if (_nextState != null) { - state = _nextState; + state = _nextState!; } _nextState = Map.from(state); } @@ -321,7 +319,7 @@ abstract class Component { /// Force a call to [render] by calling [setState], which effectively "redraws" the `Component`. /// /// Optionally accepts a [callback] that gets called after the component updates. - void redraw([Function() callback]) { + void redraw([Function()? callback]) { setState({}, callback); } @@ -332,9 +330,9 @@ abstract class Component { /// Also allows [newState] to be used as a transactional `setState` callback. /// /// See: - void setState(covariant dynamic newState, [Function() callback]) { + void setState(covariant dynamic newState, [Function()? callback]) { if (newState is Map) { - _nextState.addAll(newState); + _nextState!.addAll(newState); } else if (newState is StateUpdaterCallback) { _transactionalSetStateCallbacks.add(newState); } else if (newState != null) { @@ -357,7 +355,7 @@ abstract class Component { /// > /// > Use [setState] instead. @Deprecated('7.0.0') - void replaceState(Map newState, [Function() callback]) { + void replaceState(Map? newState, [Function()? callback]) { final nextState = newState == null ? {} : Map.from(newState); _nextState = nextState; if (callback != null) _setStateCallbacks.add(callback); @@ -425,7 +423,7 @@ abstract class Component { /// > This will be completely removed when the JS side of it is slated for removal (ReactJS 18 / react.dart 7.0.0) @Deprecated('7.0.0') // ignore: avoid_returning_null - bool shouldComponentUpdateWithContext(Map nextProps, Map nextState, Map nextContext) => null; + bool? shouldComponentUpdateWithContext(Map nextProps, Map nextState, Map nextContext) => null; /// ReactJS lifecycle method that is invoked immediately before rendering when [nextProps] or [nextState] are being /// received. @@ -457,7 +455,7 @@ abstract class Component { /// > /// > This will be completely removed when the JS side of it is slated for removal (ReactJS 18 / react.dart 7.0.0) @Deprecated('7.0.0') - void componentWillUpdateWithContext(Map nextProps, Map nextState, Map nextContext) {} + void componentWillUpdateWithContext(Map nextProps, Map nextState, Map? nextContext) {} /// ReactJS lifecycle method that is invoked immediately after the `Component`'s updates are flushed to the DOM. /// @@ -587,7 +585,7 @@ abstract class Component2 implements Component { /// } /// /// See: - Context get contextType => null; + Context? get contextType => null; /// Invoked once and cached when [registerComponent] is called. Values in the mapping will be set on [props] /// if that prop is not specified by the parent component. @@ -656,10 +654,10 @@ abstract class Component2 implements Component { dynamic context; @override - Map props; + late Map props; @override - Map state; + late Map state; @override @Deprecated('7.0.0') @@ -671,7 +669,7 @@ abstract class Component2 implements Component { /// The JavaScript [`ReactComponent`](https://reactjs.org/docs/react-api.html#reactdom.render) /// instance of this `Component` returned by [render]. @override - ReactComponent jsThis; + late ReactComponent jsThis; /// Allows the [ReactJS `displayName` property](https://reactjs.org/docs/react-component.html#displayname) /// to be set for debugging purposes. @@ -682,8 +680,8 @@ abstract class Component2 implements Component { /// This will result in the dart2js name being `ReactDartComponent2` (the /// name of the proxying JS component defined in _dart_helpers.js). @override - String get displayName { - String value; + String? get displayName { + String? value; assert(() { value = runtimeType.toString(); return true; @@ -701,7 +699,7 @@ abstract class Component2 implements Component { /// /// See: @override - void setState(Map newState, [SetStateCallback callback]) { + void setState(Map? newState, [SetStateCallback? callback]) { _bridge.setState(this, newState, callback); } @@ -711,14 +709,14 @@ abstract class Component2 implements Component { /// Optionally accepts a [callback] that gets called after the component updates. /// /// See: - void setStateWithUpdater(StateUpdaterCallback updater, [SetStateCallback callback]) { + void setStateWithUpdater(StateUpdaterCallback updater, [SetStateCallback? callback]) { _bridge.setStateWithUpdater(this, updater, callback); } /// Causes [render] to be called, skipping [shouldComponentUpdate]. /// /// > See: - void forceUpdate([SetStateCallback callback]) { + void forceUpdate([SetStateCallback? callback]) { _bridge.forceUpdate(this, callback); } @@ -770,7 +768,7 @@ abstract class Component2 implements Component { /// > [Consider recommended alternative solutions first!](https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#preferred-solutions) /// /// See: - Map getDerivedStateFromProps(Map nextProps, Map prevState) => null; + Map? getDerivedStateFromProps(Map nextProps, Map prevState) => null; /// ReactJS lifecycle method that is invoked before rendering when [nextProps] and/or [nextState] are being received. /// @@ -877,7 +875,7 @@ abstract class Component2 implements Component { /// [getDerivedStateFromError] will be ignored. /// /// See: - Map getDerivedStateFromError(dynamic error) => null; + Map? getDerivedStateFromError(dynamic error) => null; /// Allows usage of PropValidator functions to check the validity of a prop within the props passed to it. /// @@ -960,7 +958,7 @@ abstract class Component2 implements Component { /// See: @override @Deprecated('7.0.0') - void redraw([SetStateCallback callback]) { + void redraw([SetStateCallback? callback]) { setState({}, callback); } @@ -1169,7 +1167,7 @@ abstract class Component2 implements Component { /// Will be removed when [Component] is removed in the `7.0.0` release. @override @Deprecated('7.0.0') - void replaceState(Map newState, [SetStateCallback callback]) => throw _unsupportedError('replaceState'); + void replaceState(Map? newState, [SetStateCallback? callback]) => throw _unsupportedError('replaceState'); /// Do not use. /// @@ -1190,7 +1188,7 @@ abstract class Component2 implements Component { /// Will be removed when [Component] is removed in the `7.0.0` release. @override @Deprecated('7.0.0') - initComponentInternal(props, _jsRedraw, [RefMethod ref, _jsThis, context]) => + initComponentInternal(props, _jsRedraw, [RefMethod? ref, _jsThis, context]) => throw _unsupportedError('initComponentInternal'); /// Do not use. @@ -1279,35 +1277,35 @@ abstract class Component2 implements Component { @override @Deprecated('7.0.0') - Map _context; + Map? _context; @override @Deprecated('7.0.0') - var _jsRedraw; + late var _jsRedraw; @override @Deprecated('7.0.0') - Map _nextState; + Map? _nextState; @override @Deprecated('7.0.0') - Map _props; + late Map _props; @override @Deprecated('7.0.0') - RefMethod _ref; + late RefMethod _ref; @override @Deprecated('7.0.0') - List _setStateCallbacks; + late List _setStateCallbacks; @override @Deprecated('7.0.0') - Map _state; + late Map _state; @override @Deprecated('7.0.0') - List _transactionalSetStateCallbacks; + late List _transactionalSetStateCallbacks; @override @Deprecated('7.0.0') @@ -1367,12 +1365,12 @@ abstract class ReactComponentFactoryProxy implements Function { /// /// Necessary to work around DDC `dart.dcall` issues in , /// since invoking the function directly doesn't work. - dynamic /*ReactElement*/ build(Map props, [List childrenArgs]); + ReactElement build(Map props, [List childrenArgs]); /// Returns a new rendered component instance with the specified [props] and `children` ([c1], [c2], et. al.). /// /// > The additional children arguments (c2, c3, et. al.) are a workaround for . - dynamic /*ReactElement*/ call(Map props, + ReactElement call(Map props, [c1 = _notSpecified, c2 = _notSpecified, c3 = _notSpecified, @@ -1701,7 +1699,7 @@ dynamic li = validateJsApiThenReturn(() => ReactDomComponentFactoryProxy('li')); dynamic link = validateJsApiThenReturn(() => ReactDomComponentFactoryProxy('link')); /// The HTML `
` `Element`. -dynamic main = validateJsApiThenReturn(() => ReactDomComponentFactoryProxy('main')); +dynamic htmlMain = validateJsApiThenReturn(() => ReactDomComponentFactoryProxy('main')); /// The HTML `` `MapElement`. dynamic map = validateJsApiThenReturn(() => ReactDomComponentFactoryProxy('map')); diff --git a/lib/react_client/bridge.dart b/lib/react_client/bridge.dart index d7abc3b1..62a61337 100644 --- a/lib/react_client/bridge.dart +++ b/lib/react_client/bridge.dart @@ -42,11 +42,11 @@ abstract class Component2Bridge { /// `ReactDartComponentFactoryProxy2`, and not when manually instantiated. /// /// __For internal/advanced use only.__ - static Component2Bridge forComponent(Component2 component) => bridgeForComponent[component]; + static Component2Bridge forComponent(Component2 component) => bridgeForComponent[component]!; - void setState(Component2 component, Map newState, SetStateCallback callback); - void setStateWithUpdater(Component2 component, StateUpdaterCallback stateUpdater, SetStateCallback callback); - void forceUpdate(Component2 component, SetStateCallback callback); + void setState(Component2 component, Map? newState, SetStateCallback? callback); + void setStateWithUpdater(Component2 component, StateUpdaterCallback stateUpdater, SetStateCallback? callback); + void forceUpdate(Component2 component, SetStateCallback? callback); JsMap jsifyPropTypes( covariant Component2 component, covariant Map*/ Function> propTypes); } @@ -66,7 +66,7 @@ class Component2BridgeImpl extends Component2Bridge { static Component2BridgeImpl bridgeFactory(Component2 _) => const Component2BridgeImpl(); @override - void forceUpdate(Component2 component, SetStateCallback callback) { + void forceUpdate(Component2 component, SetStateCallback? callback) { if (callback == null) { component.jsThis.forceUpdate(); } else { @@ -75,7 +75,7 @@ class Component2BridgeImpl extends Component2Bridge { } @override - void setState(Component2 component, Map newState, SetStateCallback callback) { + void setState(Component2 component, Map? newState, SetStateCallback? callback) { // Short-circuit to match the ReactJS 16 behavior of not re-rendering the component if newState is null. if (newState == null) return; @@ -91,7 +91,7 @@ class Component2BridgeImpl extends Component2Bridge { } @override - void setStateWithUpdater(Component2 component, StateUpdaterCallback stateUpdater, SetStateCallback callback) { + void setStateWithUpdater(Component2 component, StateUpdaterCallback stateUpdater, SetStateCallback? callback) { final firstArg = allowInterop((JsMap jsPrevState, JsMap jsProps, [_]) { final value = stateUpdater( JsBackedMap.backedBy(jsPrevState), @@ -118,11 +118,11 @@ class Component2BridgeImpl extends Component2Bridge { dynamic handlePropValidator( JsMap props, String propName, - String componentName, + String? componentName, String location, - String propFullName, + String? propFullName, // This is a required argument of PropTypes but is usually hidden from the JS consumer. - String secret, + String? secret, ) { // Create a Dart consumable version of the JsMap. final convertedProps = JsBackedMap.fromJs(props); diff --git a/lib/react_client/component_factory.dart b/lib/react_client/component_factory.dart index a85f6f8f..63fa6651 100644 --- a/lib/react_client/component_factory.dart +++ b/lib/react_client/component_factory.dart @@ -11,7 +11,6 @@ import 'package:react/react_client/js_backed_map.dart'; import 'package:react/react_client/react_interop.dart'; import 'package:react/src/js_interop_util.dart'; -import 'package:react/src/typedefs.dart'; import 'package:react/src/react_client/factory_util.dart'; // ignore: deprecated_member_use_from_same_package @@ -86,7 +85,7 @@ class ReactDartComponentFactoryProxy extends React /// into [generateExtendedJsProps] upon [ReactElement] creation. final Map defaultProps; - ReactDartComponentFactoryProxy(this.reactClass) : defaultProps = reactClass.dartDefaultProps; + ReactDartComponentFactoryProxy(this.reactClass) : defaultProps = reactClass.dartDefaultProps!; @override ReactClass get type => reactClass; @@ -101,7 +100,7 @@ class ReactDartComponentFactoryProxy extends React /// Returns a JavaScript version of the specified [props], preprocessed for consumption by ReactJS and prepared for /// consumption by the `react` library internals. - static InteropProps generateExtendedJsProps(Map props, dynamic children, {Map defaultProps}) { + static InteropProps generateExtendedJsProps(Map props, dynamic children, {Map? defaultProps}) { if (children == null) { children = []; } else if (children is! Iterable) { @@ -179,7 +178,7 @@ class ReactDartComponentFactoryProxy2 extends Rea @override final Map defaultProps; - ReactDartComponentFactoryProxy2(this.reactClass) : defaultProps = JsBackedMap.fromJs(reactClass.defaultProps); + ReactDartComponentFactoryProxy2(this.reactClass) : defaultProps = JsBackedMap.fromJs(reactClass.defaultProps!); @override ReactClass get type => reactClass; @@ -210,12 +209,12 @@ class ReactJsContextComponentFactoryProxy extends ReactJsComponentFactoryProxy { super(jsClass, shouldConvertDomProps: shouldConvertDomProps); @override - ReactElement build(Map props, [List childrenArgs]) { + ReactElement build(Map props, [List childrenArgs = const []]) { dynamic children = generateChildren(childrenArgs); if (isConsumer) { if (children is Function) { - final contextCallback = children as Function; + final contextCallback = children; children = allowInterop((args) { return contextCallback(ContextHelpers.unjsifyNewContext(args)); }); @@ -265,14 +264,11 @@ class ReactJsComponentFactoryProxy extends ReactComponentFactoryProxy { List additionalRefPropKeys = const [], }) : type = jsClass, _additionalRefPropKeys = additionalRefPropKeys { - if (jsClass == null) { - throw ArgumentError('`jsClass` must not be null. ' - 'Ensure that the JS component class you\'re referencing is available and being accessed correctly.'); - } + ArgumentError.checkNotNull(jsClass, 'jsClass'); } @override - ReactElement build(Map props, [List childrenArgs]) { + ReactElement build(Map props, [List childrenArgs = const []]) { final children = generateChildren(childrenArgs, shouldAlwaysBeList: alwaysReturnChildrenAsList); final convertedProps = generateJsProps(props, convertCallbackRefValue: false, additionalRefPropKeys: _additionalRefPropKeys); @@ -308,12 +304,12 @@ class ReactDomComponentFactoryProxy extends ReactComponentFactoryProxy { /// Creates ReactJS [Function Component] from Dart Function. class ReactDartFunctionComponentFactoryProxy extends ReactComponentFactoryProxy with JsBackedMapComponentFactoryMixin { /// The name of this function. - final String displayName; + final String? displayName; /// The React JS component definition of this Function Component. final JsFunctionComponent reactFunction; - ReactDartFunctionComponentFactoryProxy(DartFunctionComponent dartFunctionComponent, {String displayName}) + ReactDartFunctionComponentFactoryProxy(DartFunctionComponent dartFunctionComponent, {String? displayName}) : displayName = displayName ?? getJsFunctionName(dartFunctionComponent), reactFunction = _wrapFunctionComponent(dartFunctionComponent, displayName: displayName ?? getJsFunctionName(dartFunctionComponent)); @@ -331,7 +327,7 @@ class ReactDartWrappedComponentFactoryProxy extends ReactComponentFactoryProxy w ReactDartWrappedComponentFactoryProxy(this.type); ReactDartWrappedComponentFactoryProxy.forwardRef(DartForwardRefFunctionComponent dartFunctionComponent, - {String displayName}) + {String? displayName}) : type = _wrapForwardRefFunctionComponent(dartFunctionComponent, displayName: displayName ?? getJsFunctionName(dartFunctionComponent)); } @@ -342,13 +338,13 @@ class ReactDartWrappedComponentFactoryProxy extends ReactComponentFactoryProxy w /// /// In DDC, this will be the [DartFunctionComponent] name, but in dart2js it will be null unless /// overridden, since using runtimeType can lead to larger dart2js output. -JsFunctionComponent _wrapFunctionComponent(DartFunctionComponent dartFunctionComponent, {String displayName}) { +JsFunctionComponent _wrapFunctionComponent(DartFunctionComponent dartFunctionComponent, {String? displayName}) { // dart2js uses null and undefined interchangeably, meaning returning `null` from dart // may show up in js as `undefined`, ReactJS doesnt like that and expects a js `null` to be returned, // and throws if it gets `undefined`. `jsNull` is an interop variable that holds a JS `null` value // to force `null` as the return value if user returns a Dart `null`. // See: https://github.com/dart-lang/sdk/issues/27485 - jsFunctionComponent(JsMap jsProps, [JsMap _legacyContext]) => + jsFunctionComponent(JsMap jsProps, [JsMap? _legacyContext]) => // ignore: invalid_use_of_visible_for_testing_member componentZone.run(() => dartFunctionComponent(JsBackedMap.backedBy(jsProps)) ?? jsNull); // ignore: omit_local_variable_types @@ -369,7 +365,7 @@ JsFunctionComponent _wrapFunctionComponent(DartFunctionComponent dartFunctionCom /// In DDC, this will be the [DartFunctionComponent] name, but in dart2js it will be null unless /// overridden, since using runtimeType can lead to larger dart2js output. ReactClass _wrapForwardRefFunctionComponent(DartForwardRefFunctionComponent dartFunctionComponent, - {String displayName}) { + {String? displayName}) { // dart2js uses null and undefined interchangeably, meaning returning `null` from dart // may show up in js as `undefined`, ReactJS doesnt like that and expects a js `null` to be returned, // and throws if it gets `undefined`. `jsNull` is an interop variable that holds a JS `null` value diff --git a/lib/react_client/js_backed_map.dart b/lib/react_client/js_backed_map.dart index 717af30d..58e58f4c 100644 --- a/lib/react_client/js_backed_map.dart +++ b/lib/react_client/js_backed_map.dart @@ -58,8 +58,9 @@ class JsBackedMap extends MapBase { // ---------------------------------- @override - dynamic operator [](Object key) { - return DartValueWrapper.unwrapIfNeeded(js_util.getProperty(jsObject, key)); + dynamic? operator [](Object? key) { + // Cast key as dynamic to work around https://github.com/dart-lang/sdk/issues/45219 + return DartValueWrapper.unwrapIfNeeded(js_util.getProperty(jsObject, key as dynamic)); } @override @@ -71,7 +72,7 @@ class JsBackedMap extends MapBase { Iterable get keys => _keys; @override - dynamic remove(Object key) { + dynamic? remove(Object? key) { final value = this[key]; _Reflect.deleteProperty(jsObject, key); return value; @@ -99,14 +100,15 @@ class JsBackedMap extends MapBase { } @override - bool containsKey(Object key) => js_util.hasProperty(jsObject, key); + // Cast key as dynamic to work around https://github.com/dart-lang/sdk/issues/45219 + bool containsKey(Object? key) => js_util.hasProperty(jsObject, key as dynamic); @override Iterable get values => _values; // todo figure out if this is faster than default implementation @override - bool containsValue(Object value) => _values.contains(value); + bool containsValue(Object? value) => _values.contains(value); @override bool operator ==(other) => other is JsBackedMap && other.jsObject == jsObject; diff --git a/lib/react_client/js_interop_helpers.dart b/lib/react_client/js_interop_helpers.dart index 37a9d527..165e944a 100644 --- a/lib/react_client/js_interop_helpers.dart +++ b/lib/react_client/js_interop_helpers.dart @@ -99,7 +99,7 @@ _convertDataTree(data) { return _convertedObjects[o]; } if (o is Map) { - final convertedMap = newObject(); + final convertedMap = newObject() as Object; _convertedObjects[o] = convertedMap; for (final key in o.keys) { setProperty(convertedMap, key, _convert(o[key])); diff --git a/lib/react_client/react_interop.dart b/lib/react_client/react_interop.dart index c93d46f4..eebd1dc2 100644 --- a/lib/react_client/react_interop.dart +++ b/lib/react_client/react_interop.dart @@ -31,21 +31,21 @@ typedef JsPropValidator = dynamic Function( @JS() abstract class React { external static String get version; - external static ReactElement cloneElement(ReactElement element, [JsMap props, dynamic children]); + external static ReactElement cloneElement(ReactElement element, [JsMap? props, dynamic? children]); external static ReactContext createContext([ dynamic defaultValue, - int Function(dynamic currentValue, dynamic nextValue) calculateChangedBits, + int Function(dynamic currentValue, dynamic nextValue)? calculateChangedBits, ]); @Deprecated('7.0.0') external static ReactClass createClass(ReactClassConfig reactClassConfig); @Deprecated('7.0.0') external static ReactJsComponentFactory createFactory(type); - external static ReactElement createElement(dynamic type, props, [dynamic children]); + external static ReactElement createElement(dynamic type, props, [dynamic? children]); external static JsRef createRef(); external static ReactClass forwardRef(Function(JsMap props, dynamic ref) wrapperFunction); external static ReactClass memo( dynamic wrapperFunction, [ - bool Function(JsMap prevProps, JsMap nextProps) areEqual, + bool Function(JsMap prevProps, JsMap nextProps)? areEqual, ]); external static bool isValidElement(dynamic object); @@ -55,16 +55,16 @@ abstract class React { external static ReactClass get Fragment; external static List useState(dynamic value); - external static void useEffect(dynamic Function() sideEffect, [List dependencies]); - external static List useReducer(Function reducer, dynamic initialState, [Function init]); - external static Function useCallback(Function callback, List dependencies); + external static void useEffect(dynamic Function() sideEffect, [List? dependencies]); + external static List useReducer(Function reducer, dynamic initialState, [Function? init]); + external static Function useCallback(Function callback, List dependencies); external static ReactContext useContext(ReactContext context); - external static JsRef useRef([dynamic initialValue]); - external static dynamic useMemo(dynamic Function() createFunction, [List dependencies]); - external static void useLayoutEffect(dynamic Function() sideEffect, [List dependencies]); - external static void useImperativeHandle(dynamic ref, dynamic Function() createHandle, [List dependencies]); + external static JsRef useRef([dynamic? initialValue]); + external static dynamic useMemo(dynamic Function() createFunction, [List? dependencies]); + external static void useLayoutEffect(dynamic Function() sideEffect, [List? dependencies]); + external static void useImperativeHandle(dynamic ref, dynamic Function() createHandle, [List? dependencies]); // NOTE: The use of generics on the `useDebugValue` interop will break the hook. - external static dynamic useDebugValue(dynamic value, [Function format]); + external static dynamic useDebugValue(dynamic value, [Function? format]); } /// Creates a [Ref] object that can be attached to a [ReactElement] via the ref prop. @@ -106,7 +106,7 @@ class Ref { /// A reference to the latest instance of the rendered component. /// /// See [createRef] for usage examples and more info. - T get current { + T? get current { final jsCurrent = jsRef.current; // Note: this ReactComponent check will pass for many types of JS objects, @@ -124,7 +124,7 @@ class Ref { /// Sets the value of [current]. /// /// See: . - set current(T value) { + set current(T? value) { if (value is Component) { jsRef.current = value.jsThis; } else { @@ -227,7 +227,7 @@ class JsRef { /// See: . ReactComponentFactoryProxy forwardRef2( DartForwardRefFunctionComponent wrapperFunction, { - String displayName, + String? displayName, }) => ReactDartWrappedComponentFactoryProxy.forwardRef(wrapperFunction, displayName: displayName); @@ -270,7 +270,7 @@ ReactComponentFactoryProxy forwardRef2( /// /// See: . ReactComponentFactoryProxy memo2(ReactComponentFactoryProxy factory, - {bool Function(Map prevProps, Map nextProps) areEqual}) { + {bool Function(Map prevProps, Map nextProps)? areEqual}) { final _areEqual = areEqual == null ? null : allowInterop((JsMap prevProps, JsMap nextProps) { @@ -286,7 +286,7 @@ ReactComponentFactoryProxy memo2(ReactComponentFactoryProxy factory, @Deprecated('Use memo2') ReactJsComponentFactoryProxy memo(ReactDartFunctionComponentFactoryProxy factory, - {bool Function(Map prevProps, Map nextProps) areEqual}) { + {bool Function(Map prevProps, Map nextProps)? areEqual}) { final _areEqual = areEqual == null ? null : allowInterop((JsMap prevProps, JsMap nextProps) { @@ -352,14 +352,14 @@ class ReactClass { /// /// For use in `ReactDartComponentFactoryProxy2` when creating new [ReactElement]s, /// or for external use involving inspection of Dart prop defaults. - external JsMap get defaultProps; - external set defaultProps(JsMap value); + external JsMap? get defaultProps; + external set defaultProps(JsMap? value); /// The `displayName` string is used in debugging messages. /// /// See: - external String get displayName; - external set displayName(String value); + external String? get displayName; + external set displayName(String? value); /// The cached, unmodifiable copy of [Component.getDefaultProps] computed in /// [registerComponent]. @@ -367,9 +367,9 @@ class ReactClass { /// For use in `ReactDartComponentFactoryProxy` when creating new [ReactElement]s, /// or for external use involving inspection of Dart prop defaults. @Deprecated('7.0.0`') - external Map get dartDefaultProps; + external Map? get dartDefaultProps; @Deprecated('7.0.0`') - external set dartDefaultProps(Map value); + external set dartDefaultProps(Map? value); /// A string to distinguish between different Dart component implementations / base classes. /// @@ -377,9 +377,9 @@ class ReactClass { /// /// __For internal use only.__ @protected - external String get dartComponentVersion; + external String? get dartComponentVersion; @protected - external set dartComponentVersion(String value); + external set dartComponentVersion(String? value); } /// Constants for use with [ReactClass.dartComponentVersion] to distinguish @@ -400,14 +400,14 @@ abstract class ReactDartComponentVersion { /// Returns [ReactClass.dartComponentVersion] if [type] is the [ReactClass] for a Dart component /// (a react-dart [ReactElement] or [ReactComponent]), and null otherwise. @protected - static String fromType(dynamic type) { + static String? fromType(dynamic type) { // This check doesn't do much since ReactClass is an anonymous JS object, // but it lets us safely cast to ReactClass. if (type is ReactClass) { return type.dartComponentVersion; } if (type is Function) { - return getProperty(type, 'dartComponentVersion') as String; + return getProperty(type, 'dartComponentVersion') as String?; } return null; @@ -426,20 +426,20 @@ abstract class ReactDartComponentVersion { @anonymous class ReactClassConfig { external factory ReactClassConfig({ - String displayName, - List mixins, - Function componentWillMount, - Function componentDidMount, - Function componentWillReceiveProps, - Function shouldComponentUpdate, - Function componentWillUpdate, - Function componentDidUpdate, - Function componentWillUnmount, - Function getChildContext, - Map childContextTypes, - Function getDefaultProps, - Function getInitialState, - Function render, + String? displayName, + List? mixins, + Function? componentWillMount, + Function? componentDidMount, + Function? componentWillReceiveProps, + Function? shouldComponentUpdate, + Function? componentWillUpdate, + Function? componentDidUpdate, + Function? componentWillUnmount, + Function? getChildContext, + Map? childContextTypes, + Function? getDefaultProps, + Function? getInitialState, + Function? render, }); /// The `displayName` string is used in debugging messages. @@ -529,12 +529,12 @@ class ReactPortal { @anonymous class ReactComponent { // TODO: Cast as Component2 in 7.0.0 - external Component get dartComponent; + external Component? get dartComponent; // TODO how to make this JsMap without breaking stuff? external InteropProps get props; - external dynamic get context; - external JsMap get state; - external set state(JsMap value); + external dynamic? get context; + external JsMap? get state; + external set state(JsMap? value); external get refs; external void setState(state, [callback]); external void forceUpdate([callback]); @@ -595,11 +595,11 @@ class InteropProps implements JsMap { /// Will be removed alongside `Component` in the `7.0.0` release. @Deprecated('7.0.0') external ReactDartComponentInternal get internal; - external dynamic get key; - external dynamic get ref; + external dynamic? get key; + external dynamic? get ref; - external set key(dynamic value); - external set ref(dynamic value); + external set key(dynamic? value); + external set ref(dynamic? value); /// __Deprecated.__ /// @@ -610,9 +610,9 @@ class InteropProps implements JsMap { /// Will be removed alongside `Component` in the `7.0.0` release. @Deprecated('7.0.0') external factory InteropProps({ - ReactDartComponentInternal internal, - String key, - dynamic ref, + ReactDartComponentInternal? internal, + String? key, + dynamic? ref, }); } @@ -634,7 +634,7 @@ class ReactDartComponentInternal { /// /// For a `ReactComponent`, this is the props the component was last rendered with, /// and is used within props-related lifecycle internals. - Map props; + Map? props; } /// Internal react-dart information used to proxy React JS lifecycle to Dart @@ -706,8 +706,8 @@ void markChildrenValidated(List children) { @JS('_createReactDartComponentClass') @Deprecated('7.0.0') external ReactClass createReactDartComponentClass( - ReactDartInteropStatics dartInteropStatics, ComponentStatics componentStatics, - [JsComponentConfig jsConfig]); + ReactDartInteropStatics? dartInteropStatics, ComponentStatics? componentStatics, + [JsComponentConfig? jsConfig]); /// Returns a new JS [ReactClass] for a component that uses /// [dartInteropStatics] and [componentStatics] internally to proxy between @@ -715,8 +715,8 @@ external ReactClass createReactDartComponentClass( /// /// See `_ReactDartInteropStatics2.staticsForJs`]` for an example implementation. @JS('_createReactDartComponentClass2') -external ReactClass createReactDartComponentClass2(JsMap dartInteropStatics, ComponentStatics2 componentStatics, - [JsComponentConfig2 jsConfig]); +external ReactClass createReactDartComponentClass2(JsMap? dartInteropStatics, ComponentStatics2? componentStatics, + [JsComponentConfig2? jsConfig]); @JS('React.__isDevelopment') external bool get _inReactDevMode; @@ -790,9 +790,9 @@ class ComponentStatics2 { final Component2BridgeFactory bridgeFactory; ComponentStatics2({ - @required this.componentFactory, - @required this.instanceForStaticMethods, - @required this.bridgeFactory, + required this.componentFactory, + required this.instanceForStaticMethods, + required this.bridgeFactory, }); } @@ -812,8 +812,8 @@ class ComponentStatics2 { @anonymous class JsComponentConfig { external factory JsComponentConfig({ - Iterable childContextKeys, - Iterable contextKeys, + Iterable? childContextKeys, + Iterable? contextKeys, }); } @@ -823,7 +823,7 @@ class JsComponentConfig { @anonymous class JsComponentConfig2 { external factory JsComponentConfig2({ - @required List skipMethods, + required List skipMethods, dynamic contextType, JsMap defaultProps, JsMap propTypes, diff --git a/lib/react_test_utils.dart b/lib/react_test_utils.dart index 6642d672..1a7964a0 100644 --- a/lib/react_test_utils.dart +++ b/lib/react_test_utils.dart @@ -40,7 +40,7 @@ dynamic getComponentTypeV2(ReactComponentFactoryProxy componentFactory) => compo typedef ComponentTestFunction = bool Function(dynamic /* [1] */ component); -dynamic _jsifyEventData(Map eventData) => jsifyAndAllowInterop(eventData ?? const {}); +dynamic _jsifyEventData(Map? eventData) => jsifyAndAllowInterop(eventData ?? const {}); /// Event simulation interface. /// @@ -52,66 +52,68 @@ dynamic _jsifyEventData(Map eventData) => jsifyAndAllowInterop(eventData ?? cons /// This should include all events documented at: /// https://reactjs.org/docs/events.html class Simulate { - static void animationEnd(/* [1] */ node, [Map eventData]) => + static void animationEnd(/* [1] */ node, [Map? eventData]) => sw.Simulate.animationEnd(node, _jsifyEventData(eventData)); - static void animationIteration(/* [1] */ node, [Map eventData]) => + static void animationIteration(/* [1] */ node, [Map? eventData]) => sw.Simulate.animationIteration(node, _jsifyEventData(eventData)); - static void animationStart(/* [1] */ node, [Map eventData]) => + static void animationStart(/* [1] */ node, [Map? eventData]) => sw.Simulate.animationStart(node, _jsifyEventData(eventData)); - static void blur(/*[1]*/ node, [Map eventData]) => sw.Simulate.blur(node, _jsifyEventData(eventData)); - static void change(/*[1]*/ node, [Map eventData]) => sw.Simulate.change(node, _jsifyEventData(eventData)); - static void click(/*[1]*/ node, [Map eventData]) => sw.Simulate.click(node, _jsifyEventData(eventData)); - static void contextMenu(/*[1]*/ node, [Map eventData]) => sw.Simulate.contextMenu(node, _jsifyEventData(eventData)); - static void copy(/*[1]*/ node, [Map eventData]) => sw.Simulate.copy(node, _jsifyEventData(eventData)); - static void compositionEnd(/*[1]*/ node, [Map eventData]) => + static void blur(/*[1]*/ node, [Map? eventData]) => sw.Simulate.blur(node, _jsifyEventData(eventData)); + static void change(/*[1]*/ node, [Map? eventData]) => sw.Simulate.change(node, _jsifyEventData(eventData)); + static void click(/*[1]*/ node, [Map? eventData]) => sw.Simulate.click(node, _jsifyEventData(eventData)); + static void contextMenu(/*[1]*/ node, [Map? eventData]) => sw.Simulate.contextMenu(node, _jsifyEventData(eventData)); + static void copy(/*[1]*/ node, [Map? eventData]) => sw.Simulate.copy(node, _jsifyEventData(eventData)); + static void compositionEnd(/*[1]*/ node, [Map? eventData]) => sw.Simulate.compositionEnd(node, _jsifyEventData(eventData)); - static void compositionStart(/*[1]*/ node, [Map eventData]) => + static void compositionStart(/*[1]*/ node, [Map? eventData]) => sw.Simulate.compositionStart(node, _jsifyEventData(eventData)); - static void compositionUpdate(/*[1]*/ node, [Map eventData]) => + static void compositionUpdate(/*[1]*/ node, [Map? eventData]) => sw.Simulate.compositionUpdate(node, _jsifyEventData(eventData)); - static void cut(/*[1]*/ node, [Map eventData]) => sw.Simulate.cut(node, _jsifyEventData(eventData)); - static void doubleClick(/*[1]*/ node, [Map eventData]) => sw.Simulate.doubleClick(node, _jsifyEventData(eventData)); - static void drag(/*[1]*/ node, [Map eventData]) => sw.Simulate.drag(node, _jsifyEventData(eventData)); - static void dragEnd(/*[1]*/ node, [Map eventData]) => sw.Simulate.dragEnd(node, _jsifyEventData(eventData)); - static void dragEnter(/*[1]*/ node, [Map eventData]) => sw.Simulate.dragEnter(node, _jsifyEventData(eventData)); - static void dragExit(/*[1]*/ node, [Map eventData]) => sw.Simulate.dragExit(node, _jsifyEventData(eventData)); - static void dragLeave(/*[1]*/ node, [Map eventData]) => sw.Simulate.dragLeave(node, _jsifyEventData(eventData)); - static void dragOver(/*[1]*/ node, [Map eventData]) => sw.Simulate.dragOver(node, _jsifyEventData(eventData)); - static void dragStart(/*[1]*/ node, [Map eventData]) => sw.Simulate.dragStart(node, _jsifyEventData(eventData)); - static void drop(/*[1]*/ node, [Map eventData]) => sw.Simulate.drop(node, _jsifyEventData(eventData)); - static void focus(/*[1]*/ node, [Map eventData]) => sw.Simulate.focus(node, _jsifyEventData(eventData)); - static void gotPointerCapture(/*[1]*/ node, [Map eventData]) => + static void cut(/*[1]*/ node, [Map? eventData]) => sw.Simulate.cut(node, _jsifyEventData(eventData)); + static void doubleClick(/*[1]*/ node, [Map? eventData]) => sw.Simulate.doubleClick(node, _jsifyEventData(eventData)); + static void drag(/*[1]*/ node, [Map? eventData]) => sw.Simulate.drag(node, _jsifyEventData(eventData)); + static void dragEnd(/*[1]*/ node, [Map? eventData]) => sw.Simulate.dragEnd(node, _jsifyEventData(eventData)); + static void dragEnter(/*[1]*/ node, [Map? eventData]) => sw.Simulate.dragEnter(node, _jsifyEventData(eventData)); + static void dragExit(/*[1]*/ node, [Map? eventData]) => sw.Simulate.dragExit(node, _jsifyEventData(eventData)); + static void dragLeave(/*[1]*/ node, [Map? eventData]) => sw.Simulate.dragLeave(node, _jsifyEventData(eventData)); + static void dragOver(/*[1]*/ node, [Map? eventData]) => sw.Simulate.dragOver(node, _jsifyEventData(eventData)); + static void dragStart(/*[1]*/ node, [Map? eventData]) => sw.Simulate.dragStart(node, _jsifyEventData(eventData)); + static void drop(/*[1]*/ node, [Map? eventData]) => sw.Simulate.drop(node, _jsifyEventData(eventData)); + static void focus(/*[1]*/ node, [Map? eventData]) => sw.Simulate.focus(node, _jsifyEventData(eventData)); + static void gotPointerCapture(/*[1]*/ node, [Map? eventData]) => sw.Simulate.gotPointerCapture(node, _jsifyEventData(eventData)); - static void input(/*[1]*/ node, [Map eventData]) => sw.Simulate.input(node, _jsifyEventData(eventData)); - static void keyDown(/*[1]*/ node, [Map eventData]) => sw.Simulate.keyDown(node, _jsifyEventData(eventData)); - static void keyPress(/*[1]*/ node, [Map eventData]) => sw.Simulate.keyPress(node, _jsifyEventData(eventData)); - static void keyUp(/*[1]*/ node, [Map eventData]) => sw.Simulate.keyUp(node, _jsifyEventData(eventData)); - static void lostPointerCapture(/*[1]*/ node, [Map eventData]) => + static void input(/*[1]*/ node, [Map? eventData]) => sw.Simulate.input(node, _jsifyEventData(eventData)); + static void keyDown(/*[1]*/ node, [Map? eventData]) => sw.Simulate.keyDown(node, _jsifyEventData(eventData)); + static void keyPress(/*[1]*/ node, [Map? eventData]) => sw.Simulate.keyPress(node, _jsifyEventData(eventData)); + static void keyUp(/*[1]*/ node, [Map? eventData]) => sw.Simulate.keyUp(node, _jsifyEventData(eventData)); + static void lostPointerCapture(/*[1]*/ node, [Map? eventData]) => sw.Simulate.lostPointerCapture(node, _jsifyEventData(eventData)); - static void mouseDown(/*[1]*/ node, [Map eventData]) => sw.Simulate.mouseDown(node, _jsifyEventData(eventData)); - static void mouseMove(/*[1]*/ node, [Map eventData]) => sw.Simulate.mouseMove(node, _jsifyEventData(eventData)); - static void mouseOut(/*[1]*/ node, [Map eventData]) => sw.Simulate.mouseOut(node, _jsifyEventData(eventData)); - static void mouseOver(/*[1]*/ node, [Map eventData]) => sw.Simulate.mouseOver(node, _jsifyEventData(eventData)); - static void mouseUp(/*[1]*/ node, [Map eventData]) => sw.Simulate.mouseUp(node, _jsifyEventData(eventData)); - static void pointerCancel(/*[1]*/ node, [Map eventData]) => + static void mouseDown(/*[1]*/ node, [Map? eventData]) => sw.Simulate.mouseDown(node, _jsifyEventData(eventData)); + static void mouseMove(/*[1]*/ node, [Map? eventData]) => sw.Simulate.mouseMove(node, _jsifyEventData(eventData)); + static void mouseOut(/*[1]*/ node, [Map? eventData]) => sw.Simulate.mouseOut(node, _jsifyEventData(eventData)); + static void mouseOver(/*[1]*/ node, [Map? eventData]) => sw.Simulate.mouseOver(node, _jsifyEventData(eventData)); + static void mouseUp(/*[1]*/ node, [Map? eventData]) => sw.Simulate.mouseUp(node, _jsifyEventData(eventData)); + static void pointerCancel(/*[1]*/ node, [Map? eventData]) => sw.Simulate.pointerCancel(node, _jsifyEventData(eventData)); - static void pointerDown(/*[1]*/ node, [Map eventData]) => sw.Simulate.pointerDown(node, _jsifyEventData(eventData)); - static void pointerEnter(/*[1]*/ node, [Map eventData]) => sw.Simulate.pointerEnter(node, _jsifyEventData(eventData)); - static void pointerLeave(/*[1]*/ node, [Map eventData]) => sw.Simulate.pointerLeave(node, _jsifyEventData(eventData)); - static void pointerMove(/*[1]*/ node, [Map eventData]) => sw.Simulate.pointerMove(node, _jsifyEventData(eventData)); - static void pointerOut(/*[1]*/ node, [Map eventData]) => sw.Simulate.pointerOut(node, _jsifyEventData(eventData)); - static void pointerOver(/*[1]*/ node, [Map eventData]) => sw.Simulate.pointerOver(node, _jsifyEventData(eventData)); - static void pointerUp(/*[1]*/ node, [Map eventData]) => sw.Simulate.pointerUp(node, _jsifyEventData(eventData)); - static void paste(/*[1]*/ node, [Map eventData]) => sw.Simulate.paste(node, _jsifyEventData(eventData)); - static void scroll(/*[1]*/ node, [Map eventData]) => sw.Simulate.scroll(node, _jsifyEventData(eventData)); - static void submit(/*[1]*/ node, [Map eventData]) => sw.Simulate.submit(node, _jsifyEventData(eventData)); - static void touchCancel(/*[1]*/ node, [Map eventData]) => sw.Simulate.touchCancel(node, _jsifyEventData(eventData)); - static void touchEnd(/*[1]*/ node, [Map eventData]) => sw.Simulate.touchEnd(node, _jsifyEventData(eventData)); - static void touchMove(/*[1]*/ node, [Map eventData]) => sw.Simulate.touchMove(node, _jsifyEventData(eventData)); - static void touchStart(/*[1]*/ node, [Map eventData]) => sw.Simulate.touchStart(node, _jsifyEventData(eventData)); - static void transitionEnd(/*[1]*/ node, [Map eventData]) => + static void pointerDown(/*[1]*/ node, [Map? eventData]) => sw.Simulate.pointerDown(node, _jsifyEventData(eventData)); + static void pointerEnter(/*[1]*/ node, [Map? eventData]) => + sw.Simulate.pointerEnter(node, _jsifyEventData(eventData)); + static void pointerLeave(/*[1]*/ node, [Map? eventData]) => + sw.Simulate.pointerLeave(node, _jsifyEventData(eventData)); + static void pointerMove(/*[1]*/ node, [Map? eventData]) => sw.Simulate.pointerMove(node, _jsifyEventData(eventData)); + static void pointerOut(/*[1]*/ node, [Map? eventData]) => sw.Simulate.pointerOut(node, _jsifyEventData(eventData)); + static void pointerOver(/*[1]*/ node, [Map? eventData]) => sw.Simulate.pointerOver(node, _jsifyEventData(eventData)); + static void pointerUp(/*[1]*/ node, [Map? eventData]) => sw.Simulate.pointerUp(node, _jsifyEventData(eventData)); + static void paste(/*[1]*/ node, [Map? eventData]) => sw.Simulate.paste(node, _jsifyEventData(eventData)); + static void scroll(/*[1]*/ node, [Map? eventData]) => sw.Simulate.scroll(node, _jsifyEventData(eventData)); + static void submit(/*[1]*/ node, [Map? eventData]) => sw.Simulate.submit(node, _jsifyEventData(eventData)); + static void touchCancel(/*[1]*/ node, [Map? eventData]) => sw.Simulate.touchCancel(node, _jsifyEventData(eventData)); + static void touchEnd(/*[1]*/ node, [Map? eventData]) => sw.Simulate.touchEnd(node, _jsifyEventData(eventData)); + static void touchMove(/*[1]*/ node, [Map? eventData]) => sw.Simulate.touchMove(node, _jsifyEventData(eventData)); + static void touchStart(/*[1]*/ node, [Map? eventData]) => sw.Simulate.touchStart(node, _jsifyEventData(eventData)); + static void transitionEnd(/*[1]*/ node, [Map? eventData]) => sw.Simulate.transitionEnd(node, _jsifyEventData(eventData)); - static void wheel(/*[1]*/ node, [Map eventData]) => sw.Simulate.wheel(node, _jsifyEventData(eventData)); + static void wheel(/*[1]*/ node, [Map? eventData]) => sw.Simulate.wheel(node, _jsifyEventData(eventData)); } /// Traverse all components in tree and accumulate all components where @@ -153,7 +155,7 @@ bool isCompositeComponent(/* [1] */ instance) { return _isCompositeComponent(instance) // Workaround for DOM components being detected as composite: https://github.com/facebook/react/pull/3839 && - getProperty(instance, 'tagName') == null; + getProperty(instance as Object, 'tagName') == null; } @JS('React.addons.TestUtils.isCompositeComponentWithType') @@ -205,7 +207,7 @@ external List scryRenderedDOMComponentsWithTag(/* [1] */ tree, String t /// Render a Component into a detached DOM node in the document. @JS('React.addons.TestUtils.renderIntoDocument') -external /* [1] */ renderIntoDocument(ReactElement instance); +external /* [1] */ renderIntoDocument(ReactElement? instance); /// Pass a mocked component module to this method to augment it with useful /// methods that allow it to be used as a dummy React component. Instead of @@ -238,6 +240,6 @@ external ReactShallowRenderer createRenderer(); class ReactShallowRenderer { /// Get the rendered output. [render] must be called first external ReactElement getRenderOutput(); - external void render(ReactElement element, [context]); + external void render(ReactElement? element, [context]); external void unmount(); } diff --git a/lib/src/context.dart b/lib/src/context.dart index b5692b67..6e8f0ad6 100644 --- a/lib/src/context.dart +++ b/lib/src/context.dart @@ -103,13 +103,13 @@ class Context { /// /// Learn more: https://reactjs.org/docs/context.html#reactcreatecontext Context createContext([ - TValue defaultValue, - int Function(TValue currentValue, TValue nextValue) calculateChangedBits, + TValue? defaultValue, + int Function(TValue? currentValue, TValue? nextValue)? calculateChangedBits, ]) { int jsifyCalculateChangedBitsArgs(currentValue, nextValue) { - return calculateChangedBits( - ContextHelpers.unjsifyNewContext(currentValue) as TValue, - ContextHelpers.unjsifyNewContext(nextValue) as TValue, + return calculateChangedBits!( + ContextHelpers.unjsifyNewContext(currentValue) as TValue?, + ContextHelpers.unjsifyNewContext(nextValue) as TValue?, ); } @@ -124,7 +124,7 @@ Context createContext([ // A JavaScript symbol that we use as the key in a JS Object to wrap the Dart. @JS() -external get _reactDartContextSymbol; +external Object get _reactDartContextSymbol; /// A context utility for assisting with common needs of ReactDartContext. /// @@ -132,8 +132,8 @@ external get _reactDartContextSymbol; abstract class ContextHelpers { // Wraps context value in a JS Object for use on the JS side. // It is wrapped so that the same Dart value can be retrieved from Dart with [_unjsifyNewContext]. - static dynamic jsifyNewContext(dynamic context) { - final jsContextHolder = newObject(); + static dynamic jsifyNewContext(Object? context) { + final jsContextHolder = newObject() as Object; setProperty(jsContextHolder, _reactDartContextSymbol, DartValueWrapper.wrapIfNeeded(context)); return jsContextHolder; } @@ -141,7 +141,7 @@ abstract class ContextHelpers { // Unwraps context value from a JS Object for use on the Dart side. // The value is unwrapped so that the same Dart value can be passed through js and retrived by Dart // when used with [_jsifyNewContext]. - static dynamic unjsifyNewContext(dynamic interopContext) { + static dynamic unjsifyNewContext(Object? interopContext) { if (interopContext != null && hasProperty(interopContext, _reactDartContextSymbol)) { return DartValueWrapper.unwrapIfNeeded(getProperty(interopContext, _reactDartContextSymbol)); } diff --git a/lib/src/js_interop_util.dart b/lib/src/js_interop_util.dart index 9fe94d39..b499915a 100644 --- a/lib/src/js_interop_util.dart +++ b/lib/src/js_interop_util.dart @@ -6,7 +6,7 @@ import 'dart:js_util'; import 'package:js/js.dart'; @JS('Object.keys') -external List objectKeys(Object object); +external List objectKeys(Object object); @JS() @anonymous @@ -17,8 +17,8 @@ class JsPropertyDescriptor { @JS('Object.defineProperty') external void defineProperty(dynamic object, String propertyName, JsPropertyDescriptor descriptor); -String getJsFunctionName(Function object) => - (getProperty(object, 'name') ?? getProperty(object, '\$static_name')) as String; +String? getJsFunctionName(Function object) => + (getProperty(object, 'name') ?? getProperty(object, '\$static_name')) as String?; /// Creates JS `Promise` which is resolved when [future] completes. /// diff --git a/lib/src/prop_validator.dart b/lib/src/prop_validator.dart index 23664f5c..9cbecf07 100644 --- a/lib/src/prop_validator.dart +++ b/lib/src/prop_validator.dart @@ -8,14 +8,15 @@ typedef PropValidator = Error Function(T props, PropValidatorInfo info); /// Metadata about a prop being validated by a [PropValidator]. class PropValidatorInfo { final String propName; - final String componentName; + final String? componentName; final String location; - final String propFullName; + final String? propFullName; + // FIXME this is a public API; can we make these required? const PropValidatorInfo({ - this.propName, - this.componentName, - this.location, - this.propFullName, + required this.propName, + required this.componentName, + required this.location, + required this.propFullName, }); } diff --git a/lib/src/react_client/component_registration.dart b/lib/src/react_client/component_registration.dart index 198fc800..ccaa512a 100644 --- a/lib/src/react_client/component_registration.dart +++ b/lib/src/react_client/component_registration.dart @@ -100,7 +100,7 @@ ReactDartComponentFactoryProxy registerComponent( ReactDartComponentFactoryProxy2 registerComponent2( ComponentFactory componentFactory, { Iterable skipMethods = const ['getDerivedStateFromError', 'componentDidCatch'], - Component2BridgeFactory bridgeFactory, + Component2BridgeFactory? bridgeFactory, }) { var errorPrinted = false; try { @@ -125,11 +125,11 @@ ReactDartComponentFactoryProxy2 registerComponent2( rethrow; } - JsMap jsPropTypes; + JsMap? jsPropTypes; try { // Access `componentInstance.propTypes` within an assert so they get tree-shaken out of dart2js builds. assert(() { - jsPropTypes = bridgeFactory(componentInstance).jsifyPropTypes(componentInstance, componentInstance.propTypes); + jsPropTypes = bridgeFactory!(componentInstance).jsifyPropTypes(componentInstance, componentInstance.propTypes); return true; }()); } catch (e, stack) { @@ -172,5 +172,5 @@ ReactDartComponentFactoryProxy2 registerComponent2( /// Creates and returns a new `ReactDartFunctionComponentFactoryProxy` from the provided [dartFunctionComponent] /// which produces a new `JsFunctionComponent`. ReactDartFunctionComponentFactoryProxy registerFunctionComponent(DartFunctionComponent dartFunctionComponent, - {String displayName}) => + {String? displayName}) => ReactDartFunctionComponentFactoryProxy(dartFunctionComponent, displayName: displayName); diff --git a/lib/src/react_client/dart_interop_statics.dart b/lib/src/react_client/dart_interop_statics.dart index b20041ae..dd566e95 100644 --- a/lib/src/react_client/dart_interop_statics.dart +++ b/lib/src/react_client/dart_interop_statics.dart @@ -30,7 +30,7 @@ final ReactDartInteropStatics dartInteropStatics = (() { // ignore: omit_local_variable_types, prefer_function_declarations_over_variables final RefMethod getRef = (name) { - final ref = getProperty(jsThis.refs, name); + final ref = getProperty(jsThis.refs as Object, name); if (ref == null) return null; if (ref is Element) return ref; if (ref is ReactComponent) return ref.dartComponent ?? ref; @@ -75,7 +75,7 @@ final ReactDartInteropStatics dartInteropStatics = (() { /// 3. Update [Component.state] by calling [Component.transferComponentState] void _afterPropsChange(Component component, InteropContextValue nextContext) { component - ..props = component.nextProps // [1] + ..props = component.nextProps! // [1] ..context = component.nextContext // [2] ..transferComponentState(); // [3] } @@ -125,10 +125,10 @@ final ReactDartInteropStatics dartInteropStatics = (() { // If shouldComponentUpdateWithContext returns a valid bool (default implementation returns null), // then don't bother calling `shouldComponentUpdate` and have it trump. - var shouldUpdate = - component.shouldComponentUpdateWithContext(component.nextProps, component.nextState, component.nextContext); + var shouldUpdate = component.shouldComponentUpdateWithContext( + component.nextProps!, component.nextState, component.nextContext!); - shouldUpdate ??= component.shouldComponentUpdate(component.nextProps, component.nextState); + shouldUpdate ??= component.shouldComponentUpdate(component.nextProps!, component.nextState); if (shouldUpdate) { return true; @@ -146,8 +146,8 @@ final ReactDartInteropStatics dartInteropStatics = (() { void handleComponentWillUpdate(Component component, InteropContextValue nextContext) => zone.run(() { /// Call `componentWillUpdate` and the context variant component - ..componentWillUpdate(component.nextProps, component.nextState) - ..componentWillUpdateWithContext(component.nextProps, component.nextState, component.nextContext); + ..componentWillUpdate(component.nextProps!, component.nextState) + ..componentWillUpdateWithContext(component.nextProps!, component.nextState, component.nextContext); _afterPropsChange(component, nextContext); }); @@ -159,7 +159,7 @@ final ReactDartInteropStatics dartInteropStatics = (() { final prevInternalProps = prevInternal.props; /// Call `componentDidUpdate` and the context variant - component.componentDidUpdate(prevInternalProps, component.prevState); + component.componentDidUpdate(prevInternalProps!, component.prevState!); _callSetStateCallbacks(component); // Clear out prevState after it's done being used so it's not retained @@ -216,7 +216,7 @@ abstract class ReactDartInteropStatics2 { jsThis.state = jsBackingMapOrJsCopy(component.initialState); - component.state = JsBackedMap.backedBy(jsThis.state); + component.state = JsBackedMap.backedBy(jsThis.state!); // ignore: invalid_use_of_protected_member Component2Bridge.bridgeForComponent[component] = componentStatics.bridgeFactory(component); @@ -244,7 +244,7 @@ abstract class ReactDartInteropStatics2 { return value; }); - static JsMap handleGetDerivedStateFromProps( + static JsMap? handleGetDerivedStateFromProps( ComponentStatics2 componentStatics, JsMap jsNextProps, JsMap jsPrevState) => // dartfmt // ignore: invalid_use_of_visible_for_testing_member componentZone.run(() { @@ -299,7 +299,7 @@ abstract class ReactDartInteropStatics2 { } }); - static JsMap handleGetDerivedStateFromError(ComponentStatics2 componentStatics, dynamic error) => // dartfmt + static JsMap? handleGetDerivedStateFromError(ComponentStatics2 componentStatics, dynamic error) => // dartfmt // ignore: invalid_use_of_visible_for_testing_member componentZone.run(() { // Due to the error object being passed in from ReactJS it is a javascript object that does not get dartified. diff --git a/lib/src/react_client/event_helpers.dart b/lib/src/react_client/event_helpers.dart index 443c5425..f56a05b6 100644 --- a/lib/src/react_client/event_helpers.dart +++ b/lib/src/react_client/event_helpers.dart @@ -31,7 +31,7 @@ SyntheticKeyboardEvent wrapNativeKeyboardEvent(KeyboardEvent nativeEvent) { 'isPersistent': () => true, // SyntheticKeyboardEvent fields 'altKey': nativeEvent.altKey, - 'char': nativeEvent.charCode == null ? null : String.fromCharCode(nativeEvent.charCode), + 'char': String.fromCharCode(nativeEvent.charCode), 'ctrlKey': nativeEvent.ctrlKey, 'locale': null, 'location': nativeEvent.location, @@ -110,19 +110,19 @@ SyntheticFormEvent fakeSyntheticFormEvent(Element element, String type) { } Map _wrapBaseEventPropertiesInMap({ - SyntheticEvent baseEvent, - bool bubbles, - bool cancelable, - dynamic currentTarget, - bool defaultPrevented, - void Function() preventDefault, - void Function() stopPropagation, - num eventPhase, - bool isTrusted, - dynamic nativeEvent, - dynamic target, - num timeStamp, - String type, + SyntheticEvent? baseEvent, + bool? bubbles, + bool? cancelable, + dynamic? currentTarget, + bool? defaultPrevented, + void Function()? preventDefault, + void Function()? stopPropagation, + num? eventPhase, + bool? isTrusted, + dynamic? nativeEvent, + dynamic? target, + num? timeStamp, + String? type, }) { return { 'bubbles': bubbles ?? baseEvent?.bubbles ?? false, @@ -146,19 +146,19 @@ Map _wrapBaseEventPropertiesInMap({ /// by using the named parameters, this function will merge previously existing [baseEvent]s with the named parameters provided. /// The named parameters takes precedence, and therefore can be used to override specific fields on the [baseEvent]. SyntheticEvent createSyntheticEvent({ - SyntheticEvent baseEvent, - bool bubbles, - bool cancelable, - dynamic currentTarget, - bool defaultPrevented, - void Function() preventDefault, - void Function() stopPropagation, - num eventPhase, - bool isTrusted, - dynamic nativeEvent, - dynamic target, - num timeStamp, - String type, + SyntheticEvent? baseEvent, + bool? bubbles, + bool? cancelable, + dynamic? currentTarget, + bool? defaultPrevented, + void Function()? preventDefault, + void Function()? stopPropagation, + num? eventPhase, + bool? isTrusted, + dynamic? nativeEvent, + dynamic? target, + num? timeStamp, + String? type, }) { return jsifyAndAllowInterop(_wrapBaseEventPropertiesInMap( baseEvent: baseEvent, @@ -183,20 +183,20 @@ SyntheticEvent createSyntheticEvent({ /// by using the named parameters, this function will merge previously existing [baseEvent]s with the named parameters provided. /// The named parameters takes precedence, and therefore can be used to override specific fields on the [baseEvent]. SyntheticClipboardEvent createSyntheticClipboardEvent({ - SyntheticClipboardEvent baseEvent, - bool bubbles, - bool cancelable, - dynamic currentTarget, - bool defaultPrevented, - void Function() preventDefault, - void Function() stopPropagation, - num eventPhase, - bool isTrusted, - dynamic nativeEvent, - dynamic target, - num timeStamp, - String type, - dynamic clipboardData, + SyntheticClipboardEvent? baseEvent, + bool? bubbles, + bool? cancelable, + dynamic? currentTarget, + bool? defaultPrevented, + void Function()? preventDefault, + void Function()? stopPropagation, + num? eventPhase, + bool? isTrusted, + dynamic? nativeEvent, + dynamic? target, + num? timeStamp, + String? type, + dynamic? clipboardData, }) { return jsifyAndAllowInterop({ ..._wrapBaseEventPropertiesInMap( @@ -224,30 +224,30 @@ SyntheticClipboardEvent createSyntheticClipboardEvent({ /// by using the named parameters, this function will merge previously existing [baseEvent]s with the named parameters provided. /// The named parameters takes precedence, and therefore can be used to override specific fields on the [baseEvent]. SyntheticKeyboardEvent createSyntheticKeyboardEvent({ - SyntheticKeyboardEvent baseEvent, - bool bubbles, - bool cancelable, - dynamic currentTarget, - bool defaultPrevented, - void Function() preventDefault, - void Function() stopPropagation, - num eventPhase, - bool isTrusted, - dynamic nativeEvent, - dynamic target, - num timeStamp, - String type, - bool altKey, - String char, - bool ctrlKey, - String locale, - num location, - String key, - bool metaKey, - bool repeat, - bool shiftKey, - num keyCode, - num charCode, + SyntheticKeyboardEvent? baseEvent, + bool? bubbles, + bool? cancelable, + dynamic? currentTarget, + bool? defaultPrevented, + void Function()? preventDefault, + void Function()? stopPropagation, + num? eventPhase, + bool? isTrusted, + dynamic? nativeEvent, + dynamic? target, + num? timeStamp, + String? type, + bool? altKey, + String? char, + bool? ctrlKey, + String? locale, + num? location, + String? key, + bool? metaKey, + bool? repeat, + bool? shiftKey, + num? keyCode, + num? charCode, }) { return jsifyAndAllowInterop({ ..._wrapBaseEventPropertiesInMap( @@ -285,20 +285,20 @@ SyntheticKeyboardEvent createSyntheticKeyboardEvent({ /// by using the named parameters, this function will merge previously existing [baseEvent]s with the named parameters provided. /// The named parameters takes precedence, and therefore can be used to override specific fields on the [baseEvent]. SyntheticCompositionEvent createSyntheticCompositionEvent({ - SyntheticCompositionEvent baseEvent, - bool bubbles, - bool cancelable, - dynamic currentTarget, - bool defaultPrevented, - void Function() preventDefault, - void Function() stopPropagation, - num eventPhase, - bool isTrusted, - dynamic nativeEvent, - dynamic target, - num timeStamp, - String type, - String data, + SyntheticCompositionEvent? baseEvent, + bool? bubbles, + bool? cancelable, + dynamic? currentTarget, + bool? defaultPrevented, + void Function()? preventDefault, + void Function()? stopPropagation, + num? eventPhase, + bool? isTrusted, + dynamic? nativeEvent, + dynamic? target, + num? timeStamp, + String? type, + String? data, }) { return jsifyAndAllowInterop({ ..._wrapBaseEventPropertiesInMap( @@ -326,20 +326,20 @@ SyntheticCompositionEvent createSyntheticCompositionEvent({ /// by using the named parameters, this function will merge previously existing [baseEvent]s with the named parameters provided. /// The named parameters takes precedence, and therefore can be used to override specific fields on the [baseEvent]. SyntheticFocusEvent createSyntheticFocusEvent({ - SyntheticFocusEvent baseEvent, - bool bubbles, - bool cancelable, - dynamic currentTarget, - bool defaultPrevented, - void Function() preventDefault, - void Function() stopPropagation, - num eventPhase, - bool isTrusted, - dynamic nativeEvent, - dynamic target, - num timeStamp, - String type, - /*DOMEventTarget*/ dynamic relatedTarget, + SyntheticFocusEvent? baseEvent, + bool? bubbles, + bool? cancelable, + dynamic? currentTarget, + bool? defaultPrevented, + void Function()? preventDefault, + void Function()? stopPropagation, + num? eventPhase, + bool? isTrusted, + dynamic? nativeEvent, + dynamic? target, + num? timeStamp, + String? type, + /*DOMEventTarget*/ dynamic? relatedTarget, }) { return jsifyAndAllowInterop({ ..._wrapBaseEventPropertiesInMap( @@ -367,19 +367,19 @@ SyntheticFocusEvent createSyntheticFocusEvent({ /// by using the named parameters, this function will merge previously existing [baseEvent]s with the named parameters provided. /// The named parameters takes precedence, and therefore can be used to override specific fields on the [baseEvent]. SyntheticFormEvent createSyntheticFormEvent({ - SyntheticFormEvent baseEvent, - bool bubbles, - bool cancelable, - dynamic currentTarget, - bool defaultPrevented, - void Function() preventDefault, - void Function() stopPropagation, - num eventPhase, - bool isTrusted, - dynamic nativeEvent, - dynamic target, - num timeStamp, - String type, + SyntheticFormEvent? baseEvent, + bool? bubbles, + bool? cancelable, + dynamic? currentTarget, + bool? defaultPrevented, + void Function()? preventDefault, + void Function()? stopPropagation, + num? eventPhase, + bool? isTrusted, + dynamic? nativeEvent, + dynamic? target, + num? timeStamp, + String? type, }) { return jsifyAndAllowInterop({ ..._wrapBaseEventPropertiesInMap( @@ -406,33 +406,33 @@ SyntheticFormEvent createSyntheticFormEvent({ /// by using the named parameters, this function will merge previously existing [baseEvent]s with the named parameters provided. /// The named parameters takes precedence, and therefore can be used to override specific fields on the [baseEvent]. SyntheticMouseEvent createSyntheticMouseEvent({ - SyntheticMouseEvent baseEvent, - bool bubbles, - bool cancelable, - dynamic currentTarget, - bool defaultPrevented, - void Function() preventDefault, - void Function() stopPropagation, - num eventPhase, - bool isTrusted, - dynamic nativeEvent, - dynamic target, - num timeStamp, - String type, - bool altKey, - num button, - num buttons, - num clientX, - num clientY, - bool ctrlKey, - dynamic dataTransfer, - bool metaKey, - num pageX, - num pageY, - /*DOMEventTarget*/ dynamic relatedTarget, - num screenX, - num screenY, - bool shiftKey, + SyntheticMouseEvent? baseEvent, + bool? bubbles, + bool? cancelable, + dynamic? currentTarget, + bool? defaultPrevented, + void Function()? preventDefault, + void Function()? stopPropagation, + num? eventPhase, + bool? isTrusted, + dynamic? nativeEvent, + dynamic? target, + num? timeStamp, + String? type, + bool? altKey, + num? button, + num? buttons, + num? clientX, + num? clientY, + bool? ctrlKey, + dynamic? dataTransfer, + bool? metaKey, + num? pageX, + num? pageY, + /*DOMEventTarget*/ dynamic? relatedTarget, + num? screenX, + num? screenY, + bool? shiftKey, }) { return jsifyAndAllowInterop({ ..._wrapBaseEventPropertiesInMap( @@ -473,29 +473,29 @@ SyntheticMouseEvent createSyntheticMouseEvent({ /// by using the named parameters, this function will merge previously existing [baseEvent]s with the named parameters provided. /// The named parameters takes precedence, and therefore can be used to override specific fields on the [baseEvent]. SyntheticPointerEvent createSyntheticPointerEvent({ - SyntheticPointerEvent baseEvent, - bool bubbles, - bool cancelable, - dynamic currentTarget, - bool defaultPrevented, - void Function() preventDefault, - void Function() stopPropagation, - num eventPhase, - bool isTrusted, - dynamic nativeEvent, - dynamic target, - num timeStamp, - String type, - num pointerId, - num width, - num height, - num pressure, - num tangentialPressure, - num tiltX, - num tiltY, - num twist, - String pointerType, - bool isPrimary, + SyntheticPointerEvent? baseEvent, + bool? bubbles, + bool? cancelable, + dynamic? currentTarget, + bool? defaultPrevented, + void Function()? preventDefault, + void Function()? stopPropagation, + num? eventPhase, + bool? isTrusted, + dynamic? nativeEvent, + dynamic? target, + num? timeStamp, + String? type, + num? pointerId, + num? width, + num? height, + num? pressure, + num? tangentialPressure, + num? tiltX, + num? tiltY, + num? twist, + String? pointerType, + bool? isPrimary, }) { return jsifyAndAllowInterop({ ..._wrapBaseEventPropertiesInMap( @@ -532,26 +532,26 @@ SyntheticPointerEvent createSyntheticPointerEvent({ /// by using the named parameters, this function will merge previously existing [baseEvent]s with the named parameters provided. /// The named parameters takes precedence, and therefore can be used to override specific fields on the [baseEvent]. SyntheticTouchEvent createSyntheticTouchEvent({ - SyntheticTouchEvent baseEvent, - bool bubbles, - bool cancelable, - dynamic currentTarget, - bool defaultPrevented, - void Function() preventDefault, - void Function() stopPropagation, - num eventPhase, - bool isTrusted, - dynamic nativeEvent, - dynamic target, - num timeStamp, - String type, - bool altKey, - /*DOMTouchList*/ dynamic changedTouches, - bool ctrlKey, - bool metaKey, - bool shiftKey, - /*DOMTouchList*/ dynamic targetTouches, - /*DOMTouchList*/ dynamic touches, + SyntheticTouchEvent? baseEvent, + bool? bubbles, + bool? cancelable, + dynamic? currentTarget, + bool? defaultPrevented, + void Function()? preventDefault, + void Function()? stopPropagation, + num? eventPhase, + bool? isTrusted, + dynamic? nativeEvent, + dynamic? target, + num? timeStamp, + String? type, + bool? altKey, + /*DOMTouchList*/ dynamic? changedTouches, + bool? ctrlKey, + bool? metaKey, + bool? shiftKey, + /*DOMTouchList*/ dynamic? targetTouches, + /*DOMTouchList*/ dynamic? touches, }) { return jsifyAndAllowInterop({ ..._wrapBaseEventPropertiesInMap( @@ -585,22 +585,22 @@ SyntheticTouchEvent createSyntheticTouchEvent({ /// by using the named parameters, this function will merge previously existing [baseEvent]s with the named parameters provided. /// The named parameters takes precedence, and therefore can be used to override specific fields on the [baseEvent]. SyntheticTransitionEvent createSyntheticTransitionEvent({ - SyntheticTransitionEvent baseEvent, - bool bubbles, - bool cancelable, - dynamic currentTarget, - bool defaultPrevented, - void Function() preventDefault, - void Function() stopPropagation, - num eventPhase, - bool isTrusted, - dynamic nativeEvent, - dynamic target, - num timeStamp, - String type, - String propertyName, - num elapsedTime, - String pseudoElement, + SyntheticTransitionEvent? baseEvent, + bool? bubbles, + bool? cancelable, + dynamic? currentTarget, + bool? defaultPrevented, + void Function()? preventDefault, + void Function()? stopPropagation, + num? eventPhase, + bool? isTrusted, + dynamic? nativeEvent, + dynamic? target, + num? timeStamp, + String? type, + String? propertyName, + num? elapsedTime, + String? pseudoElement, }) { return jsifyAndAllowInterop({ ..._wrapBaseEventPropertiesInMap( @@ -630,22 +630,22 @@ SyntheticTransitionEvent createSyntheticTransitionEvent({ /// by using the named parameters, this function will merge previously existing [baseEvent]s with the named parameters provided. /// The named parameters takes precedence, and therefore can be used to override specific fields on the [baseEvent]. SyntheticAnimationEvent createSyntheticAnimationEvent({ - SyntheticAnimationEvent baseEvent, - bool bubbles, - bool cancelable, - dynamic currentTarget, - bool defaultPrevented, - void Function() preventDefault, - void Function() stopPropagation, - num eventPhase, - bool isTrusted, - dynamic nativeEvent, - dynamic target, - num timeStamp, - String type, - String animationName, - num elapsedTime, - String pseudoElement, + SyntheticAnimationEvent? baseEvent, + bool? bubbles, + bool? cancelable, + dynamic? currentTarget, + bool? defaultPrevented, + void Function()? preventDefault, + void Function()? stopPropagation, + num? eventPhase, + bool? isTrusted, + dynamic? nativeEvent, + dynamic? target, + num? timeStamp, + String? type, + String? animationName, + num? elapsedTime, + String? pseudoElement, }) { return jsifyAndAllowInterop({ ..._wrapBaseEventPropertiesInMap( @@ -675,21 +675,21 @@ SyntheticAnimationEvent createSyntheticAnimationEvent({ /// by using the named parameters, this function will merge previously existing [baseEvent]s with the named parameters provided. /// The named parameters takes precedence, and therefore can be used to override specific fields on the [baseEvent]. SyntheticUIEvent createSyntheticUIEvent({ - SyntheticUIEvent baseEvent, - bool bubbles, - bool cancelable, - dynamic currentTarget, - bool defaultPrevented, - void Function() preventDefault, - void Function() stopPropagation, - num eventPhase, - bool isTrusted, - dynamic nativeEvent, - dynamic target, - num timeStamp, - String type, - num detail, - /*DOMAbstractView*/ dynamic view, + SyntheticUIEvent? baseEvent, + bool? bubbles, + bool? cancelable, + dynamic? currentTarget, + bool? defaultPrevented, + void Function()? preventDefault, + void Function()? stopPropagation, + num? eventPhase, + bool? isTrusted, + dynamic? nativeEvent, + dynamic? target, + num? timeStamp, + String? type, + num? detail, + /*DOMAbstractView*/ dynamic? view, }) { return jsifyAndAllowInterop({ ..._wrapBaseEventPropertiesInMap( @@ -718,23 +718,23 @@ SyntheticUIEvent createSyntheticUIEvent({ /// by using the named parameters, this function will merge previously existing [baseEvent]s with the named parameters provided. /// The named parameters takes precedence, and therefore can be used to override specific fields on the [baseEvent]. SyntheticWheelEvent createSyntheticWheelEvent({ - SyntheticWheelEvent baseEvent, - bool bubbles, - bool cancelable, - dynamic currentTarget, - bool defaultPrevented, - void Function() preventDefault, - void Function() stopPropagation, - num eventPhase, - bool isTrusted, - dynamic nativeEvent, - dynamic target, - num timeStamp, - String type, - num deltaX, - num deltaMode, - num deltaY, - num deltaZ, + SyntheticWheelEvent? baseEvent, + bool? bubbles, + bool? cancelable, + dynamic? currentTarget, + bool? defaultPrevented, + void Function()? preventDefault, + void Function()? stopPropagation, + num? eventPhase, + bool? isTrusted, + dynamic? nativeEvent, + dynamic? target, + num? timeStamp, + String? type, + num? deltaX, + num? deltaMode, + num? deltaY, + num? deltaZ, }) { return jsifyAndAllowInterop({ ..._wrapBaseEventPropertiesInMap( @@ -760,8 +760,9 @@ SyntheticWheelEvent createSyntheticWheelEvent({ } extension SyntheticEventTypeHelpers on SyntheticEvent { - bool _checkEventType(List types) => this != null && type != null && types.any((t) => type.contains(t)); - bool _hasProperty(String propertyName) => this != null && hasProperty(this, propertyName); + // FIXME silence warnings, reinstate test (in mixed mode?) to test for null + bool _checkEventType(List types) => getProperty(this, 'type') != null && types.any((t) => type.contains(t)); + bool _hasProperty(String propertyName) => hasProperty(this, propertyName); /// Whether the event instance has been removed from the ReactJS event pool. /// @@ -809,5 +810,5 @@ extension DataTransferHelper on SyntheticMouseEvent { /// The data that is transferred during a drag and drop interaction. /// /// See - SyntheticDataTransfer get dataTransfer => syntheticDataTransferFactory(getProperty(this, 'dataTransfer')); + SyntheticDataTransfer? get dataTransfer => syntheticDataTransferFactory(getProperty(this, 'dataTransfer')); } diff --git a/lib/src/react_client/factory_util.dart b/lib/src/react_client/factory_util.dart index aa3f4201..1d0d9b20 100644 --- a/lib/src/react_client/factory_util.dart +++ b/lib/src/react_client/factory_util.dart @@ -29,7 +29,7 @@ dynamic convertArgsToChildren(List childrenArgs) { } @Deprecated('Event handlers are no longer converted. This will be removed in 7.0.0.') -Function unconvertJsEventHandler(Function jsConvertedEventHandler) => null; +Function? unconvertJsEventHandler(Function jsConvertedEventHandler) => null; void convertRefValue(Map args) { final ref = args['ref']; @@ -101,7 +101,7 @@ dynamic generateChildren(List childrenArgs, {bool shouldAlwaysBeList = false}) { if (children == null) { children = shouldAlwaysBeList ? childrenArgs.map(listifyChildren).toList() : childrenArgs; - markChildrenValidated(children as List); + markChildrenValidated(children); } return children; diff --git a/lib/src/react_client/private_utils.dart b/lib/src/react_client/private_utils.dart index 3ea23ed9..7e9ad6a9 100644 --- a/lib/src/react_client/private_utils.dart +++ b/lib/src/react_client/private_utils.dart @@ -33,7 +33,7 @@ T validateJsApiThenReturn(T Function() computeReturn) { Map unjsifyContext(InteropContextValue interopContext) { // TODO consider using `contextKeys` for this if perf of objectKeys is bad. return Map.fromIterable(objectKeys(interopContext), value: (key) { - final internal = getProperty(interopContext, key) as ReactDartContextInternal; + final internal = getProperty(interopContext, key) as ReactDartContextInternal?; return internal?.value; }); } @@ -49,8 +49,9 @@ void validateJsApi() { React.isValidElement(null); ReactDom.findDOMNode(null); // ignore: deprecated_member_use_from_same_package - createReactDartComponentClass(null, null, null); - createReactDartComponentClass2(null, null, null); + // fixme validate the bundle another way so we don't have to make these null + // createReactDartComponentClass(null, null, null); + // createReactDartComponentClass2(null, null, null); _isJsApiValid = true; } on NoSuchMethodError catch (_) { throw Exception('react.js and react_dom.js must be loaded.'); @@ -62,18 +63,18 @@ void validateJsApi() { /// A wrapper around a value that can't be stored in its raw form /// within a JS object (e.g., a Dart function). class DartValueWrapper { - final dynamic value; + final Object? value; const DartValueWrapper(this.value); - static dynamic wrapIfNeeded(dynamic value) { + static dynamic wrapIfNeeded(Object? value) { if (value is Function && !identical(allowInterop(value), value)) { return DartValueWrapper(value); } return value; } - static dynamic unwrapIfNeeded(dynamic value) { + static dynamic unwrapIfNeeded(Object? value) { if (value is DartValueWrapper) { return value.value; } diff --git a/lib/src/react_client/synthetic_data_transfer.dart b/lib/src/react_client/synthetic_data_transfer.dart index 227ecf39..ab2e466d 100644 --- a/lib/src/react_client/synthetic_data_transfer.dart +++ b/lib/src/react_client/synthetic_data_transfer.dart @@ -13,14 +13,14 @@ class SyntheticDataTransfer { /// The value must be `none`, `copy`, `link` or `move`. /// /// See - final String dropEffect; + final String? dropEffect; /// Provides all of the types of operations that are possible. /// /// Must be one of `none`, `copy`, `copyLink`, `copyMove`, `link`, `linkMove`, `move`, `all` or `uninitialized`. /// /// See - final String effectAllowed; + final String? effectAllowed; /// Contains a list of all the local files available on the data transfer. /// @@ -41,7 +41,7 @@ class SyntheticDataTransfer { /// /// [dt] is typed as Object instead of [dynamic] to avoid dynamic calls in the method body, /// ensuring the code is statically sound. -SyntheticDataTransfer syntheticDataTransferFactory(Object dt) { +SyntheticDataTransfer? syntheticDataTransferFactory(Object? dt) { if (dt == null) return null; // `SyntheticDataTransfer` is possible because `createSyntheticMouseEvent` can take in an event that already @@ -49,11 +49,11 @@ SyntheticDataTransfer syntheticDataTransferFactory(Object dt) { // already a `SyntheticDataTransfer` event and should just be returned. if (dt is SyntheticDataTransfer) return dt; - List rawFiles; - List rawTypes; + List? rawFiles; + List? rawTypes; - String effectAllowed; - String dropEffect; + String? effectAllowed; + String? dropEffect; // Handle `dt` being either a native DOM DataTransfer object or a JS object that looks like it (events.NonNativeDataTransfer). // Casting a JS object to DataTransfer fails intermittently in dart2js, and vice-versa fails intermittently in either DDC or dart2js. diff --git a/lib/src/react_client/synthetic_event_wrappers.dart b/lib/src/react_client/synthetic_event_wrappers.dart index c4719b0e..806b6bbd 100644 --- a/lib/src/react_client/synthetic_event_wrappers.dart +++ b/lib/src/react_client/synthetic_event_wrappers.dart @@ -22,6 +22,8 @@ class SyntheticEvent { /// Use `createSyntheticEvent` instead. external factory SyntheticEvent._(); + // FIXME do most of these need to be nullable because of simulate not populating all fields? 🤢 + /// Indicates whether the [Event] bubbles up through the DOM or not. /// /// See: diff --git a/lib/src/typedefs.dart b/lib/src/typedefs.dart index 6f09e1e6..d613f58f 100644 --- a/lib/src/typedefs.dart +++ b/lib/src/typedefs.dart @@ -6,14 +6,14 @@ import 'package:react/react_client/js_backed_map.dart'; /// The type of `Component.ref` specified as a callback. /// /// See: -typedef CallbackRef = Function(T componentOrDomNode); +typedef CallbackRef = Function(T? componentOrDomNode); /// The function signature for ReactJS Function Components. /// /// - [props] will always be supplied as the first argument /// - [legacyContext] has been deprecated and should not be used but remains for backward compatibility and is necessary /// to match Dart's generated call signature based on the number of args React provides. -typedef JsFunctionComponent = dynamic Function(JsMap props, [JsMap legacyContext]); +typedef JsFunctionComponent = dynamic Function(JsMap props, [JsMap? legacyContext]); typedef JsForwardRefFunctionComponent = dynamic Function(JsMap props, dynamic ref); @@ -26,7 +26,7 @@ typedef RefMethod = dynamic Function(String ref); /// Typedef for the `updater` argument of [Component2.setStateWithUpdater]. /// /// See: -typedef StateUpdaterCallback = Map Function(Map prevState, Map props); +typedef StateUpdaterCallback = Map? Function(Map prevState, Map props); /// Typedef of a non-transactional [Component2.setState] callback. /// diff --git a/pubspec.yaml b/pubspec.yaml index c62f298f..bed96e36 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,9 +3,9 @@ version: 6.2.0 description: Bindings of the ReactJS library for building interactive interfaces. homepage: https://github.com/cleandart/react-dart environment: - sdk: ">=2.11.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dependencies: - js: ^0.6.0 + js: ^0.6.3 meta: ^1.6.0 dev_dependencies: args: ^2.0.0 diff --git a/test/factory/common_factory_tests.dart b/test/factory/common_factory_tests.dart index c8282980..864135e7 100644 --- a/test/factory/common_factory_tests.dart +++ b/test/factory/common_factory_tests.dart @@ -8,7 +8,6 @@ import 'dart:js'; import 'dart:js_util'; import 'package:js/js.dart'; -import 'package:meta/meta.dart'; import 'package:react/react_client/js_backed_map.dart'; import 'package:react/react_client/js_interop_helpers.dart'; import 'package:test/test.dart'; @@ -27,7 +26,7 @@ import '../util.dart'; /// [dartComponentVersion] should be specified for all components with Dart render code in order to /// properly test `props.children`, forwardRef compatibility, etc. void commonFactoryTests(ReactComponentFactoryProxy factory, - {String dartComponentVersion, bool skipPropValuesTest = false}) { + {String? dartComponentVersion, bool skipPropValuesTest = false}) { _childKeyWarningTests( factory, renderWithUniqueOwnerName: _renderWithUniqueOwnerName, @@ -45,7 +44,7 @@ void commonFactoryTests(ReactComponentFactoryProxy factory, }); void sharedChildrenTests(dynamic Function(ReactElement instance) getChildren, - {@required bool shouldAlwaysBeList, Map props = const {}}) { + {required bool shouldAlwaysBeList, Map props = const {}}) { // There are different code paths for 0, 1, 2, 3, 4, 5, 6, and 6+ arguments. // Test all of them. group('a number of variadic children:', () { @@ -180,7 +179,7 @@ void commonFactoryTests(ReactComponentFactoryProxy factory, expect(componentZone, isNot(Zone.current), reason: 'test setup: component zone should be different than the zone used to render it'); - Zone renderZone; + Zone? renderZone; rtu.renderIntoDocument(factory({ 'onDartRender': (_) { renderZone = Zone.current; @@ -221,17 +220,15 @@ void domEventHandlerWrappingTests(ReactComponentFactoryProxy factory) { const jsCloned = EventTestCase.js('onClick', 'cloned onto component '); const eventCases = {dart, jsCloned, dartCloned}; - Element node; - Map events; - Map propsFromDartRender; + late Element node; + late Map events; + late Map propsFromDartRender; setUpAll(() { expect(eventCases.map((h) => h.eventPropKey).toSet(), hasLength(eventCases.length), reason: 'test setup: each helper should have a unique event key'); - node = null; events = {}; - propsFromDartRender = null; var element = factory({ 'onDartRender': (Map p) { @@ -278,7 +275,7 @@ void domEventHandlerWrappingTests(ReactComponentFactoryProxy factory) { reason: 'test setup: component must pass props into props.onDartRender'); }); - react.SyntheticMouseEvent event; + late react.SyntheticMouseEvent event; final divRef = react.createRef(); render(react.div({ 'ref': divRef, @@ -298,7 +295,7 @@ void domEventHandlerWrappingTests(ReactComponentFactoryProxy factory) { }); test('event has .persist and .isPersistent methods that can be called', () { - react.SyntheticMouseEvent actualEvent; + late react.SyntheticMouseEvent actualEvent; final nodeWithClickHandler = renderAndGetRootNode(factory({ 'onClick': (react.SyntheticMouseEvent event) { @@ -321,7 +318,7 @@ void domEventHandlerWrappingTests(ReactComponentFactoryProxy factory) { test('(simulated event)', () { final testZone = Zone.current; - Element nodeWithClickHandler; + late Element nodeWithClickHandler; // Run the ReactElement creation and rendering in a separate zone to // ensure the component lifecycle isn't run in the testZone, which could // create false positives in the `expect`. @@ -339,7 +336,7 @@ void domEventHandlerWrappingTests(ReactComponentFactoryProxy factory) { test('(native event)', () { final testZone = Zone.current; - Element nodeWithClickHandler; + late Element nodeWithClickHandler; // Run the ReactElement creation and rendering in a separate zone to // ensure the component lifecycle isn't run in the testZone, which could // create false positives in the `expect`. @@ -367,8 +364,8 @@ void domEventHandlerWrappingTests(ReactComponentFactoryProxy factory) { /// It will be called with the actual ref value for JS refs, (e.g., JS ref objects as opposed to Dart objects) void refTests( ReactComponentFactoryProxy factory, { - @required void Function(dynamic refValue) verifyRefValue, - void Function(dynamic refValue) verifyJsRefValue, + required void Function(dynamic refValue) verifyRefValue, + void Function(dynamic refValue)? verifyJsRefValue, }) { if (T == dynamic) { throw ArgumentError('Generic parameter T must be specified'); @@ -397,7 +394,7 @@ void refTests( rtu.renderIntoDocument(factory({ 'ref': testCase.ref, })); - final verifyFunction = testCase.isJs ? verifyJsRefValue : verifyRefValue; + final verifyFunction = testCase.isJs ? verifyJsRefValue! : verifyRefValue; verifyFunction(testCase.getCurrent()); }); } @@ -405,7 +402,7 @@ void refTests( test('string refs', () { final renderedInstance = _renderWithStringRefSupportingOwner(() => factory({'ref': 'test'})); - verifyRefValue(renderedInstance.dartComponent.ref('test')); + verifyRefValue(renderedInstance.dartComponent!.ref('test')); }); }); @@ -420,7 +417,7 @@ void refTests( rtu.renderIntoDocument(ForwardRefTestComponent({ 'ref': testCase.ref, })); - final verifyFunction = testCase.isJs ? verifyJsRefValue : verifyRefValue; + final verifyFunction = testCase.isJs ? verifyJsRefValue! : verifyRefValue; verifyFunction(testCase.getCurrent()); }); } @@ -441,7 +438,7 @@ void refTests( })); for (final testCase in testCases) { - final verifyFunction = testCase.isJs ? verifyJsRefValue : verifyRefValue; + final verifyFunction = testCase.isJs ? verifyJsRefValue! : verifyRefValue; final valueToVerify = testCase.isJs ? refSpy.jsRef.current : refSpy.current; // Test setup check: verify refValue is correct, @@ -474,7 +471,7 @@ void refTests( })); for (final testCase in testCases) { - final verifyFunction = testCase.isJs ? verifyJsRefValue : verifyRefValue; + final verifyFunction = testCase.isJs ? verifyJsRefValue! : verifyRefValue; final valueToVerify = testCase.isJs ? refSpy.jsRef.current : refSpy.current; // Test setup check: verify refValue is correct, @@ -505,7 +502,7 @@ void refTests( 'ref': testCase.ref, })); - final verifyFunction = testCase.isJs ? verifyJsRefValue : verifyRefValue; + final verifyFunction = testCase.isJs ? verifyJsRefValue! : verifyRefValue; final valueToVerify = testCase.isJs ? refSpy.jsRef.current : refSpy.current; // Test setup check: verify refValue is correct, @@ -522,11 +519,11 @@ void refTests( } void _childKeyWarningTests(ReactComponentFactoryProxy factory, - {Function(ReactElement Function()) renderWithUniqueOwnerName}) { + {required Function(ReactElement Function()) renderWithUniqueOwnerName}) { group('key/children validation', () { - bool consoleErrorCalled; + late bool consoleErrorCalled; var consoleErrorMessage; - JsFunction originalConsoleError; + late JsFunction originalConsoleError; setUp(() { consoleErrorCalled = false; diff --git a/test/hooks_test.dart b/test/hooks_test.dart index caf593fb..8394afdb 100644 --- a/test/hooks_test.dart +++ b/test/hooks_test.dart @@ -18,16 +18,15 @@ import 'factory/common_factory_tests.dart'; main() { group('React Hooks: ', () { group('useState -', () { - ReactDartFunctionComponentFactoryProxy UseStateTest; - DivElement textRef; - DivElement countRef; - ButtonElement setButtonRef; - ButtonElement setWithUpdaterButtonRef; + DivElement? textRef; + DivElement? countRef; + ButtonElement? setButtonRef; + ButtonElement? setWithUpdaterButtonRef; setUpAll(() { final mountNode = DivElement(); - UseStateTest = react.registerFunctionComponent((props) { + final UseStateTest = react.registerFunctionComponent((props) { final text = useStateLazy(() { return 'initialValue'; }); @@ -36,14 +35,14 @@ main() { return react.div({}, [ react.div({ 'ref': (ref) { - textRef = ref as DivElement; + textRef = ref as DivElement?; }, }, [ text.value ]), react.div({ 'ref': (ref) { - countRef = ref as DivElement; + countRef = ref as DivElement?; }, }, [ count.value @@ -51,7 +50,7 @@ main() { react.button({ 'onClick': (_) => text.set('newValue'), 'ref': (ref) { - setButtonRef = ref as ButtonElement; + setButtonRef = ref as ButtonElement?; }, }, [ 'Set' @@ -59,7 +58,7 @@ main() { react.button({ 'onClick': (_) => count.setWithUpdater((prev) => prev + 1), 'ref': (ref) { - setWithUpdaterButtonRef = ref as ButtonElement; + setWithUpdaterButtonRef = ref as ButtonElement?; }, }, [ '+' @@ -71,21 +70,21 @@ main() { }); test('initializes state correctly', () { - expect(countRef.text, '0'); + expect(countRef!.text, '0'); }); test('Lazy initializes state correctly', () { - expect(textRef.text, 'initialValue'); + expect(textRef!.text, 'initialValue'); }); test('StateHook.set updates state correctly', () { react_test_utils.Simulate.click(setButtonRef); - expect(textRef.text, 'newValue'); + expect(textRef!.text, 'newValue'); }); test('StateHook.setWithUpdater updates state correctly', () { react_test_utils.Simulate.click(setWithUpdaterButtonRef); - expect(countRef.text, '1'); + expect(countRef!.text, '1'); }); }); @@ -94,12 +93,11 @@ main() { }); group('useReducer -', () { - ReactDartFunctionComponentFactoryProxy UseReducerTest; - DivElement textRef; - DivElement countRef; - ButtonElement addButtonRef; - ButtonElement subtractButtonRef; - ButtonElement textButtonRef; + DivElement? textRef; + DivElement? countRef; + ButtonElement? addButtonRef; + ButtonElement? subtractButtonRef; + ButtonElement? textButtonRef; Map reducer(Map state, Map action) { switch (action['type'] as String) { @@ -117,7 +115,7 @@ main() { setUpAll(() { final mountNode = DivElement(); - UseReducerTest = react.registerFunctionComponent((props) { + final UseReducerTest = react.registerFunctionComponent((props) { final state = useReducer(reducer, { 'text': 'initialValue', 'count': 0, @@ -126,14 +124,14 @@ main() { return react.div({}, [ react.div({ 'ref': (ref) { - textRef = ref as DivElement; + textRef = ref as DivElement?; }, }, [ state.state['text'] ]), react.div({ 'ref': (ref) { - countRef = ref as DivElement; + countRef = ref as DivElement?; }, }, [ state.state['count'] @@ -141,7 +139,7 @@ main() { react.button({ 'onClick': (_) => state.dispatch({'type': 'changeText', 'newText': 'newValue'}), 'ref': (ref) { - textButtonRef = ref as ButtonElement; + textButtonRef = ref as ButtonElement?; }, }, [ 'Set' @@ -149,7 +147,7 @@ main() { react.button({ 'onClick': (_) => state.dispatch({'type': 'increment'}), 'ref': (ref) { - addButtonRef = ref as ButtonElement; + addButtonRef = ref as ButtonElement?; }, }, [ '+' @@ -157,7 +155,7 @@ main() { react.button({ 'onClick': (_) => state.dispatch({'type': 'decrement'}), 'ref': (ref) { - subtractButtonRef = ref as ButtonElement; + subtractButtonRef = ref as ButtonElement?; }, }, [ '-' @@ -168,123 +166,117 @@ main() { react_dom.render(UseReducerTest({}), mountNode); }); - tearDownAll(() { - UseReducerTest = null; - }); - test('initializes state correctly', () { - expect(countRef.text, '0'); - expect(textRef.text, 'initialValue'); + expect(countRef!.text, '0'); + expect(textRef!.text, 'initialValue'); }); test('dispatch updates states correctly', () { react_test_utils.Simulate.click(textButtonRef); - expect(textRef.text, 'newValue'); + expect(textRef!.text, 'newValue'); react_test_utils.Simulate.click(addButtonRef); - expect(countRef.text, '1'); + expect(countRef!.text, '1'); react_test_utils.Simulate.click(subtractButtonRef); - expect(countRef.text, '0'); + expect(countRef!.text, '0'); }); + }); - group('useReducerLazy', () { - ButtonElement resetButtonRef; + group('useReducerLazy', () { + DivElement? countRef; + ButtonElement? addButtonRef; + ButtonElement? subtractButtonRef; + ButtonElement? resetButtonRef; - Map initializeCount(int initialValue) { - return {'count': initialValue}; - } + Map initializeCount(int initialValue) { + return {'count': initialValue}; + } - Map reducer2(Map state, Map action) { - switch (action['type'] as String) { - case 'increment': - return {...state, 'count': state['count'] + 1}; - case 'decrement': - return {...state, 'count': state['count'] - 1}; - case 'reset': - return initializeCount(action['payload'] as int); - default: - return state; - } + Map reducer2(Map state, Map action) { + switch (action['type'] as String) { + case 'increment': + return {...state, 'count': state['count'] + 1}; + case 'decrement': + return {...state, 'count': state['count'] - 1}; + case 'reset': + return initializeCount(action['payload'] as int); + default: + return state; } + } - setUpAll(() { - final mountNode = DivElement(); + setUpAll(() { + final mountNode = DivElement(); - UseReducerTest = react.registerFunctionComponent((props) { - final state = useReducerLazy(reducer2, props['initialCount'] as int, initializeCount); + final UseReducerTest = react.registerFunctionComponent((props) { + final state = useReducerLazy(reducer2, props['initialCount'] as int, initializeCount); - return react.div({}, [ - react.div({ - 'ref': (ref) { - countRef = ref as DivElement; - }, - }, [ - state.state['count'] - ]), - react.button({ - 'onClick': (_) => state.dispatch({'type': 'reset', 'payload': props['initialCount']}), - 'ref': (ref) { - resetButtonRef = ref as ButtonElement; - }, - }, [ - 'reset' - ]), - react.button({ - 'onClick': (_) => state.dispatch({'type': 'increment'}), - 'ref': (ref) { - addButtonRef = ref as ButtonElement; - }, - }, [ - '+' - ]), - react.button({ - 'onClick': (_) => state.dispatch({'type': 'decrement'}), - 'ref': (ref) { - subtractButtonRef = ref as ButtonElement; - }, - }, [ - '-' - ]), - ]); - }); - - react_dom.render(UseReducerTest({'initialCount': 10}), mountNode); + return react.div({}, [ + react.div({ + 'ref': (ref) { + countRef = ref as DivElement?; + }, + }, [ + state.state['count'] + ]), + react.button({ + 'onClick': (_) => state.dispatch({'type': 'reset', 'payload': props['initialCount']}), + 'ref': (ref) { + resetButtonRef = ref as ButtonElement?; + }, + }, [ + 'reset' + ]), + react.button({ + 'onClick': (_) => state.dispatch({'type': 'increment'}), + 'ref': (ref) { + addButtonRef = ref as ButtonElement?; + }, + }, [ + '+' + ]), + react.button({ + 'onClick': (_) => state.dispatch({'type': 'decrement'}), + 'ref': (ref) { + subtractButtonRef = ref as ButtonElement?; + }, + }, [ + '-' + ]), + ]); }); - tearDownAll(() { - UseReducerTest = null; - }); + react_dom.render(UseReducerTest({'initialCount': 10}), mountNode); + }); - test('initializes state correctly', () { - expect(countRef.text, '10'); - }); + test('initializes state correctly', () { + expect(countRef!.text, '10'); + }); - test('dispatch updates states correctly', () { - react_test_utils.Simulate.click(addButtonRef); - expect(countRef.text, '11'); + test('dispatch updates states correctly', () { + react_test_utils.Simulate.click(addButtonRef); + expect(countRef!.text, '11'); - react_test_utils.Simulate.click(resetButtonRef); - expect(countRef.text, '10'); + react_test_utils.Simulate.click(resetButtonRef); + expect(countRef!.text, '10'); - react_test_utils.Simulate.click(subtractButtonRef); - expect(countRef.text, '9'); - }); + react_test_utils.Simulate.click(subtractButtonRef); + expect(countRef!.text, '9'); }); }); group('useCallback -', () { - ReactDartFunctionComponentFactoryProxy UseCallbackTest; - DivElement deltaRef; - DivElement countRef; - ButtonElement incrementWithDepButtonRef; - ButtonElement incrementNoDepButtonRef; - ButtonElement incrementDeltaButtonRef; + DivElement? deltaRef; + DivElement? countRef; + ButtonElement? incrementWithDepButtonRef; + ButtonElement? incrementNoDepButtonRef; + ButtonElement? incrementDeltaButtonRef; setUpAll(() { final mountNode = DivElement(); - UseCallbackTest = react.registerFunctionComponent((props) { + final UseCallbackTest = react.registerFunctionComponent((props) { final count = useState(0); final delta = useState(1); @@ -303,14 +295,14 @@ main() { return react.div({}, [ react.div({ 'ref': (ref) { - deltaRef = ref as DivElement; + deltaRef = ref as DivElement?; }, }, [ delta.value ]), react.div({ 'ref': (ref) { - countRef = ref as DivElement; + countRef = ref as DivElement?; }, }, [ count.value @@ -318,7 +310,7 @@ main() { react.button({ 'onClick': incrementNoDep, 'ref': (ref) { - incrementNoDepButtonRef = ref as ButtonElement; + incrementNoDepButtonRef = ref as ButtonElement?; }, }, [ 'Increment count no dep' @@ -326,7 +318,7 @@ main() { react.button({ 'onClick': incrementWithDep, 'ref': (ref) { - incrementWithDepButtonRef = ref as ButtonElement; + incrementWithDepButtonRef = ref as ButtonElement?; }, }, [ 'Increment count' @@ -334,7 +326,7 @@ main() { react.button({ 'onClick': incrementDelta, 'ref': (ref) { - incrementDeltaButtonRef = ref as ButtonElement; + incrementDeltaButtonRef = ref as ButtonElement?; }, }, [ 'Increment delta' @@ -346,14 +338,14 @@ main() { }); test('callback is called correctly', () { - expect(countRef.text, '0'); - expect(deltaRef.text, '1'); + expect(countRef!.text, '0'); + expect(deltaRef!.text, '1'); react_test_utils.Simulate.click(incrementNoDepButtonRef); - expect(countRef.text, '1'); + expect(countRef!.text, '1'); react_test_utils.Simulate.click(incrementWithDepButtonRef); - expect(countRef.text, '2'); + expect(countRef!.text, '2'); }); group('after depending state changes,', () { @@ -363,25 +355,26 @@ main() { test('callback stays the same if state not in dependency list', () { react_test_utils.Simulate.click(incrementNoDepButtonRef); - expect(countRef.text, '3', reason: 'still increments by 1 because delta not in dependency list'); + expect(countRef!.text, '3', reason: 'still increments by 1 because delta not in dependency list'); }); test('callback updates if state is in dependency list', () { react_test_utils.Simulate.click(incrementWithDepButtonRef); - expect(countRef.text, '5', reason: 'increments by 2 because delta updated'); + expect(countRef!.text, '5', reason: 'increments by 2 because delta updated'); }); }); }); group('useContext -', () { - DivElement mountNode; - _ContextProviderWrapper providerRef; - var currentCount = 0; - Context testContext; + late DivElement mountNode; + _ContextProviderWrapper? providerRef; + late int currentCount; + late Context testContext; Function useContextTestFunctionComponent; setUp(() { mountNode = DivElement(); + currentCount = 0; UseContextTestComponent(Map props) { final context = useContext(testContext); @@ -402,7 +395,7 @@ main() { 'contextToUse': testContext, 'mode': 'increment', 'ref': (ref) { - providerRef = ref as _ContextProviderWrapper; + providerRef = ref as _ContextProviderWrapper?; } }, [ useContextTestFunctionComponent({'key': 't1'}, []), @@ -410,44 +403,36 @@ main() { mountNode); }); - tearDown(() { - react_dom.unmountComponentAtNode(mountNode); - mountNode = null; - currentCount = 0; - testContext = null; - useContextTestFunctionComponent = null; - providerRef = null; - }); - group('updates with the correct values', () { test('on first render', () { expect(currentCount, 1); }); test('on value updates', () { - providerRef.increment(); + providerRef!.increment(); expect(currentCount, 2); - providerRef.increment(); + providerRef!.increment(); expect(currentCount, 3); - providerRef.increment(); + providerRef!.increment(); expect(currentCount, 4); }); }); }); group('useRef -', () { - final mountNode = DivElement(); - ReactDartFunctionComponentFactoryProxy UseRefTest; - ButtonElement reRenderButton; - var noInitRef; - var initRef; - var domElementRef; - StateHook renderIndex; - var refFromUseRef; - var refFromCreateRef; + late DivElement mountNode; + ButtonElement? reRenderButton; + late Ref noInitRef; + late Ref initRef; + late Ref domElementRef; + late StateHook renderIndex; + late Ref refFromUseRef; + late Ref refFromCreateRef; setUpAll(() { - UseRefTest = react.registerFunctionComponent((props) { + mountNode = DivElement(); + + final UseRefTest = react.registerFunctionComponent((props) { noInitRef = useRef(); initRef = useRef(mountNode); domElementRef = useRef(); @@ -466,7 +451,7 @@ main() { react.p({}, [refFromUseRef.current]), react.p({}, [refFromCreateRef.current]), react.button({ - 'ref': (ref) => reRenderButton = ref as ButtonElement, + 'ref': (ref) => reRenderButton = ref as ButtonElement?, 'onClick': (_) => renderIndex.setWithUpdater((prev) => prev + 1) }, [ 're-render' @@ -511,10 +496,9 @@ main() { }); group('useMemo -', () { - ReactDartFunctionComponentFactoryProxy UseMemoTest; - StateHook count; - ButtonElement reRenderButtonRef; - ButtonElement incrementButtonRef; + late StateHook count; + ButtonElement? reRenderButtonRef; + ButtonElement? incrementButtonRef; // Count how many times createFunction() is called for each variation of dependencies. var createFunctionCallCountWithDeps = 0; @@ -522,9 +506,9 @@ main() { var createFunctionCallCountEmptyDeps = 0; // Keeps track of return value of useMemo() for each variation of dependencies. - int returnValueWithDeps; - int returnValueNoDeps; - int returnValueEmptyDeps; + late int returnValueWithDeps; + late int returnValueNoDeps; + late int returnValueEmptyDeps; int fibonacci(int n) { if (n <= 1) { @@ -536,7 +520,7 @@ main() { setUpAll(() { final mountNode = DivElement(); - UseMemoTest = react.registerFunctionComponent((props) { + final UseMemoTest = react.registerFunctionComponent((props) { final reRender = useState(0); count = useState(5); @@ -565,13 +549,13 @@ main() { return react.Fragment({}, [ react.button({ - 'ref': (ref) => incrementButtonRef = ref as ButtonElement, + 'ref': (ref) => incrementButtonRef = ref as ButtonElement?, 'onClick': (_) => count.setWithUpdater((prev) => prev + 1), }, [ '+' ]), react.button({ - 'ref': (ref) => reRenderButtonRef = ref as ButtonElement, + 'ref': (ref) => reRenderButtonRef = ref as ButtonElement?, 'onClick': (_) => reRender.setWithUpdater((prev) => prev + 1) }, [ 're-render' @@ -643,17 +627,17 @@ main() { group('useImperativeHandle -', () { group('updates `ref.current` to the return value of `createHandle()`', () { - final mountNode = DivElement(); - ReactDartFunctionComponentFactoryProxy UseImperativeHandleTest; - ButtonElement incrementButton; - ButtonElement reRenderButtonRef1; - ButtonElement reRenderButtonRef2; - Ref noDepsRef; - Ref emptyDepsRef; - Ref depsRef; - StateHook count; + ButtonElement? incrementButton; + ButtonElement? reRenderButtonRef1; + ButtonElement? reRenderButtonRef2; + late Ref noDepsRef; + late Ref emptyDepsRef; + late Ref depsRef; + late StateHook count; setUpAll(() { + final mountNode = DivElement(); + final NoDepsComponent = react.forwardRef2((props, ref) { count = useState(0); @@ -673,7 +657,7 @@ main() { return react.Fragment({}, [ react.div({'ref': ref}, count.value), react.button({ - 'ref': (ref) => reRenderButtonRef1 = ref as ButtonElement, + 'ref': (ref) => reRenderButtonRef1 = ref as ButtonElement?, 'onClick': (_) => count.setWithUpdater((prev) => prev + 1), }, []), ]); @@ -687,7 +671,7 @@ main() { return react.Fragment({}, [ react.div({'ref': ref}, count.value), react.button({ - 'ref': (ref) => reRenderButtonRef2 = ref as ButtonElement, + 'ref': (ref) => reRenderButtonRef2 = ref as ButtonElement?, 'onClick': (_) => count.setWithUpdater((prev) => prev + 1), }, []), ]); @@ -702,7 +686,7 @@ main() { return react.Fragment({}, [ react.div({'ref': someRefThatIsNotSet}, count.value), react.button({ - 'ref': (ref) => reRenderButtonRef2 = ref as ButtonElement, + 'ref': (ref) => reRenderButtonRef2 = ref as ButtonElement?, 'onClick': (_) => count.setWithUpdater((prev) => prev + 1), }, []), ]); @@ -712,7 +696,7 @@ main() { reason: 'Hook should not throw if the ref is null'); react_dom.unmountComponentAtNode(mountNode); - UseImperativeHandleTest = react.registerFunctionComponent((props) { + final UseImperativeHandleTest = react.registerFunctionComponent((props) { noDepsRef = useRef(); emptyDepsRef = useRef(); depsRef = useRef(); @@ -720,7 +704,7 @@ main() { return react.Fragment({}, [ NoDepsComponent({'ref': noDepsRef}, []), react.button({ - 'ref': (ref) => incrementButton = ref as ButtonElement, + 'ref': (ref) => incrementButton = ref as ButtonElement?, 'onClick': (_) => noDepsRef.current['increment'](), }, [ '+' @@ -775,10 +759,8 @@ main() { }); group('useDebugValue -', () { - ReactDartFunctionComponentFactoryProxy UseDebugValueTest1; - ReactDartFunctionComponentFactoryProxy UseDebugValueTest2; - StateHook isOnline1; - StateHook isOnline2; + late StateHook isOnline1; + late StateHook isOnline2; StateHook useFriendStatus() { final isOnline = useState(false); @@ -799,7 +781,7 @@ main() { setUpAll(() { final mountNode = DivElement(); - UseDebugValueTest1 = react.registerFunctionComponent((props) { + final UseDebugValueTest1 = react.registerFunctionComponent((props) { isOnline1 = useFriendStatus(); return react.li({ @@ -809,7 +791,7 @@ main() { ]); }); - UseDebugValueTest2 = react.registerFunctionComponent((props) { + final UseDebugValueTest2 = react.registerFunctionComponent((props) { isOnline2 = useFriendStatusWithFormatFunction(); return react.li({ @@ -867,19 +849,19 @@ class _ContextProviderWrapper extends react.Component2 { } } -void testEffectHook(Function effectHook) { - ReactDartFunctionComponentFactoryProxy UseEffectTest; - ButtonElement countButtonRef; - DivElement countRef; - DivElement mountNode; - int useEffectCallCount; - int useEffectCleanupCallCount; - int useEffectWithDepsCallCount; - int useEffectCleanupWithDepsCallCount; - int useEffectWithDepsCallCount2; - int useEffectCleanupWithDepsCallCount2; - int useEffectWithEmptyDepsCallCount; - int useEffectCleanupWithEmptyDepsCallCount; +void testEffectHook(void Function(dynamic Function() sideEffect, [List? dependencies]) effectHook) { + late ReactDartFunctionComponentFactoryProxy UseEffectTest; + ButtonElement? countButtonRef; + DivElement? countRef; + late DivElement mountNode; + late int useEffectCallCount; + late int useEffectCleanupCallCount; + late int useEffectWithDepsCallCount; + late int useEffectCleanupWithDepsCallCount; + late int useEffectWithDepsCallCount2; + late int useEffectCleanupWithDepsCallCount2; + late int useEffectWithEmptyDepsCallCount; + late int useEffectCleanupWithEmptyDepsCallCount; setUpAll(() { mountNode = DivElement(); @@ -927,7 +909,7 @@ void testEffectHook(Function effectHook) { return react.div({}, [ react.div({ 'ref': (ref) { - countRef = ref as DivElement; + countRef = ref as DivElement?; }, }, [ count.value @@ -937,7 +919,7 @@ void testEffectHook(Function effectHook) { count.set(count.value + 1); }, 'ref': (ref) { - countButtonRef = ref as ButtonElement; + countButtonRef = ref as ButtonElement?; }, }, [ '+' @@ -949,7 +931,7 @@ void testEffectHook(Function effectHook) { }); test('side effect (no dependency list) is called after the first render', () { - expect(countRef.text, '0'); + expect(countRef!.text, '0'); expect(useEffectCallCount, 1); expect(useEffectCleanupCallCount, 0, reason: 'component has not been unmounted or re-rendered'); @@ -974,7 +956,7 @@ void testEffectHook(Function effectHook) { }); test('side effect (no dependency list) is called again', () { - expect(countRef.text, '1'); + expect(countRef!.text, '1'); expect(useEffectCallCount, 2); expect(useEffectCleanupCallCount, 1, reason: 'cleanup called before re-render'); diff --git a/test/js_builds/shared_tests.dart b/test/js_builds/shared_tests.dart index 72d57583..b9b96cba 100644 --- a/test/js_builds/shared_tests.dart +++ b/test/js_builds/shared_tests.dart @@ -6,7 +6,6 @@ import 'dart:html'; import 'dart:js_util'; import 'package:js/js.dart'; -import 'package:meta/meta.dart'; import 'package:react/react.dart' as react; import 'package:react/react_dom.dart' as react_dom; import 'package:react/react_client/react_interop.dart'; @@ -45,15 +44,15 @@ void sharedJsFunctionTests() { /// The arguments corresponding to each call of `console.warn`, /// collected via a spy set up in console_spy_include_this_js_first.js @JS() -external List> get consoleWarnCalls; +external List> get consoleWarnCalls; @JS() -external set consoleWarnCalls(List value); +external set consoleWarnCalls(List value); /// Like window.console.warn, but allows more than one argument. @JS('console.warn') external void consoleWarn([a, b, c, d]); -void sharedConsoleWarnTests({@required bool expectDeduplicateSyntheticEventWarnings}) { +void sharedConsoleWarnTests({required bool expectDeduplicateSyntheticEventWarnings}) { group('console.warn wrapper (or lack thereof)', () { void clearConsoleWarnCalls() => consoleWarnCalls = []; @@ -148,7 +147,7 @@ void sharedConsoleWarnTests({@required bool expectDeduplicateSyntheticEventWarni void sharedErrorBoundaryComponentNameTests() { group('includes the Dart component displayName in error boundary errors for', () { - void expectRenderErrorWithComponentName(ReactElement element, {@required String expectedComponentName}) { + void expectRenderErrorWithComponentName(ReactElement element, {required String expectedComponentName}) { final capturedInfos = []; react_dom.render( _ErrorBoundary({ diff --git a/test/lifecycle_test.dart b/test/lifecycle_test.dart index 58ef358a..b3dd3c58 100644 --- a/test/lifecycle_test.dart +++ b/test/lifecycle_test.dart @@ -9,7 +9,6 @@ import 'dart:html'; import 'dart:js'; import 'package:js/js.dart'; -import 'package:meta/meta.dart'; import 'package:react/react.dart' as react; import 'package:react/react_client.dart'; import 'package:react/react_client/js_backed_map.dart'; @@ -60,15 +59,15 @@ main() { }); group('prevents concurrent modification of `_setStateCallbacks`', () { - LifecycleTestHelper component; + late LifecycleTestHelper component; const initialState = { 'initialState': 'initial', }; - int firstStateUpdateCalls; - int secondStateUpdateCalls; - Map initialProps; - Map newState1; - Map newState2; + late int firstStateUpdateCalls; + late int secondStateUpdateCalls; + late Map initialProps; + late Map newState1; + late Map newState2; setUp(() { firstStateUpdateCalls = 0; @@ -81,14 +80,6 @@ main() { component.lifecycleCalls.clear(); }); - tearDown(() { - component?.lifecycleCalls?.clear(); - component = null; - initialProps = null; - newState1 = null; - newState2 = null; - }); - test('when `replaceState` is called from within another `replaceState` callback', () { void handleSecondStateUpdate() { secondStateUpdateCalls++; @@ -131,10 +122,10 @@ main() { ); group('propTypes', () { - bool consoleErrorCalled; + late bool consoleErrorCalled; dynamic consoleErrorMessage; - JsFunction originalConsoleError; - DivElement mountNode; + late JsFunction originalConsoleError; + late DivElement mountNode; const expectedWarningPrefix = 'Warning: Failed prop type: Invalid argument(s): intProp should be int. '; setUp(() { @@ -191,7 +182,7 @@ main() { final matches = regExp.allMatches(consoleErrorMessage as String); expect(matches, hasLength(1), reason: 'Should have found a json structure in the error.'); final match = matches.elementAt(0); // => extract the first (and only) match - final errorArgs = json.decode(match.group(1)) as Map; + final errorArgs = json.decode(match.group(1)!) as Map; expect(errorArgs, { 'props': '{intProp: test, children: []}', 'propName': 'intProp', @@ -226,7 +217,7 @@ main() { matchCall('render', state: initialState), matchCall('getSnapshotBeforeUpdate', args: [expectedProps, initialState], state: initialState), matchCall('componentDidUpdate', args: [expectedProps, initialState, null], state: initialState), - ].where((matcher) => matcher != null).toList())); + ].toList())); }); group('componentDidUpdate receives the same value created in getSnapshotBeforeUpdate when snapshot is', () { @@ -282,7 +273,7 @@ main() { matchCall('getDerivedStateFromProps', args: [initialProps, {}]), matchCall('render', state: initialDerivedState), matchCall('componentDidMount', state: initialDerivedState), - ].where((matcher) => matcher != null).toList())); + ].toList())); component.lifecycleCalls.clear(); @@ -297,7 +288,7 @@ main() { matchCall('render', state: updatedDerivedState), matchCall('getSnapshotBeforeUpdate'), matchCall('componentDidUpdate', state: updatedDerivedState), - ].where((matcher) => matcher != null).toList())); + ].toList())); }); test('null, leaving the existing instance state value intact', () { @@ -328,7 +319,7 @@ main() { matchCall('render', state: initialState), matchCall('getSnapshotBeforeUpdate', args: [expectedProps, initialState], state: initialState), matchCall('componentDidUpdate', args: [expectedProps, initialState, null], state: initialState), - ].where((matcher) => matcher != null).toList())); + ].toList())); }); }); @@ -418,7 +409,7 @@ main() { } }); - LifecycleTestHelper component; + late LifecycleTestHelper component; expect( () => component = getDartComponent(render(components2.NoGetDerivedStateFromErrorLifecycleTest(initialProps))), returnsNormally, @@ -498,7 +489,7 @@ main() { // addition to DDC vs dart2js, the string 'created by' is checked // for. It is a commonality indicating that the stacktrace is // produced correctly. - expect(renderedNode.children[2].text.contains('Created By'), getComponent2ErrorInfo().contains('Created By'), + expect(renderedNode.children[2].text!.contains('Created By'), getComponent2ErrorInfo().contains('Created By'), reason: 'Check ' 'to make sure callstack is accessible'); expect(renderedNode.children[3].text, contains(getComponent2ErrorFromDerivedState())); @@ -517,14 +508,14 @@ main() { }); group('Function Component', () { - Element mountNode; + late Element mountNode; setUp(() { mountNode = DivElement(); }); group('', () { - ReactDartFunctionComponentFactoryProxy PropsTest; + late ReactDartFunctionComponentFactoryProxy PropsTest; setUpAll(() { PropsTest = react.registerFunctionComponent((props) { @@ -549,8 +540,8 @@ main() { }); group('recieves a JsBackedMap from the props argument', () { - var propTypeCheck; - ReactDartFunctionComponentFactoryProxy PropsArgTypeTest; + Object? propTypeCheck; + late ReactDartFunctionComponentFactoryProxy PropsArgTypeTest; setUp(() { propTypeCheck = null; @@ -570,19 +561,19 @@ main() { } void sharedLifecycleTests({ - @required bool skipLegacyContextTests, + required bool skipLegacyContextTests, // Need this generic to avoid function typing issues caused by using // the same factory for Component and Component2: // > Expected a value of type '() => Component2>', but got one of type '() => DefaultPropsCachingTestHelper' - @required T Function() defaultPropsCachingTestComponentFactory, - @required ReactDartComponentFactoryProxy SetStateTest, - @required ReactDartComponentFactoryProxy DefaultPropsTest, - @required ReactDartComponentFactoryProxy ContextWrapperWithoutKeys, - @required ReactDartComponentFactoryProxy ContextConsumerWrapper, - @required ReactDartComponentFactoryProxy ContextWrapper, - @required ReactDartComponentFactoryProxy LifecycleTestWithContext, - @required ReactDartComponentFactoryProxy LifecycleTest, - @required bool isComponent2, + required T Function() defaultPropsCachingTestComponentFactory, + required ReactDartComponentFactoryProxy SetStateTest, + required ReactDartComponentFactoryProxy DefaultPropsTest, + required ReactDartComponentFactoryProxy? ContextWrapperWithoutKeys, + required ReactDartComponentFactoryProxy? ContextConsumerWrapper, + required ReactDartComponentFactoryProxy? ContextWrapper, + required ReactDartComponentFactoryProxy LifecycleTestWithContext, + required ReactDartComponentFactoryProxy LifecycleTest, + required bool isComponent2, }) { group('(shared behavior)', () { group('default props', () { @@ -684,7 +675,7 @@ void sharedLifecycleTests({ test('does not call getChildContext when childContextKeys is empty', () { final mountNode = DivElement(); final instance = - react_dom.render(ContextWrapperWithoutKeys({'foo': false}, LifecycleTestWithContext({})), mountNode); + react_dom.render(ContextWrapperWithoutKeys!({'foo': false}, LifecycleTestWithContext({})), mountNode); final component = getDartComponent(instance); expect( @@ -699,7 +690,7 @@ void sharedLifecycleTests({ test('calls getChildContext when childContextKeys exist', () { final mountNode = DivElement(); - final instance = react_dom.render(ContextWrapper({'foo': false}, LifecycleTestWithContext({})), mountNode); + final instance = react_dom.render(ContextWrapper!({'foo': false}, LifecycleTestWithContext({})), mountNode); final component = getDartComponent(instance); expect( @@ -746,11 +737,11 @@ void sharedLifecycleTests({ // Render the initial instance final mountNode = DivElement(); - react_dom.render(ContextWrapper({'foo': false}, LifecycleTestWithContext(initialPropsWithRef)), mountNode); + react_dom.render(ContextWrapper!({'foo': false}, LifecycleTestWithContext(initialPropsWithRef)), mountNode); // Verify initial context/setup expect( - componentRef.current.lifecycleCalls, + componentRef.current!.lifecycleCalls, equals([ matchCall('getChildContext', props: anything, context: anything), matchCall('getInitialState', props: initialPropsWithDefaults, context: initialContext), @@ -760,14 +751,14 @@ void sharedLifecycleTests({ ])); // Clear the lifecycle calls for to not duplicate the initial calls below - componentRef.current.lifecycleCalls.clear(); + componentRef.current!.lifecycleCalls.clear(); // Trigger a re-render with new content react_dom.render(ContextWrapper({'foo': true}, LifecycleTestWithContext(newPropsWithRef)), mountNode); // Verify updated context/setup expect( - componentRef.current.lifecycleCalls, + componentRef.current!.lifecycleCalls, equals([ matchCall('getChildContext', props: anything, context: anything), matchCall('componentWillReceiveProps', @@ -818,7 +809,7 @@ void sharedLifecycleTests({ // Render the initial instance final mountNode = DivElement(); react_dom.render( - ContextWrapper( + ContextWrapper!( {'foo': false}, [ LifecycleTestWithContext(initialPropsWithRef), @@ -828,7 +819,7 @@ void sharedLifecycleTests({ // Verify initial context/setup expect( - componentRef.current.lifecycleCalls, + componentRef.current!.lifecycleCalls, equals([ matchCall('initialState', props: initialProps, context: initialContext), matchCall('getDerivedStateFromProps', args: [initialProps, expectedState]), @@ -837,7 +828,7 @@ void sharedLifecycleTests({ ])); // Clear the lifecycle calls for to not duplicate the initial calls below - componentRef.current.lifecycleCalls.clear(); + componentRef.current!.lifecycleCalls.clear(); // Trigger a re-render with new content react_dom.render( @@ -851,7 +842,7 @@ void sharedLifecycleTests({ // Verify updated context/setup expect( - componentRef.current.lifecycleCalls, + componentRef.current!.lifecycleCalls, equals([ matchCall('getDerivedStateFromProps', args: [initialProps, expectedState]), matchCall('render', props: initialProps, context: expectedContext), @@ -885,10 +876,10 @@ void sharedLifecycleTests({ // Render the initial instance final mountNode = DivElement(); react_dom.render( - ContextWrapper( + ContextWrapper!( initialContext, [ - ContextConsumerWrapper({}, [ + ContextConsumerWrapper!({}, [ (Map value) { return LifecycleTest(initialPropsWithRef..addAll(value)); } @@ -899,7 +890,7 @@ void sharedLifecycleTests({ // Verify initial context/setup expect( - componentRef.current.lifecycleCalls, + componentRef.current!.lifecycleCalls, equals([ matchCall('initialState', props: initialProps), matchCall('getDerivedStateFromProps'), @@ -908,7 +899,7 @@ void sharedLifecycleTests({ ])); // Clear the lifecycle calls for to not duplicate the initial calls below - componentRef.current.lifecycleCalls.clear(); + componentRef.current!.lifecycleCalls.clear(); // Trigger a re-render with new content3 react_dom.render( @@ -926,7 +917,7 @@ void sharedLifecycleTests({ // Verify updated context/setup expect( - componentRef.current.lifecycleCalls, + componentRef.current!.lifecycleCalls, equals([ matchCall('getDerivedStateFromProps', args: [expectedProps, expectedState]), matchCall('shouldComponentUpdate', args: [expectedProps, expectedState], props: initialProps), @@ -1014,7 +1005,7 @@ void sharedLifecycleTests({ matchCall('getDerivedStateFromProps', args: {expectedProps, initialState}), matchCall('render', props: expectedProps, state: initialState), matchCall('componentDidMount', props: expectedProps, state: initialState) - ].where((matcher) => matcher != null))); + ])); expect(component.state, isA()); }); } @@ -1031,12 +1022,12 @@ void sharedLifecycleTests({ 'newState': 'new', }; - Map initialProps; - Map expectedProps; - dynamic newContext; - void expectedSnapshot; - bool updatingStateWithNull; - LifecycleTestHelper component; + late Map initialProps; + late Map expectedProps; + Object? newContext; + Object? expectedSnapshot; + late bool updatingStateWithNull; + late LifecycleTestHelper component; setUp(() { initialProps = unmodifiableMap({'getInitialState': (_) => initialState, 'initialState': (_) => initialState}); @@ -1119,7 +1110,7 @@ void sharedLifecycleTests({ test('setStateWithUpdater argument that returns null (no re-render)', () { updatingStateWithNull = true; final calls = []; - Map stateUpdater(Map prevState, Map props) { + Map? stateUpdater(Map prevState, Map props) { calls.add({ 'name': 'stateUpdater', 'prevState': Map.from(prevState), @@ -1197,17 +1188,17 @@ void sharedLifecycleTests({ }); group('prevents concurrent modification of `_setStateCallbacks`', () { - LifecycleTestHelper component; + late LifecycleTestHelper component; const initialState = { 'initialState': 'initial', }; - int firstStateUpdateCalls; - int secondStateUpdateCalls; - Map initialProps; - Map newState1; - Map expectedState1; - Map newState2; - Map expectedState2; + late int firstStateUpdateCalls; + late int secondStateUpdateCalls; + late Map initialProps; + late Map newState1; + late Map expectedState1; + late Map newState2; + late Map expectedState2; setUp(() { firstStateUpdateCalls = 0; @@ -1226,16 +1217,6 @@ void sharedLifecycleTests({ component.lifecycleCalls.clear(); }); - tearDown(() { - component?.lifecycleCalls?.clear(); - component = null; - initialProps = null; - newState1 = null; - expectedState1 = null; - newState2 = null; - expectedState2 = null; - }); - test('when `setState` is called from within another `setState` callback', () { void handleSecondStateUpdate() { secondStateUpdateCalls++; @@ -1331,7 +1312,7 @@ void sharedLifecycleTests({ }); } - void testShouldUpdates({bool shouldComponentUpdateWithContext, bool shouldComponentUpdate}) { + void testShouldUpdates({required bool? shouldComponentUpdateWithContext, required bool shouldComponentUpdate}) { test('receives updated props with correct lifecycle calls and does not rerender', () { final dynamic expectedContext = isComponent2 ? null : const {}; final initialProps = unmodifiableMap({ @@ -1508,7 +1489,7 @@ void sharedLifecycleTests({ } group('calling setState', () { - LifecycleTestHelper component; + late LifecycleTestHelper component; setUp(() { final mountNode = DivElement(); @@ -1517,11 +1498,6 @@ void sharedLifecycleTests({ component.lifecycleCalls.clear(); }); - tearDown(() { - component?.lifecycleCalls?.clear(); - component = null; - }); - if (isComponent2) { test('does not update the component when the value passed is null', () { component.callSetStateWithNullValue(); diff --git a/test/lifecycle_test/component.dart b/test/lifecycle_test/component.dart index 0da20a45..9cd45347 100644 --- a/test/lifecycle_test/component.dart +++ b/test/lifecycle_test/component.dart @@ -191,7 +191,7 @@ class _LifecycleTest extends react.Component with LifecycleTestHelper { @override componentWillUpdateWithContext(nextProps, nextState, nextContext) => lifecycleCall('componentWillUpdateWithContext', - arguments: [Map.from(nextProps), Map.from(nextState), Map.from(nextContext)]); + arguments: [Map.from(nextProps), Map.from(nextState), Map.from(nextContext as Map)]); @override componentDidUpdate(prevProps, prevState) => diff --git a/test/lifecycle_test/component2.dart b/test/lifecycle_test/component2.dart index dcd9abbd..94cee4db 100644 --- a/test/lifecycle_test/component2.dart +++ b/test/lifecycle_test/component2.dart @@ -8,7 +8,7 @@ import 'package:react/react_client/react_interop.dart'; import 'util.dart'; -final SetStateTest = react.registerComponent2(() => _SetStateTest(), skipMethods: [null]); +final SetStateTest = react.registerComponent2(() => _SetStateTest(), skipMethods: []); final DefaultSkipMethodsTest = react.registerComponent2(() => _SetStateTest()); final SkipMethodsTest = react.registerComponent2(() => _SetStateTest(), skipMethods: ['getSnapshotBeforeUpdate']); diff --git a/test/lifecycle_test/util.dart b/test/lifecycle_test/util.dart index aa7a2692..120f3d0e 100644 --- a/test/lifecycle_test/util.dart +++ b/test/lifecycle_test/util.dart @@ -31,20 +31,36 @@ mixin LifecycleTestHelper on Component { return call['memberName']; }).toList(); - T lifecycleCall(String memberName, {List arguments = const [], T Function() defaultReturnValue, Map staticProps}) { + static bool _throwsLateInitializationError(void Function() accessLateVariable) { + try { + accessLateVariable(); + } catch (e) { + if (e.toString().contains('LateInitializationError')) { + return true; + } else { + rethrow; + } + } + + return false; + } + + T lifecycleCall(String memberName, + {List arguments = const [], T Function()? defaultReturnValue, Map? staticProps}) { + // If this is false and we access late variables, such as jsThis/props/state, we'll get a LateInitializationError + // fixme rethink this solution, since it's gross and also might not work in dart2js or mixed mode; perhaps conditionally don't try to access props/state in early lifecycle methods + final hasPropsInitialized = !_throwsLateInitializationError(() => props); + final hasStateInitialized = !_throwsLateInitializationError(() => state); + lifecycleCalls.add({ 'memberName': memberName, 'arguments': arguments, - 'props': props == null ? null : Map.from(props), - 'state': state == null ? null : Map.from(state), + 'props': hasPropsInitialized ? Map.from(props) : null, + 'state': hasStateInitialized ? Map.from(state) : null, 'context': context, }); - final lifecycleCallback = props == null - ? staticProps == null - ? null - : staticProps[memberName] - : props[memberName]; + final lifecycleCallback = (staticProps ?? (hasPropsInitialized ? props : {}))[memberName]; if (lifecycleCallback != null) { return Function.apply(lifecycleCallback as Function, [this, ...arguments]) as T; } @@ -53,7 +69,7 @@ mixin LifecycleTestHelper on Component { return defaultReturnValue(); } - return null; + return null as T; } void callSetStateWithNullValue() { diff --git a/test/react_client/bridge_test.dart b/test/react_client/bridge_test.dart index ce2945d0..17860214 100644 --- a/test/react_client/bridge_test.dart +++ b/test/react_client/bridge_test.dart @@ -51,7 +51,7 @@ class _Foo extends react.Component2 { render() => react.div({}); } -List customBridgeCalls; +List? customBridgeCalls; final BridgeTest = react.registerComponent2(() => _BridgeTest(), bridgeFactory: (component) { final returnedBridge = CustomComponent2Bridge(); diff --git a/test/react_client/event_helpers_test.dart b/test/react_client/event_helpers_test.dart index 7e5ca9a4..a10b1184 100644 --- a/test/react_client/event_helpers_test.dart +++ b/test/react_client/event_helpers_test.dart @@ -852,7 +852,7 @@ main() { expect(baseEvent.clientX, 100); expect(baseEvent.clientY, 200); expect(baseEvent.ctrlKey, isTrue); - expect(baseEvent.dataTransfer.dropEffect, testString); + expect(baseEvent.dataTransfer!.dropEffect, testString); expect(baseEvent.metaKey, isTrue); expect(baseEvent.pageX, 300); expect(baseEvent.pageY, 400); @@ -899,7 +899,7 @@ main() { expect(newEvent.clientX, 200); expect(newEvent.clientY, 300); expect(newEvent.ctrlKey, isFalse); - expect(newEvent.dataTransfer.dropEffect, updatedTestString); + expect(newEvent.dataTransfer!.dropEffect, updatedTestString); expect(newEvent.metaKey, isFalse); expect(newEvent.pageX, 400); expect(newEvent.pageY, 500); @@ -948,7 +948,7 @@ main() { expect(baseEvent.clientX, 100); expect(baseEvent.clientY, 200); expect(baseEvent.ctrlKey, isTrue); - expect(baseEvent.dataTransfer.dropEffect, testString); + expect(baseEvent.dataTransfer!.dropEffect, testString); expect(baseEvent.metaKey, isTrue); expect(baseEvent.pageX, 300); expect(baseEvent.pageY, 400); @@ -965,7 +965,7 @@ main() { expect(newEvent.clientX, 100); expect(newEvent.clientY, 200); expect(newEvent.ctrlKey, isTrue); - expect(newEvent.dataTransfer.dropEffect, testString); + expect(newEvent.dataTransfer!.dropEffect, testString); expect(newEvent.metaKey, isTrue); expect(newEvent.pageX, 300); expect(newEvent.pageY, 400); @@ -1688,10 +1688,6 @@ main() { currentEventTypeBeingTested == SyntheticEventType.syntheticFormEvent ? isTrue : isFalse, reason: 'The `SyntheticEvent` base class is considered a Form Event via Duck Typing.'); }); - - test('when the event is null', () { - expect(eventTypeTester(null), isFalse); - }); }); } @@ -1983,7 +1979,7 @@ main() { group('DataTransferHelper', () { group('dataTransfer', () { - SyntheticMouseEvent event; + SyntheticMouseEvent? event; tearDown(() { event = null; @@ -2012,11 +2008,11 @@ main() { final node = renderAndGetRootNode(); Simulate.drag(node, eventData); - final dataTransfer = event.dataTransfer; + final dataTransfer = event!.dataTransfer; expect(dataTransfer, isNotNull); - final fileNames = dataTransfer.files.map((file) => (file as File).name); + final fileNames = dataTransfer!.files.map((file) => (file as File).name); expect(fileNames, containsAll(['name1', 'name2', 'name3'])); expect(dataTransfer.types, containsAll(['d', 'e', 'f'])); @@ -2030,10 +2026,10 @@ main() { final node = renderAndGetRootNode(); Simulate.drag(node, eventData); - final dataTransfer = event.dataTransfer; + final dataTransfer = event!.dataTransfer; expect(dataTransfer, isNotNull); - expect(dataTransfer.files, isNotNull); + expect(dataTransfer!.files, isNotNull); expect(dataTransfer.files, isEmpty); expect(dataTransfer.types, isNotNull); expect(dataTransfer.types, isEmpty); @@ -2051,10 +2047,10 @@ main() { final node = renderAndGetRootNode(); Simulate.drag(node, eventData); - final dataTransfer = event.dataTransfer; + final dataTransfer = event!.dataTransfer; expect(dataTransfer, isNotNull); - expect(dataTransfer.files, isNotNull); + expect(dataTransfer!.files, isNotNull); expect(dataTransfer.files, isEmpty); expect(dataTransfer.types, isNotNull); expect(dataTransfer.types, isEmpty); diff --git a/test/react_client/js_backed_map_test.dart b/test/react_client/js_backed_map_test.dart index 1e38299d..cf10c4f4 100644 --- a/test/react_client/js_backed_map_test.dart +++ b/test/react_client/js_backed_map_test.dart @@ -109,7 +109,7 @@ main() { }); group('other map methods overridden or needed by MapBase', () { - JsBackedMap testMap; + late JsBackedMap testMap; setUp(() { testMap = JsBackedMap.from({ diff --git a/test/react_client/js_interop_helpers_test.dart b/test/react_client/js_interop_helpers_test.dart index 18b4d315..307564ee 100644 --- a/test/react_client/js_interop_helpers_test.dart +++ b/test/react_client/js_interop_helpers_test.dart @@ -71,7 +71,7 @@ main() { final array = jsifyAndAllowInterop(set); expect(array is List, isTrue); expect((array as List).length, equals(set.length)); - for (var i = 0; i < (array as List).length; i++) { + for (var i = 0; i < array.length; i++) { expect(set.contains(array[i]), isTrue); } }); @@ -82,7 +82,7 @@ main() { expect(jsMap is List, isFalse); expect(jsMap is Map, isFalse); for (final key in map.keys) { - expect(checkMap(jsMap, key, map[key]), isTrue); + expect(checkMap(jsMap, key, map[key]!), isTrue); } }); diff --git a/test/react_client/react_interop_test.dart b/test/react_client/react_interop_test.dart index bdecc2d7..fde5ba1d 100644 --- a/test/react_client/react_interop_test.dart +++ b/test/react_client/react_interop_test.dart @@ -10,7 +10,7 @@ import 'package:test/test.dart'; main() { group('ReactDom.createPortal', () { group('creates a portal ReactNode that renders correctly', () { - Element mountNode; + late Element mountNode; setUp(() { mountNode = DivElement(); diff --git a/test/react_client_test.dart b/test/react_client_test.dart index e3e3670c..97aed13a 100644 --- a/test/react_client_test.dart +++ b/test/react_client_test.dart @@ -129,8 +129,8 @@ main() { Function createHandler(eventKey) => (_) { print(eventKey); }; - Map originalHandlers; - Map props; + late Map originalHandlers; + late Map props; setUp(() { originalHandlers = {}; @@ -146,7 +146,8 @@ main() { final jsProps = unconvertJsProps(component); for (final key in knownEventKeys) { expect(jsProps[key], isNotNull, reason: 'JS event handler prop should not be null'); - expect(jsProps[key], anyOf(same(originalHandlers[key]), same(allowInterop(originalHandlers[key]))), + expect( + jsProps[key], anyOf(same(originalHandlers[key]), same(allowInterop(originalHandlers[key] as Function))), reason: 'JS event handler should be the original or original wrapped in allowInterop'); } }); @@ -156,7 +157,7 @@ main() { final jsProps = unconvertJsProps(component); for (final key in knownEventKeys) { expect(jsProps[key], isNotNull, reason: 'JS event handler prop should not be null'); - expect(jsProps[key], same(allowInterop(originalHandlers[key])), + expect(jsProps[key], same(allowInterop(originalHandlers[key] as Function)), reason: 'JS event handler prop was unexpectedly modified'); } }); diff --git a/test/react_component_test.dart b/test/react_component_test.dart index aee6d197..df04dbb9 100644 --- a/test/react_component_test.dart +++ b/test/react_component_test.dart @@ -6,7 +6,7 @@ import 'package:test/test.dart'; main() { group('Component2', () { - Component2 component; + late Component2 component; setUpAll(() { component = MyComponent(); @@ -23,22 +23,22 @@ main() { expect(() => component.componentWillMount(), throwsUnsupportedError); }); test('componentWillReceiveProps', () { - expect(() => component.componentWillReceiveProps(null), throwsUnsupportedError); + expect(() => component.componentWillReceiveProps({}), throwsUnsupportedError); }); test('componentWillUpdate', () { - expect(() => component.componentWillUpdate(null, null), throwsUnsupportedError); + expect(() => component.componentWillUpdate({}, {}), throwsUnsupportedError); }); test('getChildContext', () { expect(() => component.getChildContext(), throwsUnsupportedError); }); test('shouldComponentUpdateWithContext', () { - expect(() => component.shouldComponentUpdateWithContext(null, null, null), throwsUnsupportedError); + expect(() => component.shouldComponentUpdateWithContext({}, {}, null), throwsUnsupportedError); }); test('componentWillUpdateWithContext', () { - expect(() => component.componentWillUpdateWithContext(null, null, null), throwsUnsupportedError); + expect(() => component.componentWillUpdateWithContext({}, {}, null), throwsUnsupportedError); }); test('componentWillReceivePropsWithContext', () { - expect(() => component.componentWillReceivePropsWithContext(null, null), throwsUnsupportedError); + expect(() => component.componentWillReceivePropsWithContext({}, null), throwsUnsupportedError); }); }); diff --git a/test/react_context_test.dart b/test/react_context_test.dart index ba6ec791..965eb538 100644 --- a/test/react_context_test.dart +++ b/test/react_context_test.dart @@ -2,39 +2,34 @@ @JS() library react_test_utils_test; -import 'dart:html' as html; - import 'package:js/js.dart'; import 'package:test/test.dart'; import 'package:react/react.dart' as react; -import 'package:react/react_dom.dart' as react_dom; import 'shared_type_tester.dart'; +import 'util.dart'; main() { void testTypeValue(dynamic typeToTest) { - final mountNode = html.DivElement(); var contextTypeRef; var consumerRef; - react_dom.render( - ContextProviderWrapper({ - 'contextToUse': TestContext, - 'value': typeToTest, - }, [ - ContextTypeComponent({ - 'ref': (ref) { - contextTypeRef = ref; - } - }), - ContextConsumerWrapper({ - 'contextToUse': TestContext, - 'ref': (ref) { - consumerRef = ref; - } - }) - ]), - mountNode); + render(ContextProviderWrapper({ + 'contextToUse': TestContext, + 'value': typeToTest, + }, [ + ContextTypeComponent({ + 'ref': (ref) { + contextTypeRef = ref; + } + }), + ContextConsumerWrapper({ + 'contextToUse': TestContext, + 'ref': (ref) { + consumerRef = ref; + } + }) + ])); expect(contextTypeRef.context, same(typeToTest)); expect(consumerRef.latestValue, same(typeToTest)); } @@ -45,55 +40,52 @@ main() { }); group('calculateChangeBits argument functions correctly', () { - final mountNode = html.DivElement(); - _ContextProviderWrapper providerRef; - _ContextConsumerWrapper consumerEvenRef; - _ContextConsumerWrapper consumerOddRef; + _ContextProviderWrapper? providerRef; + _ContextConsumerWrapper? consumerEvenRef; + _ContextConsumerWrapper? consumerOddRef; setUp(() { - react_dom.render( - ContextProviderWrapper({ - 'contextToUse': TestCalculateChangedBitsContext, - 'mode': 'increment', - 'ref': (ref) { - providerRef = ref as _ContextProviderWrapper; - } - }, [ - ContextConsumerWrapper({ - 'key': 'EvenContextConsumer', - 'contextToUse': TestCalculateChangedBitsContext, - 'unstable_observedBits': 1 << 2, - 'ref': (ref) { - consumerEvenRef = ref as _ContextConsumerWrapper; - } - }), - ContextConsumerWrapper({ - 'key': 'OddContextConsumer', - 'contextToUse': TestCalculateChangedBitsContext, - 'unstable_observedBits': 1 << 3, - 'ref': (ref) { - consumerOddRef = ref as _ContextConsumerWrapper; - } - }) - ]), - mountNode); + render(ContextProviderWrapper({ + 'contextToUse': TestCalculateChangedBitsContext, + 'mode': 'increment', + 'ref': (ref) { + providerRef = ref as _ContextProviderWrapper?; + } + }, [ + ContextConsumerWrapper({ + 'key': 'EvenContextConsumer', + 'contextToUse': TestCalculateChangedBitsContext, + 'unstable_observedBits': 1 << 2, + 'ref': (ref) { + consumerEvenRef = ref as _ContextConsumerWrapper?; + } + }), + ContextConsumerWrapper({ + 'key': 'OddContextConsumer', + 'contextToUse': TestCalculateChangedBitsContext, + 'unstable_observedBits': 1 << 3, + 'ref': (ref) { + consumerOddRef = ref as _ContextConsumerWrapper?; + } + }) + ])); }); test('on first render', () { - expect(consumerEvenRef.latestValue, 1); - expect(consumerOddRef.latestValue, 1); + expect(consumerEvenRef!.latestValue, 1); + expect(consumerOddRef!.latestValue, 1); }); test('on value updates', () { - providerRef.increment(); - expect(consumerEvenRef.latestValue, 2); - expect(consumerOddRef.latestValue, 1); - providerRef.increment(); - expect(consumerEvenRef.latestValue, 2); - expect(consumerOddRef.latestValue, 3); - providerRef.increment(); - expect(consumerEvenRef.latestValue, 4); - expect(consumerOddRef.latestValue, 3); + providerRef!.increment(); + expect(consumerEvenRef!.latestValue, 2); + expect(consumerOddRef!.latestValue, 1); + providerRef!.increment(); + expect(consumerEvenRef!.latestValue, 2); + expect(consumerOddRef!.latestValue, 3); + providerRef!.increment(); + expect(consumerEvenRef!.latestValue, 4); + expect(consumerOddRef!.latestValue, 3); }); }); }); diff --git a/test/react_memo_test.dart b/test/react_memo_test.dart index 66184108..37f1f9f1 100644 --- a/test/react_memo_test.dart +++ b/test/react_memo_test.dart @@ -38,7 +38,7 @@ main() { sharedMemoTests(react.memo2); test('can be passed a forwardRef component (regression test)', () { - ReactComponentFactoryProxy factory; + late ReactComponentFactoryProxy factory; expect(() => factory = react.memo2(react.forwardRef2((props, ref) => 'foo')), returnsNormally); expect(() => rtu.renderIntoDocument(factory({})), returnsNormally); }); @@ -59,21 +59,17 @@ main() { } typedef MemoFunction = react.ReactComponentFactoryProxy Function(ReactDartFunctionComponentFactoryProxy, - {bool Function(Map, Map) areEqual}); + {bool Function(Map, Map)? areEqual}); void sharedMemoTests(MemoFunction memoFunction) { - Ref<_MemoTestWrapperComponent> memoTestWrapperComponentRef; - Ref localCountDisplayRef; - Ref valueMemoShouldIgnoreViaAreEqualDisplayRef; - int childMemoRenderCount; + late Ref<_MemoTestWrapperComponent> memoTestWrapperComponentRef; + late Ref localCountDisplayRef; + late Ref valueMemoShouldIgnoreViaAreEqualDisplayRef; + late int childMemoRenderCount; void renderMemoTest({ bool testAreEqual = false, }) { - expect(memoTestWrapperComponentRef, isNotNull, reason: 'test setup sanity check'); - expect(localCountDisplayRef, isNotNull, reason: 'test setup sanity check'); - expect(valueMemoShouldIgnoreViaAreEqualDisplayRef, isNotNull, reason: 'test setup sanity check'); - final customAreEqualFn = !testAreEqual ? null : (prevProps, nextProps) { @@ -102,12 +98,12 @@ void sharedMemoTests(MemoFunction memoFunction) { expect(localCountDisplayRef.current, isNotNull, reason: 'test setup sanity check'); expect(valueMemoShouldIgnoreViaAreEqualDisplayRef.current, isNotNull, reason: 'test setup sanity check'); - expect(memoTestWrapperComponentRef.current.redrawCount, 0, reason: 'test setup sanity check'); + expect(memoTestWrapperComponentRef.current!.redrawCount, 0, reason: 'test setup sanity check'); expect(childMemoRenderCount, 1, reason: 'test setup sanity check'); - expect(memoTestWrapperComponentRef.current.state['localCount'], 0, reason: 'test setup sanity check'); - expect(memoTestWrapperComponentRef.current.state['valueMemoShouldIgnoreViaAreEqual'], 0, + expect(memoTestWrapperComponentRef.current!.state['localCount'], 0, reason: 'test setup sanity check'); + expect(memoTestWrapperComponentRef.current!.state['valueMemoShouldIgnoreViaAreEqual'], 0, reason: 'test setup sanity check'); - expect(memoTestWrapperComponentRef.current.state['valueMemoShouldNotKnowAbout'], 0, + expect(memoTestWrapperComponentRef.current!.state['valueMemoShouldNotKnowAbout'], 0, reason: 'test setup sanity check'); } @@ -118,52 +114,46 @@ void sharedMemoTests(MemoFunction memoFunction) { childMemoRenderCount = 0; }); - tearDown(() { - memoTestWrapperComponentRef = null; - localCountDisplayRef = null; - valueMemoShouldIgnoreViaAreEqualDisplayRef = null; - }); - group('renders its child component when props change', () { test('', () { renderMemoTest(); - memoTestWrapperComponentRef.current.increaseLocalCount(); - expect(memoTestWrapperComponentRef.current.state['localCount'], 1, reason: 'test setup sanity check'); - expect(memoTestWrapperComponentRef.current.redrawCount, 1, reason: 'test setup sanity check'); + memoTestWrapperComponentRef.current!.increaseLocalCount(); + expect(memoTestWrapperComponentRef.current!.state['localCount'], 1, reason: 'test setup sanity check'); + expect(memoTestWrapperComponentRef.current!.redrawCount, 1, reason: 'test setup sanity check'); expect(childMemoRenderCount, 2); - expect(localCountDisplayRef.current.text, '1'); + expect(localCountDisplayRef.current!.text, '1'); - memoTestWrapperComponentRef.current.increaseValueMemoShouldIgnoreViaAreEqual(); - expect(memoTestWrapperComponentRef.current.state['valueMemoShouldIgnoreViaAreEqual'], 1, + memoTestWrapperComponentRef.current!.increaseValueMemoShouldIgnoreViaAreEqual(); + expect(memoTestWrapperComponentRef.current!.state['valueMemoShouldIgnoreViaAreEqual'], 1, reason: 'test setup sanity check'); - expect(memoTestWrapperComponentRef.current.redrawCount, 2, reason: 'test setup sanity check'); + expect(memoTestWrapperComponentRef.current!.redrawCount, 2, reason: 'test setup sanity check'); expect(childMemoRenderCount, 3); - expect(valueMemoShouldIgnoreViaAreEqualDisplayRef.current.text, '1'); + expect(valueMemoShouldIgnoreViaAreEqualDisplayRef.current!.text, '1'); }); test('unless the areEqual argument is set to a function that customizes when re-renders occur', () { renderMemoTest(testAreEqual: true); - memoTestWrapperComponentRef.current.increaseValueMemoShouldIgnoreViaAreEqual(); - expect(memoTestWrapperComponentRef.current.state['valueMemoShouldIgnoreViaAreEqual'], 1, + memoTestWrapperComponentRef.current!.increaseValueMemoShouldIgnoreViaAreEqual(); + expect(memoTestWrapperComponentRef.current!.state['valueMemoShouldIgnoreViaAreEqual'], 1, reason: 'test setup sanity check'); - expect(memoTestWrapperComponentRef.current.redrawCount, 1, reason: 'test setup sanity check'); + expect(memoTestWrapperComponentRef.current!.redrawCount, 1, reason: 'test setup sanity check'); expect(childMemoRenderCount, 1); - expect(valueMemoShouldIgnoreViaAreEqualDisplayRef.current.text, '0'); + expect(valueMemoShouldIgnoreViaAreEqualDisplayRef.current!.text, '0'); }); }); test('does not re-render its child component when parent updates and props remain the same', () { renderMemoTest(); - memoTestWrapperComponentRef.current.increaseValueMemoShouldNotKnowAbout(); - expect(memoTestWrapperComponentRef.current.state['valueMemoShouldNotKnowAbout'], 1, + memoTestWrapperComponentRef.current!.increaseValueMemoShouldNotKnowAbout(); + expect(memoTestWrapperComponentRef.current!.state['valueMemoShouldNotKnowAbout'], 1, reason: 'test setup sanity check'); - expect(memoTestWrapperComponentRef.current.redrawCount, 1, reason: 'test setup sanity check'); + expect(memoTestWrapperComponentRef.current!.redrawCount, 1, reason: 'test setup sanity check'); expect(childMemoRenderCount, 1); }); diff --git a/test/react_test_utils_test.dart b/test/react_test_utils_test.dart index bd51c28d..f3403d8c 100644 --- a/test/react_test_utils_test.dart +++ b/test/react_test_utils_test.dart @@ -34,21 +34,13 @@ void main() { testUtils({ bool isComponent2 = false, - ReactComponentFactoryProxy eventComponent, - ReactComponentFactoryProxy sampleComponent, - ReactComponentFactoryProxy wrapperComponent, + required ReactComponentFactoryProxy eventComponent, + required ReactComponentFactoryProxy sampleComponent, + required ReactComponentFactoryProxy wrapperComponent, }) { - var component; - Element domNode; - - tearDown(() { - component = null; - domNode = null; - }); - group('Shallow Rendering with a Component${isComponent2 ? "2" : ""}', () { - ReactElement content; - ReactShallowRenderer shallowRenderer; + late ReactElement content; + late ReactShallowRenderer shallowRenderer; setUp(() { content = sampleComponent({'className': 'test', 'id': 'createRendererTest'}); @@ -74,6 +66,9 @@ testUtils({ }); group('Simulate on a Component${isComponent2 ? "2" : ""}', () { + late Object component; + late Element domNode; + setUp(() { component = renderIntoDocument(eventComponent({})); domNode = findDomNode(component); @@ -87,8 +82,8 @@ testUtils({ ) { final eventHandlerName = 'on${eventName[0].toUpperCase() + eventName.substring(1)}'; eventName = eventName.toLowerCase(); - Map eventData; - int fakeTimeStamp; + late Map eventData; + late int fakeTimeStamp; setUp(() { fakeTimeStamp = eventName.hashCode; @@ -107,27 +102,26 @@ testUtils({ expect(domNode.text, equals('$eventName $fakeTimeStamp')); }); - if (expectEventType != null) { - test('with correct type', () { - SyntheticEvent capturedEvent; - final ref = createRef(); - - renderIntoDocument(div({ - eventHandlerName: (SyntheticEvent e) { - capturedEvent = e; - }, - 'ref': ref, - })); - - event(ref.current, eventData); - expectEventType(capturedEvent); - }); - } + test('with correct type', () { + SyntheticEvent? capturedEvent; + final ref = createRef(); + + renderIntoDocument(div({ + eventHandlerName: (SyntheticEvent e) { + capturedEvent = e; + }, + 'ref': ref, + })); + + event(ref.current, eventData); + expect(capturedEvent, isNotNull); + expectEventType(capturedEvent!); + }); } group('event', () { void Function(SyntheticEvent) _expectEventType(SyntheticEventType type) { - return (SyntheticEvent event) { + return (event) { expect(event.isClipboardEvent, type == SyntheticEventType.syntheticClipboardEvent); expect(event.isKeyboardEvent, type == SyntheticEventType.syntheticKeyboardEvent); expect(event.isCompositionEvent, type == SyntheticEventType.syntheticCompositionEvent); @@ -213,7 +207,7 @@ testUtils({ test('passes in and jsifies eventData properly', () { const testKeyCode = 42; - String callInfo; + String? callInfo; var wasStopPropagationCalled = false; final renderedNode = renderIntoDocument(div({ @@ -236,34 +230,34 @@ testUtils({ }); test('findRenderedDOMComponentWithClass on a Component${isComponent2 ? "2" : ""}', () { - component = renderIntoDocument(sampleComponent({})); + final component = renderIntoDocument(sampleComponent({})); final spanComponent = findRenderedDOMComponentWithClass(component, 'span1'); expect(getProperty(spanComponent, 'tagName'), equals('SPAN')); }); test('findRenderedDOMComponentWithTag on a Component${isComponent2 ? "2" : ""}', () { - component = renderIntoDocument(sampleComponent({})); + final component = renderIntoDocument(sampleComponent({})); final h1Component = findRenderedDOMComponentWithTag(component, 'h1'); expect(getProperty(h1Component, 'tagName'), equals('H1')); }); test('findRenderedComponentWithTypeV2 on a Component${isComponent2 ? "2" : ""}', () { - component = renderIntoDocument(wrapperComponent({}, [sampleComponent({})])); + final component = renderIntoDocument(wrapperComponent({}, [sampleComponent({})])); final result = findRenderedComponentWithTypeV2(component, sampleComponent); expect(isCompositeComponentWithTypeV2(result, sampleComponent), isTrue); }); group('isCompositeComponent on a Component${isComponent2 ? "2" : ""}', () { test('returns true when element is a composite component (created with React.createClass())', () { - component = renderIntoDocument(eventComponent({})); + final component = renderIntoDocument(eventComponent({})); expect(isCompositeComponent(component), isTrue); }); test('returns false when element is not a composite component (created with React.createClass())', () { - component = renderIntoDocument(div({})); + final component = renderIntoDocument(div({}) as ReactElement); expect(isCompositeComponent(component), isFalse); }); @@ -286,7 +280,7 @@ testUtils({ group('isDOMComponent on a Component${isComponent2 ? "2" : ""}', () { test('returns true when argument is a DOM component', () { - component = renderIntoDocument(sampleComponent({})); + final component = renderIntoDocument(sampleComponent({})); final h1Element = findRenderedDOMComponentWithTag(component, 'h1'); expect(isDOMComponent(h1Element), isTrue); @@ -318,7 +312,7 @@ testUtils({ }); test('scryRenderedComponentsWithTypeV2 on a Component${isComponent2 ? "2" : ""}', () { - component = + final component = renderIntoDocument(wrapperComponent({}, [sampleComponent({}), sampleComponent({}), eventComponent({})])); final results = scryRenderedComponentsWithTypeV2(component, sampleComponent); @@ -329,7 +323,7 @@ testUtils({ }); test('scryRenderedDOMComponentsWithClass', () { - component = renderIntoDocument(wrapperComponent({}, [ + final component = renderIntoDocument(wrapperComponent({}, [ div({'className': 'divClass'}), div({'className': 'divClass'}), span({}) @@ -343,7 +337,7 @@ testUtils({ }); test('scryRenderedDOMComponentsWithTag', () { - component = renderIntoDocument(wrapperComponent({}, [div({}), div({}), span({})])); + final component = renderIntoDocument(wrapperComponent({}, [div({}), div({}), span({})])); final results = scryRenderedDOMComponentsWithTag(component, 'div'); diff --git a/test/util.dart b/test/util.dart index b9cfaee9..0521dc85 100644 --- a/test/util.dart +++ b/test/util.dart @@ -7,7 +7,6 @@ import 'dart:html'; import 'dart:js_util' show getProperty; import 'package:js/js.dart'; -import 'package:meta/meta.dart'; import 'package:react/react.dart' as react; import 'package:react/react_dom.dart' as react_dom; import 'package:react/react_client/js_backed_map.dart'; @@ -39,7 +38,7 @@ Map getDartComponentProps(dynamic dartComponent) { } Map getDartElementProps(ReactElement dartElement) { - return isDartComponent2(dartElement) ? JsBackedMap.fromJs(dartElement.props) : dartElement.props.internal.props; + return isDartComponent2(dartElement) ? JsBackedMap.fromJs(dartElement.props) : dartElement.props.internal.props!; } ReactComponent render(ReactElement reactElement) { @@ -50,7 +49,7 @@ ReactComponent render(ReactElement reactElement) { Element findDomNode(dynamic component) => react_dom.findDOMNode(component) as Element; /// Returns a new [Map.unmodifiable] with all argument maps merged in. -Map unmodifiableMap([Map map1, Map map2, Map map3, Map map4]) { +Map unmodifiableMap([Map? map1, Map? map2, Map? map3, Map? map4]) { final merged = {}; if (map1 != null) merged.addAll(map1); if (map2 != null) merged.addAll(map2); @@ -88,10 +87,10 @@ class RefTestCase { final bool isJs; RefTestCase({ - @required this.name, - @required this.ref, - @required this.verifyRefWasUpdated, - @required this.getCurrent, + required this.name, + required this.ref, + required this.verifyRefWasUpdated, + required this.getCurrent, this.isJs = false, }); } From 3109175a1b2ce41d4e3cb74b3e86a549458b5220 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Wed, 16 Aug 2023 14:35:07 -0700 Subject: [PATCH 04/53] Use Never since Null is no longer the bottom type --- lib/react.dart | 3 +-- lib/react_client/component_factory.dart | 2 +- lib/src/react_client/chain_refs.dart | 2 +- lib/src/react_client/factory_util.dart | 4 +--- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/react.dart b/lib/react.dart index 13c51716..cb980004 100644 --- a/lib/react.dart +++ b/lib/react.dart @@ -918,8 +918,7 @@ abstract class Component2 implements Component { /// ``` /// /// See: - // ignore: prefer_void_to_null - Map> get propTypes => {}; + Map> get propTypes => {}; /// Examines [props] and [state] and returns one of the following types: /// diff --git a/lib/react_client/component_factory.dart b/lib/react_client/component_factory.dart index 63fa6651..6a7825e3 100644 --- a/lib/react_client/component_factory.dart +++ b/lib/react_client/component_factory.dart @@ -140,7 +140,7 @@ class ReactDartComponentFactoryProxy extends React // would fail the `is CallbackRef` check. // See https://github.com/dart-lang/sdk/issues/34593 for more information on arity checks. // ignore: prefer_void_to_null - if (ref is CallbackRef) { + if (ref is Function(Never)) { interopProps.ref = allowInterop((dynamic instance) { // Call as dynamic to perform dynamic dispatch, since we can't cast to CallbackRef, // and since calling with non-null values will fail at runtime due to the CallbackRef typing. diff --git a/lib/src/react_client/chain_refs.dart b/lib/src/react_client/chain_refs.dart index 1a4387cf..61b2b485 100644 --- a/lib/src/react_client/chain_refs.dart +++ b/lib/src/react_client/chain_refs.dart @@ -84,7 +84,7 @@ dynamic chainRefList(List refs) { } void _validateChainRefsArg(dynamic ref) { - if (ref is Function(Null) || + if (ref is Function(Never) || ref is Ref || // Need to duck-type since `is JsRef` will return true for most JS objects. (ref is JsRef && hasProperty(ref, 'current'))) { diff --git a/lib/src/react_client/factory_util.dart b/lib/src/react_client/factory_util.dart index 1d0d9b20..85d20875 100644 --- a/lib/src/react_client/factory_util.dart +++ b/lib/src/react_client/factory_util.dart @@ -8,8 +8,6 @@ import 'package:react/react_client/js_backed_map.dart'; import 'package:react/react_client/js_interop_helpers.dart'; import 'package:react/react_client/react_interop.dart'; -import 'package:react/src/typedefs.dart'; - /// Converts a list of variadic children arguments to children that should be passed to ReactJS. /// /// Returns: @@ -56,7 +54,7 @@ void convertRefValue2( // would fail the `is _CallbackRef` check. // See https://github.com/dart-lang/sdk/issues/34593 for more information on arity checks. // ignore: prefer_void_to_null - } else if (ref is CallbackRef && convertCallbackRefValue) { + } else if (ref is Function(Never) && convertCallbackRefValue) { args[refKey] = allowInterop((dynamic instance) { // Call as dynamic to perform dynamic dispatch, since we can't cast to _CallbackRef, // and since calling with non-null values will fail at runtime due to the _CallbackRef typing. From 9b2379e78aecccbc6d89dcbae53cd1509b556e98 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Wed, 16 Aug 2023 14:37:27 -0700 Subject: [PATCH 05/53] Fix lints --- lib/react_client/react_interop.dart | 3 +-- test/react_test_utils_test.dart | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/react_client/react_interop.dart b/lib/react_client/react_interop.dart index eebd1dc2..8ccc240d 100644 --- a/lib/react_client/react_interop.dart +++ b/lib/react_client/react_interop.dart @@ -673,9 +673,8 @@ external get jsUndefined; /// Throws the error passed to it from Javascript. /// This allows us to catch the error in dart which re-dartifies the js errors/exceptions. -@alwaysThrows @JS('_throwErrorFromJS') -external void throwErrorFromJS(error); +external Never throwErrorFromJS(error); /// Marks [child] as validated, as if it were passed into [React.createElement] /// as a variadic child. diff --git a/test/react_test_utils_test.dart b/test/react_test_utils_test.dart index f3403d8c..83e2f354 100644 --- a/test/react_test_utils_test.dart +++ b/test/react_test_utils_test.dart @@ -32,8 +32,8 @@ void main() { wrapperComponent: component2.wrapperComponent); } -testUtils({ - bool isComponent2 = false, +void testUtils({ + required bool isComponent2, required ReactComponentFactoryProxy eventComponent, required ReactComponentFactoryProxy sampleComponent, required ReactComponentFactoryProxy wrapperComponent, From f1101b7ed57e0fdb8bd9f00f9030e7ddad1e65c5 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Wed, 16 Aug 2023 14:54:48 -0700 Subject: [PATCH 06/53] Fix instances of `dynamic?` --- lib/react_client/js_backed_map.dart | 4 +- lib/react_client/react_interop.dart | 18 ++--- lib/src/react_client/event_helpers.dart | 100 ++++++++++++------------ 3 files changed, 61 insertions(+), 61 deletions(-) diff --git a/lib/react_client/js_backed_map.dart b/lib/react_client/js_backed_map.dart index 58e58f4c..527ed50e 100644 --- a/lib/react_client/js_backed_map.dart +++ b/lib/react_client/js_backed_map.dart @@ -58,7 +58,7 @@ class JsBackedMap extends MapBase { // ---------------------------------- @override - dynamic? operator [](Object? key) { + dynamic/*?*/ operator [](Object? key) { // Cast key as dynamic to work around https://github.com/dart-lang/sdk/issues/45219 return DartValueWrapper.unwrapIfNeeded(js_util.getProperty(jsObject, key as dynamic)); } @@ -72,7 +72,7 @@ class JsBackedMap extends MapBase { Iterable get keys => _keys; @override - dynamic? remove(Object? key) { + dynamic/*?*/ remove(Object? key) { final value = this[key]; _Reflect.deleteProperty(jsObject, key); return value; diff --git a/lib/react_client/react_interop.dart b/lib/react_client/react_interop.dart index 8ccc240d..f1874f84 100644 --- a/lib/react_client/react_interop.dart +++ b/lib/react_client/react_interop.dart @@ -31,7 +31,7 @@ typedef JsPropValidator = dynamic Function( @JS() abstract class React { external static String get version; - external static ReactElement cloneElement(ReactElement element, [JsMap? props, dynamic? children]); + external static ReactElement cloneElement(ReactElement element, [JsMap? props, Object? children]); external static ReactContext createContext([ dynamic defaultValue, int Function(dynamic currentValue, dynamic nextValue)? calculateChangedBits, @@ -40,7 +40,7 @@ abstract class React { external static ReactClass createClass(ReactClassConfig reactClassConfig); @Deprecated('7.0.0') external static ReactJsComponentFactory createFactory(type); - external static ReactElement createElement(dynamic type, props, [dynamic? children]); + external static ReactElement createElement(dynamic type, props, [Object? children]); external static JsRef createRef(); external static ReactClass forwardRef(Function(JsMap props, dynamic ref) wrapperFunction); external static ReactClass memo( @@ -59,7 +59,7 @@ abstract class React { external static List useReducer(Function reducer, dynamic initialState, [Function? init]); external static Function useCallback(Function callback, List dependencies); external static ReactContext useContext(ReactContext context); - external static JsRef useRef([dynamic? initialValue]); + external static JsRef useRef([Object? initialValue]); external static dynamic useMemo(dynamic Function() createFunction, [List? dependencies]); external static void useLayoutEffect(dynamic Function() sideEffect, [List? dependencies]); external static void useImperativeHandle(dynamic ref, dynamic Function() createHandle, [List? dependencies]); @@ -532,7 +532,7 @@ class ReactComponent { external Component? get dartComponent; // TODO how to make this JsMap without breaking stuff? external InteropProps get props; - external dynamic? get context; + external dynamic/*?*/ get context; external JsMap? get state; external set state(JsMap? value); external get refs; @@ -595,11 +595,11 @@ class InteropProps implements JsMap { /// Will be removed alongside `Component` in the `7.0.0` release. @Deprecated('7.0.0') external ReactDartComponentInternal get internal; - external dynamic? get key; - external dynamic? get ref; + external dynamic/*?*/ get key; + external dynamic/*?*/ get ref; - external set key(dynamic? value); - external set ref(dynamic? value); + external set key(dynamic/*?*/ value); + external set ref(dynamic/*?*/ value); /// __Deprecated.__ /// @@ -612,7 +612,7 @@ class InteropProps implements JsMap { external factory InteropProps({ ReactDartComponentInternal? internal, String? key, - dynamic? ref, + Object? ref, }); } diff --git a/lib/src/react_client/event_helpers.dart b/lib/src/react_client/event_helpers.dart index f56a05b6..13d2e990 100644 --- a/lib/src/react_client/event_helpers.dart +++ b/lib/src/react_client/event_helpers.dart @@ -113,14 +113,14 @@ Map _wrapBaseEventPropertiesInMap({ SyntheticEvent? baseEvent, bool? bubbles, bool? cancelable, - dynamic? currentTarget, + Object? currentTarget, bool? defaultPrevented, void Function()? preventDefault, void Function()? stopPropagation, num? eventPhase, bool? isTrusted, - dynamic? nativeEvent, - dynamic? target, + Object? nativeEvent, + Object? target, num? timeStamp, String? type, }) { @@ -149,14 +149,14 @@ SyntheticEvent createSyntheticEvent({ SyntheticEvent? baseEvent, bool? bubbles, bool? cancelable, - dynamic? currentTarget, + Object? currentTarget, bool? defaultPrevented, void Function()? preventDefault, void Function()? stopPropagation, num? eventPhase, bool? isTrusted, - dynamic? nativeEvent, - dynamic? target, + Object? nativeEvent, + Object? target, num? timeStamp, String? type, }) { @@ -186,17 +186,17 @@ SyntheticClipboardEvent createSyntheticClipboardEvent({ SyntheticClipboardEvent? baseEvent, bool? bubbles, bool? cancelable, - dynamic? currentTarget, + Object? currentTarget, bool? defaultPrevented, void Function()? preventDefault, void Function()? stopPropagation, num? eventPhase, bool? isTrusted, - dynamic? nativeEvent, - dynamic? target, + Object? nativeEvent, + Object? target, num? timeStamp, String? type, - dynamic? clipboardData, + Object? clipboardData, }) { return jsifyAndAllowInterop({ ..._wrapBaseEventPropertiesInMap( @@ -227,14 +227,14 @@ SyntheticKeyboardEvent createSyntheticKeyboardEvent({ SyntheticKeyboardEvent? baseEvent, bool? bubbles, bool? cancelable, - dynamic? currentTarget, + Object? currentTarget, bool? defaultPrevented, void Function()? preventDefault, void Function()? stopPropagation, num? eventPhase, bool? isTrusted, - dynamic? nativeEvent, - dynamic? target, + Object? nativeEvent, + Object? target, num? timeStamp, String? type, bool? altKey, @@ -288,14 +288,14 @@ SyntheticCompositionEvent createSyntheticCompositionEvent({ SyntheticCompositionEvent? baseEvent, bool? bubbles, bool? cancelable, - dynamic? currentTarget, + Object? currentTarget, bool? defaultPrevented, void Function()? preventDefault, void Function()? stopPropagation, num? eventPhase, bool? isTrusted, - dynamic? nativeEvent, - dynamic? target, + Object? nativeEvent, + Object? target, num? timeStamp, String? type, String? data, @@ -329,17 +329,17 @@ SyntheticFocusEvent createSyntheticFocusEvent({ SyntheticFocusEvent? baseEvent, bool? bubbles, bool? cancelable, - dynamic? currentTarget, + Object? currentTarget, bool? defaultPrevented, void Function()? preventDefault, void Function()? stopPropagation, num? eventPhase, bool? isTrusted, - dynamic? nativeEvent, - dynamic? target, + Object? nativeEvent, + Object? target, num? timeStamp, String? type, - /*DOMEventTarget*/ dynamic? relatedTarget, + /*DOMEventTarget*/ Object? relatedTarget, }) { return jsifyAndAllowInterop({ ..._wrapBaseEventPropertiesInMap( @@ -370,14 +370,14 @@ SyntheticFormEvent createSyntheticFormEvent({ SyntheticFormEvent? baseEvent, bool? bubbles, bool? cancelable, - dynamic? currentTarget, + Object? currentTarget, bool? defaultPrevented, void Function()? preventDefault, void Function()? stopPropagation, num? eventPhase, bool? isTrusted, - dynamic? nativeEvent, - dynamic? target, + Object? nativeEvent, + Object? target, num? timeStamp, String? type, }) { @@ -409,14 +409,14 @@ SyntheticMouseEvent createSyntheticMouseEvent({ SyntheticMouseEvent? baseEvent, bool? bubbles, bool? cancelable, - dynamic? currentTarget, + Object? currentTarget, bool? defaultPrevented, void Function()? preventDefault, void Function()? stopPropagation, num? eventPhase, bool? isTrusted, - dynamic? nativeEvent, - dynamic? target, + Object? nativeEvent, + Object? target, num? timeStamp, String? type, bool? altKey, @@ -425,11 +425,11 @@ SyntheticMouseEvent createSyntheticMouseEvent({ num? clientX, num? clientY, bool? ctrlKey, - dynamic? dataTransfer, + Object? dataTransfer, bool? metaKey, num? pageX, num? pageY, - /*DOMEventTarget*/ dynamic? relatedTarget, + /*DOMEventTarget*/ Object? relatedTarget, num? screenX, num? screenY, bool? shiftKey, @@ -476,14 +476,14 @@ SyntheticPointerEvent createSyntheticPointerEvent({ SyntheticPointerEvent? baseEvent, bool? bubbles, bool? cancelable, - dynamic? currentTarget, + Object? currentTarget, bool? defaultPrevented, void Function()? preventDefault, void Function()? stopPropagation, num? eventPhase, bool? isTrusted, - dynamic? nativeEvent, - dynamic? target, + Object? nativeEvent, + Object? target, num? timeStamp, String? type, num? pointerId, @@ -535,23 +535,23 @@ SyntheticTouchEvent createSyntheticTouchEvent({ SyntheticTouchEvent? baseEvent, bool? bubbles, bool? cancelable, - dynamic? currentTarget, + Object? currentTarget, bool? defaultPrevented, void Function()? preventDefault, void Function()? stopPropagation, num? eventPhase, bool? isTrusted, - dynamic? nativeEvent, - dynamic? target, + Object? nativeEvent, + Object? target, num? timeStamp, String? type, bool? altKey, - /*DOMTouchList*/ dynamic? changedTouches, + /*DOMTouchList*/ Object? changedTouches, bool? ctrlKey, bool? metaKey, bool? shiftKey, - /*DOMTouchList*/ dynamic? targetTouches, - /*DOMTouchList*/ dynamic? touches, + /*DOMTouchList*/ Object? targetTouches, + /*DOMTouchList*/ Object? touches, }) { return jsifyAndAllowInterop({ ..._wrapBaseEventPropertiesInMap( @@ -588,14 +588,14 @@ SyntheticTransitionEvent createSyntheticTransitionEvent({ SyntheticTransitionEvent? baseEvent, bool? bubbles, bool? cancelable, - dynamic? currentTarget, + Object? currentTarget, bool? defaultPrevented, void Function()? preventDefault, void Function()? stopPropagation, num? eventPhase, bool? isTrusted, - dynamic? nativeEvent, - dynamic? target, + Object? nativeEvent, + Object? target, num? timeStamp, String? type, String? propertyName, @@ -633,14 +633,14 @@ SyntheticAnimationEvent createSyntheticAnimationEvent({ SyntheticAnimationEvent? baseEvent, bool? bubbles, bool? cancelable, - dynamic? currentTarget, + Object? currentTarget, bool? defaultPrevented, void Function()? preventDefault, void Function()? stopPropagation, num? eventPhase, bool? isTrusted, - dynamic? nativeEvent, - dynamic? target, + Object? nativeEvent, + Object? target, num? timeStamp, String? type, String? animationName, @@ -678,18 +678,18 @@ SyntheticUIEvent createSyntheticUIEvent({ SyntheticUIEvent? baseEvent, bool? bubbles, bool? cancelable, - dynamic? currentTarget, + Object? currentTarget, bool? defaultPrevented, void Function()? preventDefault, void Function()? stopPropagation, num? eventPhase, bool? isTrusted, - dynamic? nativeEvent, - dynamic? target, + Object? nativeEvent, + Object? target, num? timeStamp, String? type, num? detail, - /*DOMAbstractView*/ dynamic? view, + /*DOMAbstractView*/ Object? view, }) { return jsifyAndAllowInterop({ ..._wrapBaseEventPropertiesInMap( @@ -721,14 +721,14 @@ SyntheticWheelEvent createSyntheticWheelEvent({ SyntheticWheelEvent? baseEvent, bool? bubbles, bool? cancelable, - dynamic? currentTarget, + Object? currentTarget, bool? defaultPrevented, void Function()? preventDefault, void Function()? stopPropagation, num? eventPhase, bool? isTrusted, - dynamic? nativeEvent, - dynamic? target, + Object? nativeEvent, + Object? target, num? timeStamp, String? type, num? deltaX, From 1c88f0f4bb6072d1f033f0982563c5aadb861fa3 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Wed, 16 Aug 2023 15:07:50 -0700 Subject: [PATCH 07/53] Fix more implicit casts in examples --- example/test/react_test_components.dart | 4 ++-- example/test/speed_test.dart | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/example/test/react_test_components.dart b/example/test/react_test_components.dart index 0942add4..84c179bd 100644 --- a/example/test/react_test_components.dart +++ b/example/test/react_test_components.dart @@ -169,7 +169,7 @@ class _ListComponent extends react.Component { @override render() { final items = []; - for (final item in state['items']) { + for (final item in state['items'] as List) { items.add(react.li({'key': item}, '$item')); } @@ -473,7 +473,7 @@ class _Component2TestComponent extends react.Component2 with react.TypedSnapshot // Used to generate unique keys even when the list contains duplicate items final itemCounts = {}; final items = []; - for (final item in state['items']) { + for (final item in state['items'] as List) { final count = itemCounts[item] = (itemCounts[item] ?? 0) + 1; items.add(react.li({'key': 'c2-$item-$count'}, '$item')); } diff --git a/example/test/speed_test.dart b/example/test/speed_test.dart index 057e5a93..4a32f6dd 100644 --- a/example/test/speed_test.dart +++ b/example/test/speed_test.dart @@ -53,7 +53,7 @@ class _Hello extends react.Component { @override render() { timeprint('rendering start'); - final data = props['data']; + final data = props['data'] as List; final children = []; for (final elem in data) { children.add(react.div({ From d7cd3545f5c68acaed5528976d69835b0093749c Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Wed, 16 Aug 2023 15:57:47 -0700 Subject: [PATCH 08/53] Update mocks to work in null safety by using mockito's builder --- build.yaml | 7 +- test/mockito.dart | 14 + test/mockito.mocks.dart | 446 ++++++++++++++++++++++ test/react_client/event_helpers_test.dart | 16 +- 4 files changed, 472 insertions(+), 11 deletions(-) create mode 100644 test/mockito.dart create mode 100644 test/mockito.mocks.dart diff --git a/build.yaml b/build.yaml index 1b101855..5bee8d28 100644 --- a/build.yaml +++ b/build.yaml @@ -1,11 +1,10 @@ targets: $default: builders: - # mockito's builder is expensive and is not needed until this package is - # migrated to null-safety. At that point, it should be scoped only to - # relevant files. mockito:mockBuilder: - enabled: false + # Scope only to files declaring mocks, for performance. + generate_for: + - test/mockito.dart build_web_compilers|entrypoint: # These are globs for the entrypoints you want to compile. generate_for: diff --git a/test/mockito.dart b/test/mockito.dart new file mode 100644 index 00000000..be1b6a19 --- /dev/null +++ b/test/mockito.dart @@ -0,0 +1,14 @@ +// This file generates a mockito.mocks.dart, containing mock classes that +// can be imported by other tests. + +library react.test.mockito_gen_entrypoint; + +import 'dart:html'; +import 'package:mockito/annotations.dart'; + +@GenerateNiceMocks([ + MockSpec(), + MockSpec(), + MockSpec(), +]) +main() {} diff --git a/test/mockito.mocks.dart b/test/mockito.mocks.dart new file mode 100644 index 00000000..6becb95a --- /dev/null +++ b/test/mockito.mocks.dart @@ -0,0 +1,446 @@ +// Mocks generated by Mockito 5.4.0 from annotations +// in react/test/mockito.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:html' as _i2; +import 'dart:math' as _i3; + +import 'package:mockito/mockito.dart' as _i1; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeElement_0 extends _i1.SmartFake implements _i2.Element { + _FakeElement_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePoint_1 extends _i1.SmartFake + implements _i3.Point { + _FakePoint_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeDataTransfer_2 extends _i1.SmartFake implements _i2.DataTransfer { + _FakeDataTransfer_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [KeyboardEvent]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockKeyboardEvent extends _i1.Mock implements _i2.KeyboardEvent { + @override + int get keyCode => (super.noSuchMethod( + Invocation.getter(#keyCode), + returnValue: 0, + returnValueForMissingStub: 0, + ) as int); + @override + int get charCode => (super.noSuchMethod( + Invocation.getter(#charCode), + returnValue: 0, + returnValueForMissingStub: 0, + ) as int); + @override + bool get altKey => (super.noSuchMethod( + Invocation.getter(#altKey), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + @override + bool get ctrlKey => (super.noSuchMethod( + Invocation.getter(#ctrlKey), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + @override + int get location => (super.noSuchMethod( + Invocation.getter(#location), + returnValue: 0, + returnValueForMissingStub: 0, + ) as int); + @override + bool get metaKey => (super.noSuchMethod( + Invocation.getter(#metaKey), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + @override + bool get shiftKey => (super.noSuchMethod( + Invocation.getter(#shiftKey), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + @override + _i2.Element get matchingTarget => (super.noSuchMethod( + Invocation.getter(#matchingTarget), + returnValue: _FakeElement_0( + this, + Invocation.getter(#matchingTarget), + ), + returnValueForMissingStub: _FakeElement_0( + this, + Invocation.getter(#matchingTarget), + ), + ) as _i2.Element); + @override + List<_i2.EventTarget> get path => (super.noSuchMethod( + Invocation.getter(#path), + returnValue: <_i2.EventTarget>[], + returnValueForMissingStub: <_i2.EventTarget>[], + ) as List<_i2.EventTarget>); + @override + bool get defaultPrevented => (super.noSuchMethod( + Invocation.getter(#defaultPrevented), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + @override + int get eventPhase => (super.noSuchMethod( + Invocation.getter(#eventPhase), + returnValue: 0, + returnValueForMissingStub: 0, + ) as int); + @override + String get type => (super.noSuchMethod( + Invocation.getter(#type), + returnValue: '', + returnValueForMissingStub: '', + ) as String); + @override + bool getModifierState(String? keyArg) => (super.noSuchMethod( + Invocation.method( + #getModifierState, + [keyArg], + ), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + @override + List<_i2.EventTarget> composedPath() => (super.noSuchMethod( + Invocation.method( + #composedPath, + [], + ), + returnValue: <_i2.EventTarget>[], + returnValueForMissingStub: <_i2.EventTarget>[], + ) as List<_i2.EventTarget>); + @override + void preventDefault() => super.noSuchMethod( + Invocation.method( + #preventDefault, + [], + ), + returnValueForMissingStub: null, + ); + @override + void stopImmediatePropagation() => super.noSuchMethod( + Invocation.method( + #stopImmediatePropagation, + [], + ), + returnValueForMissingStub: null, + ); + @override + void stopPropagation() => super.noSuchMethod( + Invocation.method( + #stopPropagation, + [], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [DataTransfer]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockDataTransfer extends _i1.Mock implements _i2.DataTransfer { + @override + set dropEffect(String? value) => super.noSuchMethod( + Invocation.setter( + #dropEffect, + value, + ), + returnValueForMissingStub: null, + ); + @override + set effectAllowed(String? value) => super.noSuchMethod( + Invocation.setter( + #effectAllowed, + value, + ), + returnValueForMissingStub: null, + ); + @override + void clearData([String? format]) => super.noSuchMethod( + Invocation.method( + #clearData, + [format], + ), + returnValueForMissingStub: null, + ); + @override + String getData(String? format) => (super.noSuchMethod( + Invocation.method( + #getData, + [format], + ), + returnValue: '', + returnValueForMissingStub: '', + ) as String); + @override + void setData( + String? format, + String? data, + ) => + super.noSuchMethod( + Invocation.method( + #setData, + [ + format, + data, + ], + ), + returnValueForMissingStub: null, + ); + @override + void setDragImage( + _i2.Element? image, + int? x, + int? y, + ) => + super.noSuchMethod( + Invocation.method( + #setDragImage, + [ + image, + x, + y, + ], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [MouseEvent]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockMouseEvent extends _i1.Mock implements _i2.MouseEvent { + @override + bool get altKey => (super.noSuchMethod( + Invocation.getter(#altKey), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + @override + int get button => (super.noSuchMethod( + Invocation.getter(#button), + returnValue: 0, + returnValueForMissingStub: 0, + ) as int); + @override + bool get ctrlKey => (super.noSuchMethod( + Invocation.getter(#ctrlKey), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + @override + bool get metaKey => (super.noSuchMethod( + Invocation.getter(#metaKey), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + @override + bool get shiftKey => (super.noSuchMethod( + Invocation.getter(#shiftKey), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + @override + _i3.Point get client => (super.noSuchMethod( + Invocation.getter(#client), + returnValue: _FakePoint_1( + this, + Invocation.getter(#client), + ), + returnValueForMissingStub: _FakePoint_1( + this, + Invocation.getter(#client), + ), + ) as _i3.Point); + @override + _i3.Point get movement => (super.noSuchMethod( + Invocation.getter(#movement), + returnValue: _FakePoint_1( + this, + Invocation.getter(#movement), + ), + returnValueForMissingStub: _FakePoint_1( + this, + Invocation.getter(#movement), + ), + ) as _i3.Point); + @override + _i3.Point get offset => (super.noSuchMethod( + Invocation.getter(#offset), + returnValue: _FakePoint_1( + this, + Invocation.getter(#offset), + ), + returnValueForMissingStub: _FakePoint_1( + this, + Invocation.getter(#offset), + ), + ) as _i3.Point); + @override + _i3.Point get screen => (super.noSuchMethod( + Invocation.getter(#screen), + returnValue: _FakePoint_1( + this, + Invocation.getter(#screen), + ), + returnValueForMissingStub: _FakePoint_1( + this, + Invocation.getter(#screen), + ), + ) as _i3.Point); + @override + _i3.Point get layer => (super.noSuchMethod( + Invocation.getter(#layer), + returnValue: _FakePoint_1( + this, + Invocation.getter(#layer), + ), + returnValueForMissingStub: _FakePoint_1( + this, + Invocation.getter(#layer), + ), + ) as _i3.Point); + @override + _i3.Point get page => (super.noSuchMethod( + Invocation.getter(#page), + returnValue: _FakePoint_1( + this, + Invocation.getter(#page), + ), + returnValueForMissingStub: _FakePoint_1( + this, + Invocation.getter(#page), + ), + ) as _i3.Point); + @override + _i2.DataTransfer get dataTransfer => (super.noSuchMethod( + Invocation.getter(#dataTransfer), + returnValue: _FakeDataTransfer_2( + this, + Invocation.getter(#dataTransfer), + ), + returnValueForMissingStub: _FakeDataTransfer_2( + this, + Invocation.getter(#dataTransfer), + ), + ) as _i2.DataTransfer); + @override + _i2.Element get matchingTarget => (super.noSuchMethod( + Invocation.getter(#matchingTarget), + returnValue: _FakeElement_0( + this, + Invocation.getter(#matchingTarget), + ), + returnValueForMissingStub: _FakeElement_0( + this, + Invocation.getter(#matchingTarget), + ), + ) as _i2.Element); + @override + List<_i2.EventTarget> get path => (super.noSuchMethod( + Invocation.getter(#path), + returnValue: <_i2.EventTarget>[], + returnValueForMissingStub: <_i2.EventTarget>[], + ) as List<_i2.EventTarget>); + @override + bool get defaultPrevented => (super.noSuchMethod( + Invocation.getter(#defaultPrevented), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + @override + int get eventPhase => (super.noSuchMethod( + Invocation.getter(#eventPhase), + returnValue: 0, + returnValueForMissingStub: 0, + ) as int); + @override + String get type => (super.noSuchMethod( + Invocation.getter(#type), + returnValue: '', + returnValueForMissingStub: '', + ) as String); + @override + bool getModifierState(String? keyArg) => (super.noSuchMethod( + Invocation.method( + #getModifierState, + [keyArg], + ), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + @override + List<_i2.EventTarget> composedPath() => (super.noSuchMethod( + Invocation.method( + #composedPath, + [], + ), + returnValue: <_i2.EventTarget>[], + returnValueForMissingStub: <_i2.EventTarget>[], + ) as List<_i2.EventTarget>); + @override + void preventDefault() => super.noSuchMethod( + Invocation.method( + #preventDefault, + [], + ), + returnValueForMissingStub: null, + ); + @override + void stopImmediatePropagation() => super.noSuchMethod( + Invocation.method( + #stopImmediatePropagation, + [], + ), + returnValueForMissingStub: null, + ); + @override + void stopPropagation() => super.noSuchMethod( + Invocation.method( + #stopPropagation, + [], + ), + returnValueForMissingStub: null, + ); +} diff --git a/test/react_client/event_helpers_test.dart b/test/react_client/event_helpers_test.dart index a10b1184..49610382 100644 --- a/test/react_client/event_helpers_test.dart +++ b/test/react_client/event_helpers_test.dart @@ -10,6 +10,8 @@ import 'package:react/react_test_utils.dart'; import 'package:test/test.dart'; import 'package:mockito/mockito.dart'; +import '../mockito.mocks.dart'; + main() { group('Synthetic event helpers', () { const testString = 'test'; @@ -163,6 +165,9 @@ main() { final relatedTarget = DivElement(); final calls = []; + final dataTransfer = MockDataTransfer(); + when(dataTransfer.dropEffect).thenReturn('move'); + when(nativeMouseEvent.bubbles).thenReturn(true); when(nativeMouseEvent.cancelable).thenReturn(true); when(nativeMouseEvent.currentTarget).thenReturn(currentTarget); @@ -177,6 +182,7 @@ main() { when(nativeMouseEvent.button).thenReturn(0); when(nativeMouseEvent.ctrlKey).thenReturn(false); when(nativeMouseEvent.metaKey).thenReturn(false); + when(nativeMouseEvent.dataTransfer).thenReturn(dataTransfer); when(nativeMouseEvent.relatedTarget).thenReturn(relatedTarget); when(nativeMouseEvent.shiftKey).thenReturn(false); when(nativeMouseEvent.client).thenReturn(Point(1, 2)); @@ -208,10 +214,12 @@ main() { expect(syntheticMouseEvent.clientX, 1); expect(syntheticMouseEvent.clientY, 2); expect(syntheticMouseEvent.ctrlKey, isFalse); - expect(syntheticMouseEvent.dataTransfer, isNull); expect(syntheticMouseEvent.metaKey, isFalse); expect(syntheticMouseEvent.pageX, 3); expect(syntheticMouseEvent.pageY, 4); + // This getter returns an equivalent SyntheticDataTransfer, + // so we can't just use equality here. + expect(syntheticMouseEvent.dataTransfer?.dropEffect, dataTransfer.dropEffect); expect(syntheticMouseEvent.relatedTarget, relatedTarget); expect(syntheticMouseEvent.screenX, 5); expect(syntheticMouseEvent.screenY, 6); @@ -2063,12 +2071,6 @@ main() { }); } -// ignore: avoid_implementing_value_types -class MockKeyboardEvent extends Mock implements KeyboardEvent {} - -// ignore: avoid_implementing_value_types -class MockMouseEvent extends Mock implements MouseEvent {} - enum SyntheticEventType { syntheticClipboardEvent, syntheticKeyboardEvent, From 4176a9e9653b4f46da0bf2b66a30c5a1b442c866 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Wed, 16 Aug 2023 16:17:08 -0700 Subject: [PATCH 09/53] Address fixme around createReactDartComponentClass(2) nullability --- lib/react_client/react_interop.dart | 4 ++-- lib/src/react_client/private_utils.dart | 13 +++++++++---- test/js_builds/shared_tests.dart | 18 ++++++++++++++---- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/lib/react_client/react_interop.dart b/lib/react_client/react_interop.dart index f1874f84..ed9db0d0 100644 --- a/lib/react_client/react_interop.dart +++ b/lib/react_client/react_interop.dart @@ -705,7 +705,7 @@ void markChildrenValidated(List children) { @JS('_createReactDartComponentClass') @Deprecated('7.0.0') external ReactClass createReactDartComponentClass( - ReactDartInteropStatics? dartInteropStatics, ComponentStatics? componentStatics, + ReactDartInteropStatics dartInteropStatics, ComponentStatics componentStatics, [JsComponentConfig? jsConfig]); /// Returns a new JS [ReactClass] for a component that uses @@ -714,7 +714,7 @@ external ReactClass createReactDartComponentClass( /// /// See `_ReactDartInteropStatics2.staticsForJs`]` for an example implementation. @JS('_createReactDartComponentClass2') -external ReactClass createReactDartComponentClass2(JsMap? dartInteropStatics, ComponentStatics2? componentStatics, +external ReactClass createReactDartComponentClass2(JsMap dartInteropStatics, ComponentStatics2 componentStatics, [JsComponentConfig2? jsConfig]); @JS('React.__isDevelopment') diff --git a/lib/src/react_client/private_utils.dart b/lib/src/react_client/private_utils.dart index 7e9ad6a9..d0ddb016 100644 --- a/lib/src/react_client/private_utils.dart +++ b/lib/src/react_client/private_utils.dart @@ -4,8 +4,10 @@ library react_client_private_utils; import 'dart:js_util'; import 'package:js/js.dart'; +import 'package:react/react.dart' show Component2; import 'package:react/react_client/react_interop.dart'; import 'package:react/src/js_interop_util.dart'; +import 'package:react/src/react_client/component_registration.dart' show registerComponent2; /// A flag used to cache whether React is accessible. /// @@ -48,10 +50,8 @@ void validateJsApi() { // corresponding JS functions are not available. React.isValidElement(null); ReactDom.findDOMNode(null); - // ignore: deprecated_member_use_from_same_package - // fixme validate the bundle another way so we don't have to make these null - // createReactDartComponentClass(null, null, null); - // createReactDartComponentClass2(null, null, null); + // This indirectly calls createReactDartComponentClass2 + registerComponent2(() => _DummyComponent2()); _isJsApiValid = true; } on NoSuchMethodError catch (_) { throw Exception('react.js and react_dom.js must be loaded.'); @@ -60,6 +60,11 @@ void validateJsApi() { } } +class _DummyComponent2 extends Component2 { + @override + render() => null; +} + /// A wrapper around a value that can't be stored in its raw form /// within a JS object (e.g., a Dart function). class DartValueWrapper { diff --git a/test/js_builds/shared_tests.dart b/test/js_builds/shared_tests.dart index b9b96cba..c4d47432 100644 --- a/test/js_builds/shared_tests.dart +++ b/test/js_builds/shared_tests.dart @@ -28,19 +28,29 @@ void sharedJsFunctionTests() { }); group('createReactDartComponentClass', () { - test('is function that does not throw when called', () { - expect(() => createReactDartComponentClass(null, null), returnsNormally); + test('is a function that does not throw when called (indirectly tested by registering a Component)', () { + expect(() => react.registerComponent(() => DummyComponent()), returnsNormally); }); }); group('createReactDartComponentClass2', () { - test('is function that does not throw when called', () { - expect(() => createReactDartComponentClass2(null, null, JsComponentConfig2(skipMethods: [])), returnsNormally); + test('is function that does not throw when called (indirectly tested by registering a Component2)', () { + expect(() => react.registerComponent(() => DummyComponent2()), returnsNormally); }); }); }); } +class DummyComponent extends react.Component { + @override + render() => null; +} + +class DummyComponent2 extends react.Component2 { + @override + render() => null; +} + /// The arguments corresponding to each call of `console.warn`, /// collected via a spy set up in console_spy_include_this_js_first.js @JS() From 5adad69d10af0518223fca06ef2c7f0e8d0e42eb Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Wed, 16 Aug 2023 16:26:59 -0700 Subject: [PATCH 10/53] Address fixme around handling a null SyntheticEvent.type --- lib/src/react_client/event_helpers.dart | 5 ++++- test/react_client/event_helpers_test.dart | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/src/react_client/event_helpers.dart b/lib/src/react_client/event_helpers.dart index 13d2e990..aa73cb82 100644 --- a/lib/src/react_client/event_helpers.dart +++ b/lib/src/react_client/event_helpers.dart @@ -760,7 +760,10 @@ SyntheticWheelEvent createSyntheticWheelEvent({ } extension SyntheticEventTypeHelpers on SyntheticEvent { - // FIXME silence warnings, reinstate test (in mixed mode?) to test for null + // Use getProperty(this, 'type') since, although statically we may be dealing with a SyntheticEvent, + // this could be a non-event object casted to SyntheticEvent with a null `type`. + // This is unlikely, but is possible, and before the null safety migration this method + // gracefully returned false instead of throwing. bool _checkEventType(List types) => getProperty(this, 'type') != null && types.any((t) => type.contains(t)); bool _hasProperty(String propertyName) => hasProperty(this, propertyName); diff --git a/test/react_client/event_helpers_test.dart b/test/react_client/event_helpers_test.dart index 49610382..561318b7 100644 --- a/test/react_client/event_helpers_test.dart +++ b/test/react_client/event_helpers_test.dart @@ -2,6 +2,7 @@ library react.event_helpers_test; import 'dart:html'; +import 'dart:js_util'; import 'package:react/react.dart'; import 'package:react/react_client/js_interop_helpers.dart'; @@ -1696,6 +1697,13 @@ main() { currentEventTypeBeingTested == SyntheticEventType.syntheticFormEvent ? isTrue : isFalse, reason: 'The `SyntheticEvent` base class is considered a Form Event via Duck Typing.'); }); + + // This case shouldn't happen, but there may be consumers relying on this behavior. + test( + 'when the argument is a non-event JS object casted to SyntheticEvent, and has a null `type` property', + () { + expect(eventTypeTester(newObject() as SyntheticEvent), isFalse); + }); }); } From af89ed36a169ad83bc631f57c2c565db2a82e1fb Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Wed, 16 Aug 2023 16:39:38 -0700 Subject: [PATCH 11/53] Address fixme around accessing uninitialized late props/state fields in tests --- test/lifecycle_test/util.dart | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/test/lifecycle_test/util.dart b/test/lifecycle_test/util.dart index 120f3d0e..e2580d6f 100644 --- a/test/lifecycle_test/util.dart +++ b/test/lifecycle_test/util.dart @@ -31,26 +31,21 @@ mixin LifecycleTestHelper on Component { return call['memberName']; }).toList(); - static bool _throwsLateInitializationError(void Function() accessLateVariable) { - try { - accessLateVariable(); - } catch (e) { - if (e.toString().contains('LateInitializationError')) { - return true; - } else { - rethrow; - } - } - - return false; - } - T lifecycleCall(String memberName, {List arguments = const [], T Function()? defaultReturnValue, Map? staticProps}) { - // If this is false and we access late variables, such as jsThis/props/state, we'll get a LateInitializationError - // fixme rethink this solution, since it's gross and also might not work in dart2js or mixed mode; perhaps conditionally don't try to access props/state in early lifecycle methods - final hasPropsInitialized = !_throwsLateInitializationError(() => props); - final hasStateInitialized = !_throwsLateInitializationError(() => state); + // Don't try to access late variables props/state/jsThis before they're initialized. + const staticLifecycleMethods = { + 'defaultProps', + 'getDefaultProps', + 'getDerivedStateFromProps', + 'getDerivedStateFromError', + }; + final hasPropsInitialized = !staticLifecycleMethods.contains(memberName); + final hasStateInitialized = !{ + ...staticLifecycleMethods, + 'initialState', + 'getInitialState', + }.contains(memberName); lifecycleCalls.add({ 'memberName': memberName, From 961aa4a31f2b73f8e01af833112f78c93fcae2f9 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Thu, 17 Aug 2023 16:57:40 -0700 Subject: [PATCH 12/53] Support non-nullable refs, remove unsafe Ref constructors --- example/test/ref_test.dart | 5 ++-- lib/hooks.dart | 40 +++++++++++++++++++++++++++-- lib/react_client/react_interop.dart | 18 +++++-------- test/react_memo_test.dart | 6 ++--- 4 files changed, 49 insertions(+), 20 deletions(-) diff --git a/example/test/ref_test.dart b/example/test/ref_test.dart index 6ab68934..118451e7 100644 --- a/example/test/ref_test.dart +++ b/example/test/ref_test.dart @@ -3,7 +3,6 @@ import 'dart:html'; import 'package:react/react.dart' as react; import 'package:react/react_dom.dart' as react_dom; -import 'package:react/react_client.dart'; var ChildComponent = react.registerComponent(() => _ChildComponent()); @@ -101,8 +100,8 @@ class _ParentComponent extends react.Component { } // Create refs - final Ref _inputCreateRef = react.createRef(); - final Ref<_ChildComponent> _childCreateRef = react.createRef(); + final _inputCreateRef = react.createRef(); + final _childCreateRef = react.createRef<_ChildComponent>(); showInputCreateRefValue(_) { final input = react_dom.findDOMNode(_inputCreateRef.current) as InputElement; diff --git a/lib/hooks.dart b/lib/hooks.dart index 8108fe3c..5129444f 100644 --- a/lib/hooks.dart +++ b/lib/hooks.dart @@ -392,8 +392,44 @@ T useContext(Context context) => ContextHelpers.unjsifyNewContext(React.us /// ``` /// /// Learn more: . -// FIXME should we make a non-nullable version of this? -Ref useRef([T? initialValue]) => Ref.useRefInit(initialValue); +@Deprecated('Use useRef2 instead, which supports non-nullable values. For component/element refs, pass `null` as the initial value: `useRef2(null)`.') +Ref useRef([T? initialValue]) => useRef2(initialValue); + +/// Returns a mutable [Ref] object with [Ref.current] property initialized to [initialValue]. +/// +/// Changes to the [Ref.current] property do not cause the containing [DartFunctionComponent] to re-render. +/// +/// The returned [Ref] object will persist for the full lifetime of the [DartFunctionComponent]. +/// Compare to [createRef] which returns a new [Ref] object on each render. +/// +/// > __Note:__ there are two [rules for using Hooks](https://reactjs.org/docs/hooks-rules.html): +/// > +/// > * Only call Hooks at the top level. +/// > * Only call Hooks from inside a [DartFunctionComponent]. +/// +/// __Example__: +/// +/// ```dart +/// UseRefTestComponent(Map props) { +/// final inputValue = useState(''); +/// +/// final inputRef = useRef2(null); +/// final prevInputValueRef = useRef2(null); +/// +/// useEffect(() { +/// prevInputValueRef.current = inputValue.value; +/// }); +/// +/// return react.Fragment({}, [ +/// react.p({}, ['Current Input: ${inputValue.value}, Previous Input: ${prevInputValueRef.current}']), +/// react.input({'ref': inputRef}), +/// react.button({'onClick': (_) => inputValue.set(inputRef.current.value)}, ['Update']), +/// ]); +/// } +/// ``` +/// +/// Learn more: . +Ref useRef2(T initialValue) => Ref.fromJs(React.useRef(initialValue)); /// Returns a memoized version of the return value of [createFunction]. /// diff --git a/lib/react_client/react_interop.dart b/lib/react_client/react_interop.dart index ed9db0d0..d0176793 100644 --- a/lib/react_client/react_interop.dart +++ b/lib/react_client/react_interop.dart @@ -82,9 +82,10 @@ abstract class React { /// } /// /// Learn more: . -Ref createRef() { - return Ref(); -} +// This return type must always be nullable since the ref is always initially empty, +// and in most usages represents a component ref object which could be null. +// useRef2 on the other hand, can be made non-nullable. +Ref createRef() => Ref.fromJs(React.createRef()); /// When this is provided as the ref prop, a reference to the rendered component /// will be available via [current]. @@ -94,19 +95,12 @@ class Ref { /// A JavaScript ref object returned by [React.createRef]. final JsRef jsRef; - Ref() : jsRef = React.createRef(); - - /// Constructor for [useRef], calls [React.useRef] to initialize [current] to [initialValue]. - /// - /// See: . - Ref.useRefInit(T initialValue) : jsRef = React.useRef(initialValue); - Ref.fromJs(this.jsRef); /// A reference to the latest instance of the rendered component. /// /// See [createRef] for usage examples and more info. - T? get current { + T get current { final jsCurrent = jsRef.current; // Note: this ReactComponent check will pass for many types of JS objects, @@ -124,7 +118,7 @@ class Ref { /// Sets the value of [current]. /// /// See: . - set current(T? value) { + set current(T value) { if (value is Component) { jsRef.current = value.jsThis; } else { diff --git a/test/react_memo_test.dart b/test/react_memo_test.dart index 37f1f9f1..db948cc4 100644 --- a/test/react_memo_test.dart +++ b/test/react_memo_test.dart @@ -62,9 +62,9 @@ typedef MemoFunction = react.ReactComponentFactoryProxy Function(ReactDartFuncti {bool Function(Map, Map)? areEqual}); void sharedMemoTests(MemoFunction memoFunction) { - late Ref<_MemoTestWrapperComponent> memoTestWrapperComponentRef; - late Ref localCountDisplayRef; - late Ref valueMemoShouldIgnoreViaAreEqualDisplayRef; + late Ref<_MemoTestWrapperComponent?> memoTestWrapperComponentRef; + late Ref localCountDisplayRef; + late Ref valueMemoShouldIgnoreViaAreEqualDisplayRef; late int childMemoRenderCount; void renderMemoTest({ From 6024c37bb6083b00f22107f15b1e92d818212b4e Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Thu, 17 Aug 2023 17:02:29 -0700 Subject: [PATCH 13/53] Fix useRef deprecation warnings, add useRef2 tests --- example/test/function_component_test.dart | 14 ++-- test/hooks_test.dart | 86 ++++++++++++++++++++++- 2 files changed, 90 insertions(+), 10 deletions(-) diff --git a/example/test/function_component_test.dart b/example/test/function_component_test.dart index aefeff0c..37df2cf7 100644 --- a/example/test/function_component_test.dart +++ b/example/test/function_component_test.dart @@ -230,8 +230,8 @@ var useRefTestFunctionComponent = react.registerFunctionComponent(UseRefTestComp UseRefTestComponent(Map props) { final inputValue = useState(''); - final inputRef = useRef(); - final prevInputValueRef = useRef(); + final inputRef = useRef2(null); + final prevInputValueRef = useRef2(null); useEffect(() { prevInputValueRef.current = inputValue.value; @@ -338,13 +338,13 @@ class FancyInputApi { } final FancyInput = react.forwardRef2((props, ref) { - final inputRef = useRef(); + final inputRef = useRef2(null); useImperativeHandle( ref, () { print('FancyInput: useImperativeHandle re-assigns ref.current'); - return FancyInputApi(() => inputRef.current.focus()); + return FancyInputApi(() => inputRef.current!.focus()); }, /// Because the return value of createHandle never changes, it is not necessary for ref.current @@ -370,8 +370,8 @@ UseImperativeHandleTestComponent(Map props) { final error = useState(''); final message = useState(''); - final cityRef = useRef(); - final stateRef = useRef(); + final cityRef = useRef2(null); + final stateRef = useRef2(null); validate(_) { if (!RegExp(r'^[a-zA-Z]+$').hasMatch(city.value)) { @@ -443,7 +443,7 @@ final useImperativeHandleTestFunctionComponent2 = UseImperativeHandleTestComponent2(Map props) { final diff = useState(1); - final fancyCounterRef = useRef(); + final fancyCounterRef = useRef2(null); return react.Fragment({}, [ FancyCounter({ diff --git a/test/hooks_test.dart b/test/hooks_test.dart index 8394afdb..aeec5f18 100644 --- a/test/hooks_test.dart +++ b/test/hooks_test.dart @@ -433,11 +433,15 @@ main() { mountNode = DivElement(); final UseRefTest = react.registerFunctionComponent((props) { + // ignore: deprecated_member_use_from_same_package noInitRef = useRef(); + // ignore: deprecated_member_use_from_same_package initRef = useRef(mountNode); + // ignore: deprecated_member_use_from_same_package domElementRef = useRef(); renderIndex = useState(1); + // ignore: deprecated_member_use_from_same_package refFromUseRef = useRef(); refFromCreateRef = react.createRef(); @@ -495,6 +499,82 @@ main() { }); }); + group('useRef2 -', () { + late DivElement mountNode; + ButtonElement? reRenderButton; + late Ref nullInitRef; + late Ref initRef; + late Ref domElementRef; + late StateHook renderIndex; + late Ref refFromUseRef; + late Ref refFromCreateRef; + + setUpAll(() { + mountNode = DivElement(); + + final UseRefTest = react.registerFunctionComponent((props) { + nullInitRef = useRef2(null); + initRef = useRef2(mountNode); + domElementRef = useRef2(null); + + renderIndex = useState(1); + refFromUseRef = useRef2(null); + refFromCreateRef = react.createRef(); + + refFromUseRef.current ??= renderIndex.value; + + refFromCreateRef.current ??= renderIndex.value; + + return react.Fragment({}, [ + react.input({'ref': domElementRef}), + react.p({}, [renderIndex.value]), + react.p({}, [refFromUseRef.current]), + react.p({}, [refFromCreateRef.current]), + react.button({ + 'ref': (ref) => reRenderButton = ref as ButtonElement?, + 'onClick': (_) => renderIndex.setWithUpdater((prev) => prev + 1) + }, [ + 're-render' + ]), + ]); + }); + + react_dom.render(UseRefTest({}), mountNode); + }); + + group('correctly initializes a Ref object', () { + test('with current property set to null if no initial value given', () { + expect(nullInitRef, isA()); + expect(nullInitRef.current, null); + }); + + test('with current property set to the initial value given', () { + expect(initRef, isA()); + expect(initRef.current, mountNode); + }); + }); + + group('the returned Ref', () { + test('can be attached to elements via the ref attribute', () { + expect(domElementRef.current, isA()); + }); + + test('will persist even after the component re-renders', () { + expect(renderIndex.value, 1); + expect(refFromUseRef.current, 1, reason: 'Ref object initially created on first render'); + expect(refFromCreateRef.current, 1); + + react_test_utils.Simulate.click(reRenderButton); + + expect(renderIndex.value, 2); + expect(refFromUseRef.current, 1, + reason: 'useRef returns the same Ref object on every render for the full lifetime of the component'); + expect(refFromCreateRef.current, 2, + reason: 'compare to createRef which creates a new Ref object on every render'); + }); + }); + }); + group('useMemo -', () { late StateHook count; ButtonElement? reRenderButtonRef; @@ -697,9 +777,9 @@ main() { react_dom.unmountComponentAtNode(mountNode); final UseImperativeHandleTest = react.registerFunctionComponent((props) { - noDepsRef = useRef(); - emptyDepsRef = useRef(); - depsRef = useRef(); + noDepsRef = useRef2(null); + emptyDepsRef = useRef2(null); + depsRef = useRef2(null); return react.Fragment({}, [ NoDepsComponent({'ref': noDepsRef}, []), From 4aed55f49211701877a305b4dbefa5e0f4f06a70 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Thu, 17 Aug 2023 17:10:12 -0700 Subject: [PATCH 14/53] Remove FIXME for Simulated event nullability (more info below) Simulated synthetic events will be missing some properties if they're not included in eventData, meaning the non-nullable typings on them are incorrect. As opposed to making everything nullable to account for that, we'll treat those simulated events the same way Mockito treats mock objects, and just require that any properties being accessed get stubbed in using the eventData argument. --- lib/src/react_client/synthetic_event_wrappers.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/src/react_client/synthetic_event_wrappers.dart b/lib/src/react_client/synthetic_event_wrappers.dart index 806b6bbd..c4719b0e 100644 --- a/lib/src/react_client/synthetic_event_wrappers.dart +++ b/lib/src/react_client/synthetic_event_wrappers.dart @@ -22,8 +22,6 @@ class SyntheticEvent { /// Use `createSyntheticEvent` instead. external factory SyntheticEvent._(); - // FIXME do most of these need to be nullable because of simulate not populating all fields? 🤢 - /// Indicates whether the [Event] bubbles up through the DOM or not. /// /// See: From 71f519a8cee4ff90e723bb90e33cff8dd3834721 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 22 Aug 2023 12:32:20 -0700 Subject: [PATCH 15/53] Improve typing and safety of initComponentInternal --- lib/react.dart | 24 +++++++++++------------- test/react_component_test.dart | 2 +- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/lib/react.dart b/lib/react.dart index cb980004..84a662e6 100644 --- a/lib/react.dart +++ b/lib/react.dart @@ -194,7 +194,7 @@ abstract class Component { late Function _jsRedraw; - late Object _jsThis; + late dynamic _jsThis; final List _setStateCallbacks = []; @@ -214,29 +214,27 @@ abstract class Component { /// to be set for debugging purposes. String? get displayName => runtimeType.toString(); - initComponentInternal(props, _jsRedraw, [RefMethod? ref, _jsThis, context]) { + static dynamic _defaultRef(String _) => null; + + initComponentInternal(Map props, Function _jsRedraw, [RefMethod? ref, dynamic _jsThis, Map? context]) { this._jsRedraw = _jsRedraw; - this.ref = ref ?? (_) {}; + this.ref = ref ?? _defaultRef; this._jsThis = _jsThis; _initContext(context); _initProps(props); } /// Initializes context - _initContext(context) { - /// [context]s typing was loosened from Map to dynamic to support the new context API in [Component2] - /// which extends from [Component]. Only "legacy" context APIs are supported in [Component] - which means - /// it will still be expected to be a Map. - this.context = Map.from(context as Map? ?? const {}); - - /// [nextContext]s typing was loosened from Map to dynamic to support the new context API in [Component2] + _initContext(Map? context) { + /// [context]'s and [nextContext]'ss typings were loosened from Map to dynamic to support the new context API in [Component2] /// which extends from [Component]. Only "legacy" context APIs are supported in [Component] - which means /// it will still be expected to be a Map. - nextContext = Map.from(this.context as Map? ?? const {}); + this.context = {...?context}; + nextContext = {...?context}; } - _initProps(props) { - this.props = Map.from(props as Map); + _initProps(Map props) { + this.props = Map.from(props); nextProps = this.props; } diff --git a/test/react_component_test.dart b/test/react_component_test.dart index df04dbb9..6ad8472e 100644 --- a/test/react_component_test.dart +++ b/test/react_component_test.dart @@ -53,7 +53,7 @@ main() { expect(() => component.contextKeys, throwsUnsupportedError); }); test('initComponentInternal', () { - expect(() => component.initComponentInternal(null, null), throwsUnsupportedError); + expect(() => component.initComponentInternal({}, () {}), throwsUnsupportedError); }); test('initStateInternal', () { expect(() => component.initStateInternal(), throwsUnsupportedError); From 4fa6b9d7521d3a9085b9682a571559795fa39942 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 22 Aug 2023 12:32:27 -0700 Subject: [PATCH 16/53] Improve comment --- lib/src/react_client/event_helpers.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/react_client/event_helpers.dart b/lib/src/react_client/event_helpers.dart index aa73cb82..5c399db1 100644 --- a/lib/src/react_client/event_helpers.dart +++ b/lib/src/react_client/event_helpers.dart @@ -761,7 +761,7 @@ SyntheticWheelEvent createSyntheticWheelEvent({ extension SyntheticEventTypeHelpers on SyntheticEvent { // Use getProperty(this, 'type') since, although statically we may be dealing with a SyntheticEvent, - // this could be a non-event object casted to SyntheticEvent with a null `type`. + // this could be a non-event JS object cast to SyntheticEvent with a null `type`. // This is unlikely, but is possible, and before the null safety migration this method // gracefully returned false instead of throwing. bool _checkEventType(List types) => getProperty(this, 'type') != null && types.any((t) => type.contains(t)); From e7f2e9eeabf640f01534d3e32e2858413cb20821 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 22 Aug 2023 12:33:58 -0700 Subject: [PATCH 17/53] Make ReactDartComponentInternal.props non-nullable --- lib/react_client/component_factory.dart | 2 +- lib/react_client/react_interop.dart | 4 +++- lib/src/react_client/dart_interop_statics.dart | 5 ++--- test/util.dart | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/react_client/component_factory.dart b/lib/react_client/component_factory.dart index 6a7825e3..833fb0d8 100644 --- a/lib/react_client/component_factory.dart +++ b/lib/react_client/component_factory.dart @@ -120,7 +120,7 @@ class ReactDartComponentFactoryProxy extends React ..remove('key') ..remove('ref'); - final internal = ReactDartComponentInternal()..props = extendedProps; + final internal = ReactDartComponentInternal(extendedProps); final interopProps = InteropProps(internal: internal); diff --git a/lib/react_client/react_interop.dart b/lib/react_client/react_interop.dart index d0176793..bb9cf674 100644 --- a/lib/react_client/react_interop.dart +++ b/lib/react_client/react_interop.dart @@ -628,7 +628,9 @@ class ReactDartComponentInternal { /// /// For a `ReactComponent`, this is the props the component was last rendered with, /// and is used within props-related lifecycle internals. - Map? props; + final Map props; + + ReactDartComponentInternal(this.props); } /// Internal react-dart information used to proxy React JS lifecycle to Dart diff --git a/lib/src/react_client/dart_interop_statics.dart b/lib/src/react_client/dart_interop_statics.dart index dd566e95..0881b8e2 100644 --- a/lib/src/react_client/dart_interop_statics.dart +++ b/lib/src/react_client/dart_interop_statics.dart @@ -64,8 +64,7 @@ final ReactDartInteropStatics dartInteropStatics = (() { }); Map _getNextProps(Component component, ReactDartComponentInternal nextInternal) { - final newProps = nextInternal.props; - return newProps != null ? Map.from(newProps) : {}; + return Map.of(nextInternal.props); } /// 1. Update [Component.props] using the value stored to [Component.nextProps] @@ -159,7 +158,7 @@ final ReactDartInteropStatics dartInteropStatics = (() { final prevInternalProps = prevInternal.props; /// Call `componentDidUpdate` and the context variant - component.componentDidUpdate(prevInternalProps!, component.prevState!); + component.componentDidUpdate(prevInternalProps, component.prevState!); _callSetStateCallbacks(component); // Clear out prevState after it's done being used so it's not retained diff --git a/test/util.dart b/test/util.dart index 0521dc85..8dd45e41 100644 --- a/test/util.dart +++ b/test/util.dart @@ -38,7 +38,7 @@ Map getDartComponentProps(dynamic dartComponent) { } Map getDartElementProps(ReactElement dartElement) { - return isDartComponent2(dartElement) ? JsBackedMap.fromJs(dartElement.props) : dartElement.props.internal.props!; + return isDartComponent2(dartElement) ? JsBackedMap.fromJs(dartElement.props) : dartElement.props.internal.props; } ReactComponent render(ReactElement reactElement) { From d807613156327ec4d7c2930d591a5a5b902fb57e Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 22 Aug 2023 14:07:12 -0700 Subject: [PATCH 18/53] Eliminate ! operator from state initialization --- lib/src/react_client/dart_interop_statics.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/react_client/dart_interop_statics.dart b/lib/src/react_client/dart_interop_statics.dart index 0881b8e2..e99de374 100644 --- a/lib/src/react_client/dart_interop_statics.dart +++ b/lib/src/react_client/dart_interop_statics.dart @@ -213,9 +213,9 @@ abstract class ReactDartInteropStatics2 { ..props = JsBackedMap.backedBy(jsThis.props) ..context = ContextHelpers.unjsifyNewContext(jsThis.context); - jsThis.state = jsBackingMapOrJsCopy(component.initialState); - - component.state = JsBackedMap.backedBy(jsThis.state!); + final jsState = jsBackingMapOrJsCopy(component.initialState); + jsThis.state = jsState; + component.state = JsBackedMap.backedBy(jsState); // ignore: invalid_use_of_protected_member Component2Bridge.bridgeForComponent[component] = componentStatics.bridgeFactory(component); From 90d911e342c32ace2c898ade6a7445c53e7fab84 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 29 Aug 2023 14:29:54 -0700 Subject: [PATCH 19/53] Fix bad non-null assertion --- lib/react_client/component_factory.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/react_client/component_factory.dart b/lib/react_client/component_factory.dart index 833fb0d8..1ef8739d 100644 --- a/lib/react_client/component_factory.dart +++ b/lib/react_client/component_factory.dart @@ -178,7 +178,11 @@ class ReactDartComponentFactoryProxy2 extends Rea @override final Map defaultProps; - ReactDartComponentFactoryProxy2(this.reactClass) : defaultProps = JsBackedMap.fromJs(reactClass.defaultProps!); + ReactDartComponentFactoryProxy2(this.reactClass) + // While .defaultProps will be non-null on all Dart components created via registerComponent2, + // there are some valid usages of ReactDartComponentFactoryProxy2 (such as within over_react's connect) + // where we're dealing with other components, so we can't assume this is non-null + : defaultProps = JsBackedMap.fromJs(reactClass.defaultProps ?? JsMap()); @override ReactClass get type => reactClass; From 0dc260ea12178e27ae0334356b030567177ffde5 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Wed, 30 Aug 2023 16:32:44 -0700 Subject: [PATCH 20/53] Improve useRef nullability by deprecating its argument instead of versioning it useRef without an argument is more common than with one, and this approach improves that experience by: 1. not requiring consumers explicitly provide `null` and provide a nullable type argument 2. not involving a new, versioned API that must be migrated to --- example/test/function_component_test.dart | 12 +++---- lib/hooks.dart | 30 +++++++++-------- test/hooks_test.dart | 41 ++++------------------- 3 files changed, 29 insertions(+), 54 deletions(-) diff --git a/example/test/function_component_test.dart b/example/test/function_component_test.dart index 37df2cf7..618b2ed0 100644 --- a/example/test/function_component_test.dart +++ b/example/test/function_component_test.dart @@ -230,8 +230,8 @@ var useRefTestFunctionComponent = react.registerFunctionComponent(UseRefTestComp UseRefTestComponent(Map props) { final inputValue = useState(''); - final inputRef = useRef2(null); - final prevInputValueRef = useRef2(null); + final inputRef = useRef(); + final prevInputValueRef = useRef(); useEffect(() { prevInputValueRef.current = inputValue.value; @@ -338,7 +338,7 @@ class FancyInputApi { } final FancyInput = react.forwardRef2((props, ref) { - final inputRef = useRef2(null); + final inputRef = useRef(); useImperativeHandle( ref, @@ -370,8 +370,8 @@ UseImperativeHandleTestComponent(Map props) { final error = useState(''); final message = useState(''); - final cityRef = useRef2(null); - final stateRef = useRef2(null); + final cityRef = useRef(); + final stateRef = useRef(); validate(_) { if (!RegExp(r'^[a-zA-Z]+$').hasMatch(city.value)) { @@ -443,7 +443,7 @@ final useImperativeHandleTestFunctionComponent2 = UseImperativeHandleTestComponent2(Map props) { final diff = useState(1); - final fancyCounterRef = useRef2(null); + final fancyCounterRef = useRef(); return react.Fragment({}, [ FancyCounter({ diff --git a/lib/hooks.dart b/lib/hooks.dart index 5129444f..941623ce 100644 --- a/lib/hooks.dart +++ b/lib/hooks.dart @@ -358,7 +358,9 @@ T useCallback(T callback, List dependencies) => /// Learn more: . T useContext(Context context) => ContextHelpers.unjsifyNewContext(React.useContext(context.jsThis)) as T; -/// Returns a mutable [Ref] object with [Ref.current] property initialized to [initialValue]. +/// Returns an empty mutable [Ref] object. +/// +/// To initialize a ref with a value, use [useRefInit] instead. /// /// Changes to the [Ref.current] property do not cause the containing [DartFunctionComponent] to re-render. /// @@ -392,8 +394,12 @@ T useContext(Context context) => ContextHelpers.unjsifyNewContext(React.us /// ``` /// /// Learn more: . -@Deprecated('Use useRef2 instead, which supports non-nullable values. For component/element refs, pass `null` as the initial value: `useRef2(null)`.') -Ref useRef([T? initialValue]) => useRef2(initialValue); +Ref useRef([ + @Deprecated('Use `useRefInit` instead to create a ref with an initial value, because unlike this function, ' + 'the generic parameter of the Ref returned by `useRefInit` can be non-nullable.') + T? initialValue, +]) => + useRefInit(null); /// Returns a mutable [Ref] object with [Ref.current] property initialized to [initialValue]. /// @@ -411,25 +417,21 @@ Ref useRef([T? initialValue]) => useRef2(initialValue); /// /// ```dart /// UseRefTestComponent(Map props) { -/// final inputValue = useState(''); +/// final countRef = useRefInit(1); /// -/// final inputRef = useRef2(null); -/// final prevInputValueRef = useRef2(null); -/// -/// useEffect(() { -/// prevInputValueRef.current = inputValue.value; -/// }); +/// handleClick([_]) { +/// ref.current = ref.current + 1; +/// window.alert('You clicked ${ref.current} times!'); +/// } /// /// return react.Fragment({}, [ -/// react.p({}, ['Current Input: ${inputValue.value}, Previous Input: ${prevInputValueRef.current}']), -/// react.input({'ref': inputRef}), -/// react.button({'onClick': (_) => inputValue.set(inputRef.current.value)}, ['Update']), +/// react.button({'onClick': handleClick}, ['Click me!']), /// ]); /// } /// ``` /// /// Learn more: . -Ref useRef2(T initialValue) => Ref.fromJs(React.useRef(initialValue)); +Ref useRefInit(T initialValue) => Ref.fromJs(React.useRef(initialValue)); /// Returns a memoized version of the return value of [createFunction]. /// diff --git a/test/hooks_test.dart b/test/hooks_test.dart index aeec5f18..7379988a 100644 --- a/test/hooks_test.dart +++ b/test/hooks_test.dart @@ -499,37 +499,22 @@ main() { }); }); - group('useRef2 -', () { + group('useRefInit -', () { late DivElement mountNode; ButtonElement? reRenderButton; - late Ref nullInitRef; late Ref initRef; - late Ref domElementRef; late StateHook renderIndex; - late Ref refFromUseRef; - late Ref refFromCreateRef; setUpAll(() { mountNode = DivElement(); final UseRefTest = react.registerFunctionComponent((props) { - nullInitRef = useRef2(null); - initRef = useRef2(mountNode); - domElementRef = useRef2(null); + initRef = useRefInit(mountNode); renderIndex = useState(1); - refFromUseRef = useRef2(null); - refFromCreateRef = react.createRef(); - - refFromUseRef.current ??= renderIndex.value; - - refFromCreateRef.current ??= renderIndex.value; return react.Fragment({}, [ - react.input({'ref': domElementRef}), react.p({}, [renderIndex.value]), - react.p({}, [refFromUseRef.current]), - react.p({}, [refFromCreateRef.current]), react.button({ 'ref': (ref) => reRenderButton = ref as ButtonElement?, 'onClick': (_) => renderIndex.setWithUpdater((prev) => prev + 1) @@ -543,11 +528,6 @@ main() { }); group('correctly initializes a Ref object', () { - test('with current property set to null if no initial value given', () { - expect(nullInitRef, isA()); - expect(nullInitRef.current, null); - }); - test('with current property set to the initial value given', () { expect(initRef, isA()); expect(initRef.current, mountNode); @@ -555,22 +535,15 @@ main() { }); group('the returned Ref', () { - test('can be attached to elements via the ref attribute', () { - expect(domElementRef.current, isA()); - }); - test('will persist even after the component re-renders', () { expect(renderIndex.value, 1); - expect(refFromUseRef.current, 1, reason: 'Ref object initially created on first render'); - expect(refFromCreateRef.current, 1); + expect(initRef.current, mountNode, reason: 'Ref object initially created on first render'); react_test_utils.Simulate.click(reRenderButton); expect(renderIndex.value, 2); - expect(refFromUseRef.current, 1, + expect(initRef.current, mountNode, reason: 'useRef returns the same Ref object on every render for the full lifetime of the component'); - expect(refFromCreateRef.current, 2, - reason: 'compare to createRef which creates a new Ref object on every render'); }); }); }); @@ -777,9 +750,9 @@ main() { react_dom.unmountComponentAtNode(mountNode); final UseImperativeHandleTest = react.registerFunctionComponent((props) { - noDepsRef = useRef2(null); - emptyDepsRef = useRef2(null); - depsRef = useRef2(null); + noDepsRef = useRef(); + emptyDepsRef = useRef(); + depsRef = useRef(); return react.Fragment({}, [ NoDepsComponent({'ref': noDepsRef}, []), From 1a06d4853f055cfa9307883f9b1bdbb4950ce061 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Thu, 31 Aug 2023 14:34:00 -0700 Subject: [PATCH 21/53] Remove duplicate PropValidator typedef, align typing --- lib/react.dart | 2 -- lib/src/prop_validator.dart | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/react.dart b/lib/react.dart index 84a662e6..66316f12 100644 --- a/lib/react.dart +++ b/lib/react.dart @@ -26,8 +26,6 @@ export 'package:react/src/react_client/synthetic_event_wrappers.dart' hide NonNa export 'package:react/src/react_client/synthetic_data_transfer.dart' show SyntheticDataTransfer; export 'package:react/src/react_client/event_helpers.dart'; -typedef PropValidator = Error? Function(TProps props, PropValidatorInfo info); - /// A React component declared using a function that takes in [props] and returns rendered output. /// /// See . diff --git a/lib/src/prop_validator.dart b/lib/src/prop_validator.dart index 9cbecf07..5762f411 100644 --- a/lib/src/prop_validator.dart +++ b/lib/src/prop_validator.dart @@ -3,7 +3,7 @@ /// [info] is a [PropValidatorInfo] class that contains metadata about the prop referenced as /// the key within the `Component2.propTypes` map. /// `propName`, `componentName`, `location` and `propFullName` are available. -typedef PropValidator = Error Function(T props, PropValidatorInfo info); +typedef PropValidator = Error? Function(T props, PropValidatorInfo info); /// Metadata about a prop being validated by a [PropValidator]. class PropValidatorInfo { From f2ad196693977081049ad040e215187e281d264b Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Thu, 31 Aug 2023 14:35:34 -0700 Subject: [PATCH 22/53] Fix deprecated initial value in useRef not being passed along --- lib/hooks.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/hooks.dart b/lib/hooks.dart index 941623ce..12752b18 100644 --- a/lib/hooks.dart +++ b/lib/hooks.dart @@ -399,7 +399,7 @@ Ref useRef([ 'the generic parameter of the Ref returned by `useRefInit` can be non-nullable.') T? initialValue, ]) => - useRefInit(null); + useRefInit(initialValue); /// Returns a mutable [Ref] object with [Ref.current] property initialized to [initialValue]. /// From 0c561dc2462eda9b25bbacd43785bf89065411db Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Mon, 11 Sep 2023 11:15:00 -0700 Subject: [PATCH 23/53] Pull in RefTestCase changes from over_react Importantly, this fixes some callback ref typing issues after fixing unnecessary_lambdas lints --- test/factory/common_factory_tests.dart | 19 +++-- test/util.dart | 107 +++++++++++++++++++------ 2 files changed, 91 insertions(+), 35 deletions(-) diff --git a/test/factory/common_factory_tests.dart b/test/factory/common_factory_tests.dart index 864135e7..35abdd79 100644 --- a/test/factory/common_factory_tests.dart +++ b/test/factory/common_factory_tests.dart @@ -394,7 +394,7 @@ void refTests( rtu.renderIntoDocument(factory({ 'ref': testCase.ref, })); - final verifyFunction = testCase.isJs ? verifyJsRefValue! : verifyRefValue; + final verifyFunction = testCase.meta.isJs ? verifyJsRefValue! : verifyRefValue; verifyFunction(testCase.getCurrent()); }); } @@ -417,7 +417,7 @@ void refTests( rtu.renderIntoDocument(ForwardRefTestComponent({ 'ref': testCase.ref, })); - final verifyFunction = testCase.isJs ? verifyJsRefValue! : verifyRefValue; + final verifyFunction = testCase.meta.isJs ? verifyJsRefValue! : verifyRefValue; verifyFunction(testCase.getCurrent()); }); } @@ -438,8 +438,8 @@ void refTests( })); for (final testCase in testCases) { - final verifyFunction = testCase.isJs ? verifyJsRefValue! : verifyRefValue; - final valueToVerify = testCase.isJs ? refSpy.jsRef.current : refSpy.current; + final verifyFunction = testCase.meta.isJs ? verifyJsRefValue! : verifyRefValue; + final valueToVerify = testCase.meta.isJs ? refSpy.jsRef.current : refSpy.current; // Test setup check: verify refValue is correct, // which we'll use below to verify refs were updated. @@ -452,11 +452,10 @@ void refTests( test('ReactElement.ref', () { final testCases = testCaseCollection.createAllCases().map((testCase) { return RefTestCase( - name: testCase.name, ref: (factory({'ref': testCase.ref}) as ReactElement).ref, verifyRefWasUpdated: testCase.verifyRefWasUpdated, getCurrent: testCase.getCurrent, - isJs: testCase.isJs, + meta: testCase.meta, ); }).toList(); @@ -471,8 +470,8 @@ void refTests( })); for (final testCase in testCases) { - final verifyFunction = testCase.isJs ? verifyJsRefValue! : verifyRefValue; - final valueToVerify = testCase.isJs ? refSpy.jsRef.current : refSpy.current; + final verifyFunction = testCase.meta.isJs ? verifyJsRefValue! : verifyRefValue; + final valueToVerify = testCase.meta.isJs ? refSpy.jsRef.current : refSpy.current; // Test setup check: verify refValue is correct, // which we'll use below to verify refs were updated. @@ -502,8 +501,8 @@ void refTests( 'ref': testCase.ref, })); - final verifyFunction = testCase.isJs ? verifyJsRefValue! : verifyRefValue; - final valueToVerify = testCase.isJs ? refSpy.jsRef.current : refSpy.current; + final verifyFunction = testCase.meta.isJs ? verifyJsRefValue! : verifyRefValue; + final valueToVerify = testCase.meta.isJs ? refSpy.jsRef.current : refSpy.current; // Test setup check: verify refValue is correct, // which we'll use below to verify refs were updated. diff --git a/test/util.dart b/test/util.dart index 8dd45e41..454bcfcb 100644 --- a/test/util.dart +++ b/test/util.dart @@ -70,9 +70,6 @@ bool assertsEnabled() { /// Test cases should not be reused within a test or across multiple tests, to avoid /// the [ref] from being used by multiple components and its value being polluted. class RefTestCase { - /// The name of the test case. - final String name; - /// The ref to be passed into a component. final dynamic ref; @@ -82,16 +79,13 @@ class RefTestCase { /// Returns the current value of the ref. final dynamic Function() getCurrent; - /// Whether the ref is a non-Dart object, such as a ref originating from outside of Dart code - /// or a JS-converted Dart ref. - final bool isJs; + final RefTestCaseMeta meta; RefTestCase({ - required this.name, required this.ref, required this.verifyRefWasUpdated, required this.getCurrent, - this.isJs = false, + required this.meta, }); } @@ -106,60 +100,90 @@ class RefTestCaseCollection { } } + static const untypedCallbackRefCaseName = 'untyped callback ref'; + RefTestCase createUntypedCallbackRefCase() { - const name = 'untyped callback ref'; + const name = untypedCallbackRefCaseName; final calls = []; return RefTestCase( - name: name, - ref: calls.add, + // Use a lambda instead of a tearoff since we want to explicitly verify + // a function with a certain argument type. + // ignore: unnecessary_lambdas + ref: (value) => calls.add(value), verifyRefWasUpdated: (actualValue) => expect(calls, [same(actualValue)], reason: _reasonMessage(name)), getCurrent: () => calls.single, + meta: RefTestCaseMeta(name, RefKind.callback, isJs: false, isStronglyTyped: false), ); } + static const typedCallbackRefCaseName = 'typed callback ref'; + RefTestCase createTypedCallbackRefCase() { - const name = 'typed callback ref'; + const name = typedCallbackRefCaseName; final calls = []; return RefTestCase( - name: name, - ref: calls.add, + // Use a lambda instead of a tearoff since we want to explicitly verify + // a function with a certain argument type. + // ignore: unnecessary_lambdas, avoid_types_on_closure_parameters + ref: (T? value) => calls.add(value), verifyRefWasUpdated: (actualValue) => expect(calls, [same(actualValue)], reason: _reasonMessage(name)), getCurrent: () => calls.single, + meta: RefTestCaseMeta(name, RefKind.callback, isJs: false, isStronglyTyped: true), + ); + } + + static const untypedRefObjectCaseName = 'untyped ref object'; + + RefTestCase createUntypedRefObjectCase() { + const name = untypedRefObjectCaseName; + final ref = createRef(); + return RefTestCase( + ref: ref, + verifyRefWasUpdated: (actualValue) => expect(ref.current, same(actualValue), reason: _reasonMessage(name)), + getCurrent: () => ref.current, + meta: RefTestCaseMeta(name, RefKind.object, isJs: false, isStronglyTyped: false), ); } + static const refObjectCaseName = 'ref object'; + RefTestCase createRefObjectCase() { - const name = 'ref object'; + const name = refObjectCaseName; final ref = createRef(); return RefTestCase( - name: name, ref: ref, verifyRefWasUpdated: (actualValue) => expect(ref.current, same(actualValue), reason: _reasonMessage(name)), getCurrent: () => ref.current, + meta: RefTestCaseMeta(name, RefKind.object, isJs: false, isStronglyTyped: true), ); } + static const jsCallbackRefCaseName = 'JS callback ref'; + RefTestCase createJsCallbackRefCase() { - const name = 'JS callback ref'; + const name = jsCallbackRefCaseName; final calls = []; return RefTestCase( - name: name, - ref: allowInterop(calls.add), + // Use a lambda instead of a tearoff since we want to explicitly verify + // a function with a certain argument type. + // ignore: unnecessary_lambdas + ref: allowInterop((value) => calls.add(value)), verifyRefWasUpdated: (actualValue) => expect(calls, [same(actualValue)], reason: _reasonMessage(name)), getCurrent: () => calls.single, - isJs: true, + meta: RefTestCaseMeta(name, RefKind.callback, isJs: true, isStronglyTyped: false), ); } + static const jsRefObjectCaseName = 'JS ref object'; + RefTestCase createJsRefObjectCase() { - const name = 'JS ref object'; + const name = jsRefObjectCaseName; final ref = React.createRef(); return RefTestCase( - name: name, ref: ref, verifyRefWasUpdated: (actualValue) => expect(ref.current, same(actualValue), reason: _reasonMessage(name)), getCurrent: () => ref.current, - isJs: true, + meta: RefTestCaseMeta(name, RefKind.object, isJs: true, isStronglyTyped: false), ); } @@ -174,12 +198,45 @@ class RefTestCaseCollection { List createAllCases() => [ createUntypedCallbackRefCase(), createTypedCallbackRefCase(), + createUntypedRefObjectCase(), createRefObjectCase(), if (includeJsCallbackRefCase) createJsCallbackRefCase(), createJsRefObjectCase(), ]; - RefTestCase createCaseByName(String name) => createAllCases().singleWhere((c) => c.name == name); + RefTestCase createCaseByName(String name) => createAllCases().singleWhere((c) => c.meta.name == name); + + RefTestCaseMeta testCaseMetaByName(String name) => createCaseByName(name).meta; + + List get allTestCaseNames => allTestCaseMetas.map((m) => m.name).toList(); + + List get allTestCaseMetas => createAllCases().map((c) => c.meta).toList(); +} + +class RefTestCaseMeta { + final String name; + + final RefKind kind; + + /// Whether the ref is a non-Dart object, such as a ref originating from outside of Dart code + /// or a JS-converted Dart ref. + final bool isJs; + + final bool isStronglyTyped; + + const RefTestCaseMeta(this.name, this.kind, {this.isJs = false, this.isStronglyTyped = false}); + + @override + String toString() => '$name ($kind, isJs: $isJs, isStronglyTyped: $isStronglyTyped)'; +} + +enum RefKind { + object, + callback, +} + +extension RefKindBooleans on RefKind { + bool get isObject => this == RefKind.object; - List get allTestCaseNames => createAllCases().map((c) => c.name).toList(); + bool get isCallback => this == RefKind.callback; } From 61cc34d7d090a530128dc16c12cadd5c590ea110 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 12 Sep 2023 14:11:19 -0700 Subject: [PATCH 24/53] assert that callback ref arguments aren't non-nullable, add tests --- lib/react_client/component_factory.dart | 2 + lib/src/react_client/chain_refs.dart | 14 ++++-- lib/src/react_client/factory_util.dart | 24 +++++++++++ test/factory/common_factory_tests.dart | 6 ++- .../non_null_safe_refs.dart | 7 +++ .../null_safe_refs.dart | 11 +++++ ...ullable_callback_detection_sound_test.dart | 30 +++++++++++++ ...ullable_callback_detection_sound_test.html | 13 ++++++ ...lable_callback_detection_unsound_test.dart | 43 +++++++++++++++++++ ...lable_callback_detection_unsound_test.html | 13 ++++++ .../sound_null_safety_detection.dart | 29 +++++++++++++ test/react_client/chain_refs_test.dart | 6 ++- test/util.dart | 8 +++- 13 files changed, 199 insertions(+), 7 deletions(-) create mode 100644 test/nullable_callback_detection/non_null_safe_refs.dart create mode 100644 test/nullable_callback_detection/null_safe_refs.dart create mode 100644 test/nullable_callback_detection/nullable_callback_detection_sound_test.dart create mode 100644 test/nullable_callback_detection/nullable_callback_detection_sound_test.html create mode 100644 test/nullable_callback_detection/nullable_callback_detection_unsound_test.dart create mode 100644 test/nullable_callback_detection/nullable_callback_detection_unsound_test.html create mode 100644 test/nullable_callback_detection/sound_null_safety_detection.dart diff --git a/lib/react_client/component_factory.dart b/lib/react_client/component_factory.dart index 1ef8739d..377703eb 100644 --- a/lib/react_client/component_factory.dart +++ b/lib/react_client/component_factory.dart @@ -141,6 +141,8 @@ class ReactDartComponentFactoryProxy extends React // See https://github.com/dart-lang/sdk/issues/34593 for more information on arity checks. // ignore: prefer_void_to_null if (ref is Function(Never)) { + assert(!isRefArgumentDefinitelyNonNullable(ref), + nonNullableCallbackRefArgMessage); interopProps.ref = allowInterop((dynamic instance) { // Call as dynamic to perform dynamic dispatch, since we can't cast to CallbackRef, // and since calling with non-null values will fail at runtime due to the CallbackRef typing. diff --git a/lib/src/react_client/chain_refs.dart b/lib/src/react_client/chain_refs.dart index 61b2b485..1245e445 100644 --- a/lib/src/react_client/chain_refs.dart +++ b/lib/src/react_client/chain_refs.dart @@ -1,6 +1,7 @@ import 'package:js/js_util.dart'; import 'package:react/react.dart' as react; import 'package:react/react_client/react_interop.dart'; +import 'package:react/src/react_client/factory_util.dart'; /// Returns a ref that updates both [ref1] and [ref2], effectively /// allowing you to set multiple refs. @@ -84,10 +85,15 @@ dynamic chainRefList(List refs) { } void _validateChainRefsArg(dynamic ref) { - if (ref is Function(Never) || - ref is Ref || - // Need to duck-type since `is JsRef` will return true for most JS objects. - (ref is JsRef && hasProperty(ref, 'current'))) { + if (ref is Function(Never)) { + if (isRefArgumentDefinitelyNonNullable(ref)) { + throw AssertionError(nonNullableCallbackRefArgMessage); + } + return; + } + + // Need to duck-type since `is JsRef` will return true for most JS objects. + if (ref is Ref || (ref is JsRef && hasProperty(ref, 'current'))) { return; } diff --git a/lib/src/react_client/factory_util.dart b/lib/src/react_client/factory_util.dart index 85d20875..ff001837 100644 --- a/lib/src/react_client/factory_util.dart +++ b/lib/src/react_client/factory_util.dart @@ -55,6 +55,7 @@ void convertRefValue2( // See https://github.com/dart-lang/sdk/issues/34593 for more information on arity checks. // ignore: prefer_void_to_null } else if (ref is Function(Never) && convertCallbackRefValue) { + assert(!isRefArgumentDefinitelyNonNullable(ref), nonNullableCallbackRefArgMessage); args[refKey] = allowInterop((dynamic instance) { // Call as dynamic to perform dynamic dispatch, since we can't cast to _CallbackRef, // and since calling with non-null values will fail at runtime due to the _CallbackRef typing. @@ -68,6 +69,27 @@ void convertRefValue2( } } +const nonNullableCallbackRefArgMessage = + 'Arguments to callback ref functions must not be non-nullable, Since React null to callback refs when they\'re detached.' + '\nInstead of: `(MyComponent ref) { myComponentRef = ref; }`' + '\nDo either:' + '\n- `(MyComponent? ref) { myComponentRef = ref; }`' + '\n- `(ref) { myComponentRef = ref as MyComponent?; }'; + +/// Returns whether [props] contains a ref that is a callback ref with a non-nullable argument. +bool mapHasCallbackRefWithDefinitelyNonNullableArgument(Map props) { + final ref = props['ref']; + return ref is Function(Never) && isRefArgumentDefinitelyNonNullable(ref); +} + +/// Returns whether the argument to [callbackRef] is definitely a non-nullable type. +/// +/// "Definitely", since function will always return `false` when running under unsound null safety, +/// even if the ref argument is typed as non-nullable. +bool isRefArgumentDefinitelyNonNullable(Function(Never) callbackRef) { + return callbackRef is! Function(Null); +} + /// Converts a list of variadic children arguments to children that should be passed to ReactJS. /// /// Returns: @@ -117,5 +139,7 @@ JsMap generateJsProps(Map props, convertCallbackRefValue: convertCallbackRefValue, additionalRefPropKeys: additionalRefPropKeys); } + assert(!mapHasCallbackRefWithDefinitelyNonNullableArgument(propsForJs), nonNullableCallbackRefArgMessage); + return wrapWithJsify ? jsifyAndAllowInterop(propsForJs) as JsMap : propsForJs.jsObject; } diff --git a/test/factory/common_factory_tests.dart b/test/factory/common_factory_tests.dart index 35abdd79..c5044ee0 100644 --- a/test/factory/common_factory_tests.dart +++ b/test/factory/common_factory_tests.dart @@ -362,7 +362,7 @@ void domEventHandlerWrappingTests(ReactComponentFactoryProxy factory) { /// JS (e.g., react.Component vs ReactComponent for class based-components). /// If omitted, it defaults to [verifyRefValue]. /// It will be called with the actual ref value for JS refs, (e.g., JS ref objects as opposed to Dart objects) -void refTests( +void refTests( ReactComponentFactoryProxy factory, { required void Function(dynamic refValue) verifyRefValue, void Function(dynamic refValue)? verifyJsRefValue, @@ -406,6 +406,10 @@ void refTests( }); }); + test('asserts that callback ref arguments are nullable on ReactElement creation', () { + expect(() => factory({'ref': nonNullableCallbackRef}), throwsNonNullableCallbackRefAssertionError); + }, tags: 'no-dart2js'); + group('forwardRef2 function passes a ref through a component to one of its children, when the ref is a:', () { for (final name in testCaseCollection.allTestCaseNames) { test(name, () { diff --git a/test/nullable_callback_detection/non_null_safe_refs.dart b/test/nullable_callback_detection/non_null_safe_refs.dart new file mode 100644 index 00000000..6b5cf479 --- /dev/null +++ b/test/nullable_callback_detection/non_null_safe_refs.dart @@ -0,0 +1,7 @@ +//@dart=2.11 + +import 'dart:html'; + +void elementRef(Element _) {} + +void dynamicRef(dynamic _) {} diff --git a/test/nullable_callback_detection/null_safe_refs.dart b/test/nullable_callback_detection/null_safe_refs.dart new file mode 100644 index 00000000..2bbf68b9 --- /dev/null +++ b/test/nullable_callback_detection/null_safe_refs.dart @@ -0,0 +1,11 @@ +import 'dart:html'; + +import 'package:test/test.dart'; + +// Declare these separately so we can reference them from non-null-safe test files. + +void nonNullableElementRef(Element _) {} + +void nullableElementRef(Element? _) {} + +void dynamicRef(dynamic _) {} diff --git a/test/nullable_callback_detection/nullable_callback_detection_sound_test.dart b/test/nullable_callback_detection/nullable_callback_detection_sound_test.dart new file mode 100644 index 00000000..925f7de5 --- /dev/null +++ b/test/nullable_callback_detection/nullable_callback_detection_sound_test.dart @@ -0,0 +1,30 @@ +@TestOn('browser') +library react.test.unsound_null_safety_test; + +import 'package:react/src/react_client/factory_util.dart'; +import 'package:test/test.dart'; + +import 'null_safe_refs.dart' as null_safe_refs; +import 'sound_null_safety_detection.dart'; + +main() { + // This test ths the counterpart to nullable_callback_detection_unsound_test.dart, + // to verify expected behavior under sound null safety. + // See that file for more info. + group('nullable callback detection, when compiled with sound null safety:', () { + setUpAll(() { + expect(hasUnsoundNullSafety, isFalse, reason: 'these tests should be running under sound null safety'); + }); + + group('isRefArgumentDefinitelyNonNullable', () { + test('returns true for non-nullable refs', () { + expect(isRefArgumentDefinitelyNonNullable(null_safe_refs.nonNullableElementRef), isTrue); + }); + + test('returns false for nullable refs', () { + expect(isRefArgumentDefinitelyNonNullable(null_safe_refs.nullableElementRef), isFalse); + expect(isRefArgumentDefinitelyNonNullable(null_safe_refs.dynamicRef), isFalse); + }); + }); + }); +} diff --git a/test/nullable_callback_detection/nullable_callback_detection_sound_test.html b/test/nullable_callback_detection/nullable_callback_detection_sound_test.html new file mode 100644 index 00000000..a39e4db4 --- /dev/null +++ b/test/nullable_callback_detection/nullable_callback_detection_sound_test.html @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/test/nullable_callback_detection/nullable_callback_detection_unsound_test.dart b/test/nullable_callback_detection/nullable_callback_detection_unsound_test.dart new file mode 100644 index 00000000..7f71faad --- /dev/null +++ b/test/nullable_callback_detection/nullable_callback_detection_unsound_test.dart @@ -0,0 +1,43 @@ +// @dart=2.9 +@TestOn('browser') +library react.test.unsound_null_safety_test; + +import 'package:react/src/react_client/factory_util.dart'; +import 'package:test/test.dart'; + +import 'non_null_safe_refs.dart' as non_null_safe_refs; +import 'null_safe_refs.dart' as null_safe_refs; +import 'sound_null_safety_detection.dart'; + +main() { + // Since our callback ref non-nullable argument detection relies on runtime type-checking behavior + // that's different in null safety (`is Function(Null)`, we want to ensure we know how it behaves + // both in sound and unsound null safety runtime environments. + // + // So, this test file simulates that setup. + group('nullable callback detection, when compiled with unsound null safety:', () { + setUpAll(() { + expect(hasUnsoundNullSafety, isTrue, reason: 'these tests should be running under unsound null safety'); + }); + + group('isRefArgumentDefinitelyNonNullable:', () { + group('when refs are declared in null-safe code,', () { + test('returns false, even for non-nullable refs', () { + expect(isRefArgumentDefinitelyNonNullable(null_safe_refs.nonNullableElementRef), isFalse); + }); + + test('returns false for nullable refs', () { + expect(isRefArgumentDefinitelyNonNullable(null_safe_refs.nullableElementRef), isFalse); + expect(isRefArgumentDefinitelyNonNullable(null_safe_refs.dynamicRef), isFalse); + }); + }); + + group('when refs are declared in non-null-safe code,', () { + test('returns false', () { + expect(isRefArgumentDefinitelyNonNullable(non_null_safe_refs.elementRef), isFalse); + expect(isRefArgumentDefinitelyNonNullable(non_null_safe_refs.dynamicRef), isFalse); + }); + }); + }); + }); +} diff --git a/test/nullable_callback_detection/nullable_callback_detection_unsound_test.html b/test/nullable_callback_detection/nullable_callback_detection_unsound_test.html new file mode 100644 index 00000000..d2fc74da --- /dev/null +++ b/test/nullable_callback_detection/nullable_callback_detection_unsound_test.html @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/test/nullable_callback_detection/sound_null_safety_detection.dart b/test/nullable_callback_detection/sound_null_safety_detection.dart new file mode 100644 index 00000000..15b70742 --- /dev/null +++ b/test/nullable_callback_detection/sound_null_safety_detection.dart @@ -0,0 +1,29 @@ +// Taken from https://github.com/dart-lang/sdk/blob/252bfcb27aa14042ee07e081f25abf60cf6222b3/pkg/front_end/testcases/patterns/caching_constants.dart.textual_outline_modelled.expect#L3 +// Copyright 2012, the Dart project authors. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +bool get hasUnsoundNullSafety => const [] is List; + diff --git a/test/react_client/chain_refs_test.dart b/test/react_client/chain_refs_test.dart index 64c3aa9a..d257726e 100644 --- a/test/react_client/chain_refs_test.dart +++ b/test/react_client/chain_refs_test.dart @@ -32,7 +32,7 @@ main() { }); group('chainRefList', () { - // Chaining is tested functionally in refTests with each component type. + // Chaining is tested functionally in refTests with each component type and each ref type. group('skips chaining and passes through arguments when', () { test('the list is empty', () { @@ -74,6 +74,10 @@ main() { test('non-createRef JS interop objects', () { expect(() => chainRefList([testRef, JsType()]), throwsA(isA())); }); + + test('callback refs with non-nullable arguments', () { + expect(() => chainRefList([testRef, nonNullableCallbackRef]), throwsNonNullableCallbackRefAssertionError); + }); }, tags: 'no-dart2js'); test('does not raise an assertion for valid input refs', () { diff --git a/test/util.dart b/test/util.dart index 454bcfcb..57a7d50b 100644 --- a/test/util.dart +++ b/test/util.dart @@ -13,6 +13,7 @@ import 'package:react/react_client/js_backed_map.dart'; import 'package:react/react_client/react_interop.dart'; import 'package:react/react_test_utils.dart' as rtu; import 'package:react/src/js_interop_util.dart'; +import 'package:react/src/react_client/factory_util.dart'; import 'package:test/test.dart'; Map getProps(dynamic elementOrComponent) { @@ -91,7 +92,7 @@ class RefTestCase { /// A collection of methods that create [RefTestCase]s, combined into a class so that they can easily share a /// generic parameter [T] (the type of the Dart ref value). -class RefTestCaseCollection { +class RefTestCaseCollection { final bool includeJsCallbackRefCase; RefTestCaseCollection({this.includeJsCallbackRefCase = true}) { @@ -240,3 +241,8 @@ extension RefKindBooleans on RefKind { bool get isCallback => this == RefKind.callback; } + +final throwsNonNullableCallbackRefAssertionError = throwsA(isA() + .having((e) => e.message, 'message', allOf(contains(nonNullableCallbackRefArgMessage)))); + +void nonNullableCallbackRef(Object ref) {} From 11ca3a79ca72fc314e8a61e69a9f2d35fd4bd00f Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 12 Sep 2023 14:16:38 -0700 Subject: [PATCH 25/53] Make chainRefs AssertionError tests more specific --- test/react_client/chain_refs_test.dart | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/test/react_client/chain_refs_test.dart b/test/react_client/chain_refs_test.dart index d257726e..eb7b9664 100644 --- a/test/react_client/chain_refs_test.dart +++ b/test/react_client/chain_refs_test.dart @@ -54,25 +54,30 @@ main() { group('raises an assertion when inputs are invalid:', () { test('strings', () { - expect(() => chainRefList([testRef, 'bad ref']), throwsA(isA())); + expect(() => chainRefList([testRef, 'bad ref']), + throwsA(isA().havingMessage('String refs cannot be chained'))); }); test('unsupported function types', () { - expect(() => chainRefList([testRef, () {}]), throwsA(isA())); + expect(() => chainRefList([testRef, () {}]), + throwsA(isA().havingMessage('callback refs must take a single argument'))); }); test('other objects', () { - expect(() => chainRefList([testRef, Object()]), throwsA(isA())); + expect(() => chainRefList([testRef, Object()]), + throwsA(isA().havingMessage(contains('Invalid ref type')))); }); // test JS interop objects since type-checking anonymous interop objects test('non-createRef anonymous JS interop objects', () { - expect(() => chainRefList([testRef, JsTypeAnonymous()]), throwsA(isA())); + expect(() => chainRefList([testRef, JsTypeAnonymous()]), + throwsA(isA().havingMessage(contains('Invalid ref type')))); }); // test JS interop objects since type-checking anonymous interop objects test('non-createRef JS interop objects', () { - expect(() => chainRefList([testRef, JsType()]), throwsA(isA())); + expect(() => chainRefList([testRef, JsType()]), + throwsA(isA().havingMessage(contains('Invalid ref type')))); }); test('callback refs with non-nullable arguments', () { @@ -98,3 +103,7 @@ class JsTypeAnonymous { class JsType { external JsType(); } + +extension on TypeMatcher { + TypeMatcher havingMessage(dynamic matcher) => having((e) => e.message, 'message', matcher); +} From abe57357aed3aca2540166a07426b956a761b7f1 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 12 Sep 2023 14:20:18 -0700 Subject: [PATCH 26/53] Remove unused import --- test/nullable_callback_detection/null_safe_refs.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/nullable_callback_detection/null_safe_refs.dart b/test/nullable_callback_detection/null_safe_refs.dart index 2bbf68b9..1af4ab56 100644 --- a/test/nullable_callback_detection/null_safe_refs.dart +++ b/test/nullable_callback_detection/null_safe_refs.dart @@ -1,7 +1,5 @@ import 'dart:html'; -import 'package:test/test.dart'; - // Declare these separately so we can reference them from non-null-safe test files. void nonNullableElementRef(Element _) {} From 37a47f6edf3bcdf5ad7601f7113673b5eeaa7a51 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 12 Sep 2023 14:25:27 -0700 Subject: [PATCH 27/53] Remove `implements Function` which has had no effect since Dart 2.0 --- lib/react.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/react.dart b/lib/react.dart index 66316f12..10f8da50 100644 --- a/lib/react.dart +++ b/lib/react.dart @@ -1352,7 +1352,7 @@ mixin TypedSnapshot { } /// Creates a ReactJS virtual DOM instance ([ReactElement] on the client). -abstract class ReactComponentFactoryProxy implements Function { +abstract class ReactComponentFactoryProxy { /// The type of component created by this factory. get type; From a6f96f8729b88bcbfdf9d56e552528d358e4b7b2 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 12 Sep 2023 14:26:07 -0700 Subject: [PATCH 28/53] Remove unnecessary ReactElement casts --- test/factory/common_factory_tests.dart | 2 +- test/react_test_utils_test.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/factory/common_factory_tests.dart b/test/factory/common_factory_tests.dart index c5044ee0..e0721095 100644 --- a/test/factory/common_factory_tests.dart +++ b/test/factory/common_factory_tests.dart @@ -456,7 +456,7 @@ void refTests( test('ReactElement.ref', () { final testCases = testCaseCollection.createAllCases().map((testCase) { return RefTestCase( - ref: (factory({'ref': testCase.ref}) as ReactElement).ref, + ref: factory({'ref': testCase.ref}).ref, verifyRefWasUpdated: testCase.verifyRefWasUpdated, getCurrent: testCase.getCurrent, meta: testCase.meta, diff --git a/test/react_test_utils_test.dart b/test/react_test_utils_test.dart index 83e2f354..11d4e485 100644 --- a/test/react_test_utils_test.dart +++ b/test/react_test_utils_test.dart @@ -257,7 +257,7 @@ void testUtils({ }); test('returns false when element is not a composite component (created with React.createClass())', () { - final component = renderIntoDocument(div({}) as ReactElement); + final component = renderIntoDocument(div({})); expect(isCompositeComponent(component), isFalse); }); From 069f4788104b61cdc2a3f96f4eb237937f994ba9 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 12 Sep 2023 14:44:56 -0700 Subject: [PATCH 29/53] Fix most remaining implicit casts --- lib/src/js_interop_util.dart | 2 +- lib/src/react_client/private_utils.dart | 8 ++-- test/factory/common_factory_tests.dart | 2 +- test/forward_ref_test.dart | 4 +- test/js_builds/shared_tests.dart | 2 +- .../react_client/js_interop_helpers_test.dart | 48 ++++++++++--------- test/react_test_utils_test.dart | 20 ++++---- test/util.dart | 12 +++-- 8 files changed, 52 insertions(+), 46 deletions(-) diff --git a/lib/src/js_interop_util.dart b/lib/src/js_interop_util.dart index b499915a..6ed0e942 100644 --- a/lib/src/js_interop_util.dart +++ b/lib/src/js_interop_util.dart @@ -6,7 +6,7 @@ import 'dart:js_util'; import 'package:js/js.dart'; @JS('Object.keys') -external List objectKeys(Object object); +external List objectKeys(Object object); @JS() @anonymous diff --git a/lib/src/react_client/private_utils.dart b/lib/src/react_client/private_utils.dart index d0ddb016..a0129aad 100644 --- a/lib/src/react_client/private_utils.dart +++ b/lib/src/react_client/private_utils.dart @@ -34,10 +34,10 @@ T validateJsApiThenReturn(T Function() computeReturn) { @Deprecated('7.0.0') Map unjsifyContext(InteropContextValue interopContext) { // TODO consider using `contextKeys` for this if perf of objectKeys is bad. - return Map.fromIterable(objectKeys(interopContext), value: (key) { - final internal = getProperty(interopContext, key) as ReactDartContextInternal?; - return internal?.value; - }); + return { + for (final key in objectKeys(interopContext)) + key: (getProperty(interopContext, key) as ReactDartContextInternal?)?.value + }; } /// Validates that React JS has been loaded and its APIs made available on the window, diff --git a/test/factory/common_factory_tests.dart b/test/factory/common_factory_tests.dart index e0721095..adb98a03 100644 --- a/test/factory/common_factory_tests.dart +++ b/test/factory/common_factory_tests.dart @@ -638,4 +638,4 @@ extension on String { } @JS('React.addons.TestUtils.Simulate') -external dynamic get _Simulate; +external Object get _Simulate; diff --git a/test/forward_ref_test.dart b/test/forward_ref_test.dart index d8caf1fd..bf908fd8 100644 --- a/test/forward_ref_test.dart +++ b/test/forward_ref_test.dart @@ -31,13 +31,13 @@ main() { group('sets name on the rendered component as expected', () { test('unless the displayName argument is not passed to forwardRef2', () { final ForwardRefTestComponent = forwardRef2((props, ref) {}); - expect(getProperty(getProperty(ForwardRefTestComponent.type, 'render'), 'name'), anyOf('', isNull)); + expect(getProperty(getProperty(ForwardRefTestComponent.type as Object, 'render'), 'name'), anyOf('', isNull)); }); test('when displayName argument is passed to forwardRef2', () { const name = 'ForwardRefTestComponent'; final ForwardRefTestComponent = forwardRef2((props, ref) {}, displayName: name); - expect(getProperty(getProperty(ForwardRefTestComponent.type, 'render'), 'name'), name); + expect(getProperty(getProperty(ForwardRefTestComponent.type as Object, 'render'), 'name'), name); }); }); }); diff --git a/test/js_builds/shared_tests.dart b/test/js_builds/shared_tests.dart index c4d47432..078c1dde 100644 --- a/test/js_builds/shared_tests.dart +++ b/test/js_builds/shared_tests.dart @@ -172,7 +172,7 @@ void sharedErrorBoundaryComponentNameTests() { test('Component components', () { expectRenderErrorWithComponentName( - _ThrowingComponent({}), + _ThrowingComponent({}) as ReactElement, expectedComponentName: r'DisplayName$_ThrowingComponent', ); }); diff --git a/test/react_client/js_interop_helpers_test.dart b/test/react_client/js_interop_helpers_test.dart index 307564ee..2fe397ca 100644 --- a/test/react_client/js_interop_helpers_test.dart +++ b/test/react_client/js_interop_helpers_test.dart @@ -53,12 +53,16 @@ class Foo { @JS() external dynamic createArray(); +/// Like jsifyAndAllowInterop, but casts to Object +Object jsifyAndAllowInteropAsObject(Object value) => jsifyAndAllowInterop(value) as Object; + main() { - group('jsifyAndAllowInterop', () { + group('jsifyAndAllowInteropAsObject', () { test('converts a List', () { final list = [1, 2, 3, 4, 5, 6, 7, 8]; - final array = jsifyAndAllowInterop(list); + final array = jsifyAndAllowInteropAsObject(list); expect(array is List, isTrue); + array as List; expect(identical(array, list), isFalse); expect(array.length, equals(list.length)); for (var i = 0; i < list.length; i++) { @@ -68,7 +72,7 @@ main() { test('converts an Iterable', () { final set = {1, 2, 3, 4, 5, 6, 7, 8}; - final array = jsifyAndAllowInterop(set); + final array = jsifyAndAllowInteropAsObject(set); expect(array is List, isTrue); expect((array as List).length, equals(set.length)); for (var i = 0; i < array.length; i++) { @@ -78,7 +82,7 @@ main() { test('converts a Map', () { final map = {'a': 1, 'b': 2, 'c': 3}; - final jsMap = jsifyAndAllowInterop(map); + final jsMap = jsifyAndAllowInteropAsObject(map); expect(jsMap is List, isFalse); expect(jsMap is Map, isFalse); for (final key in map.keys) { @@ -88,7 +92,7 @@ main() { test('converts nested Functions', () { function() {} - final converted = jsifyAndAllowInterop({'function': function}); + final converted = jsifyAndAllowInteropAsObject({'function': function}); expect(getProperty(converted, 'function'), same(allowInterop(function))); }); @@ -101,14 +105,14 @@ main() { 'b': {'c': 3, 'd': Foo(42)}, 'e': null } as dynamic; - final jsObject = jsifyAndAllowInterop(object); + final jsObject = jsifyAndAllowInterop(object) as Object; expect(getProperty(jsObject, 'a')[0], equals(object['a'][0])); expect(getProperty(jsObject, 'a')[1][0], equals(object['a'][1][0])); expect(getProperty(jsObject, 'a')[1][1], equals(object['a'][1][1])); - final b = getProperty(jsObject, 'b'); + final b = getProperty(jsObject, 'b') as Object; expect(getProperty(b, 'c'), equals(object['b']['c'])); - final d = getProperty(b, 'd'); + final d = getProperty(b, 'd') as Object; expect(d, equals(object['b']['d'])); expect(getProperty(d, 'a'), equals(42)); expect(callMethod(d, 'bar', []), equals(42)); @@ -117,21 +121,21 @@ main() { }); test('throws if object is not a Map or Iterable', () { - expect(() => jsifyAndAllowInterop('a'), throwsArgumentError); + expect(() => jsifyAndAllowInteropAsObject('a'), throwsArgumentError); }); test('does not result in unwanted properties (e.g., \$identityHash) being added to converted JS objects', () { - final nestedJsObject = jsifyAndAllowInterop({'foo': 'bar'}); + final nestedJsObject = jsifyAndAllowInteropAsObject({'foo': 'bar'}); const expectedProperties = ['foo']; expect(objectKeys(nestedJsObject), expectedProperties, reason: 'test setup check'); - // We want to include a JS object in jsifyAndAllowInterop - final jsObject = jsifyAndAllowInterop({ + // We want to include a JS object in jsifyAndAllowInteropAsObject + final jsObject = jsifyAndAllowInteropAsObject({ 'nestedJsObject': nestedJsObject, }); final convertedNestedJsObject = getProperty(jsObject, 'nestedJsObject'); expect(convertedNestedJsObject, same(nestedJsObject), reason: 'JS object should have just gotten passed through'); - expect(objectKeys(convertedNestedJsObject), expectedProperties, + expect(objectKeys(convertedNestedJsObject as Object), expectedProperties, reason: 'JS object should not have any additional properties'); }); @@ -144,9 +148,9 @@ main() { _freeze(frozenAnonymousObject); expect(_isFrozen(frozenAnonymousObject), isTrue, reason: 'test setup check; should have frozen'); - dynamic jsObject; + late Object jsObject; expect(() { - jsObject = jsifyAndAllowInterop({ + jsObject = jsifyAndAllowInteropAsObject({ 'frozenObject': frozenAnonymousObject, }); }, returnsNormally, reason: 'should not throw when it encounters a frozen object'); @@ -163,9 +167,9 @@ main() { _freeze(frozenNonAnynymousObject); expect(_isFrozen(frozenNonAnynymousObject), isTrue, reason: 'test setup check; should have frozen'); - dynamic jsObject; + late Object jsObject; expect(() { - jsObject = jsifyAndAllowInterop({ + jsObject = jsifyAndAllowInteropAsObject({ 'frozenObject': frozenNonAnynymousObject, }); }, returnsNormally, reason: 'should not throw when it encounters a frozen object'); @@ -183,9 +187,9 @@ main() { _freeze(frozenArray); expect(_isFrozen(frozenArray), isTrue, reason: 'test setup check; should have frozen'); - dynamic jsObject; + late Object jsObject; expect(() { - jsObject = jsifyAndAllowInterop({ + jsObject = jsifyAndAllowInteropAsObject({ 'frozenArray': frozenArray, }); }, returnsNormally, reason: 'should not throw when it encounters a frozen object'); @@ -206,10 +210,10 @@ external dynamic get _objectPrototype; external dynamic get _arrayPrototype; @JS('Object.freeze') -external void _freeze(Object object); +external void _freeze(dynamic object); @JS('Object.isFrozen') -external bool _isFrozen(Object object); +external bool _isFrozen(dynamic object); @JS('Object.getPrototypeOf') -external dynamic _getPrototypeOf(Object object); +external dynamic _getPrototypeOf(dynamic); diff --git a/test/react_test_utils_test.dart b/test/react_test_utils_test.dart index 11d4e485..e78e9d06 100644 --- a/test/react_test_utils_test.dart +++ b/test/react_test_utils_test.dart @@ -70,7 +70,7 @@ void testUtils({ late Element domNode; setUp(() { - component = renderIntoDocument(eventComponent({})); + component = renderIntoDocument(eventComponent({})) as Object; domNode = findDomNode(component); expect(domNode.text, equals('')); }); @@ -231,14 +231,14 @@ void testUtils({ test('findRenderedDOMComponentWithClass on a Component${isComponent2 ? "2" : ""}', () { final component = renderIntoDocument(sampleComponent({})); - final spanComponent = findRenderedDOMComponentWithClass(component, 'span1'); + final spanComponent = findRenderedDOMComponentWithClass(component, 'span1') as Element; expect(getProperty(spanComponent, 'tagName'), equals('SPAN')); }); test('findRenderedDOMComponentWithTag on a Component${isComponent2 ? "2" : ""}', () { final component = renderIntoDocument(sampleComponent({})); - final h1Component = findRenderedDOMComponentWithTag(component, 'h1'); + final h1Component = findRenderedDOMComponentWithTag(component, 'h1') as Element; expect(getProperty(h1Component, 'tagName'), equals('H1')); }); @@ -329,22 +329,22 @@ void testUtils({ span({}) ])); - final results = scryRenderedDOMComponentsWithClass(component, 'divClass'); + final results = scryRenderedDOMComponentsWithClass(component, 'divClass').cast(); expect(results.length, 2); - expect(getProperty(results[0], 'tagName'), equals('DIV')); - expect(getProperty(results[1], 'tagName'), equals('DIV')); + expect(results[0].tagName, equals('DIV')); + expect(results[1].tagName, equals('DIV')); }); test('scryRenderedDOMComponentsWithTag', () { final component = renderIntoDocument(wrapperComponent({}, [div({}), div({}), span({})])); - final results = scryRenderedDOMComponentsWithTag(component, 'div'); + final results = scryRenderedDOMComponentsWithTag(component, 'div').cast(); expect(results.length, 3); - expect(getProperty(results[0], 'tagName'), equals('DIV')); - expect(getProperty(results[1], 'tagName'), equals('DIV')); - expect(getProperty(results[2], 'tagName'), equals('DIV')); + expect(results[0].tagName, equals('DIV')); + expect(results[1].tagName, equals('DIV')); + expect(results[2].tagName, equals('DIV')); }); test('renderIntoDocument with a Component${isComponent2 ? "2" : ""}', () { diff --git a/test/util.dart b/test/util.dart index 57a7d50b..f8e69994 100644 --- a/test/util.dart +++ b/test/util.dart @@ -4,7 +4,6 @@ library react.test.util; import 'dart:html'; -import 'dart:js_util' show getProperty; import 'package:js/js.dart'; import 'package:react/react.dart' as react; @@ -12,14 +11,17 @@ import 'package:react/react_dom.dart' as react_dom; import 'package:react/react_client/js_backed_map.dart'; import 'package:react/react_client/react_interop.dart'; import 'package:react/react_test_utils.dart' as rtu; -import 'package:react/src/js_interop_util.dart'; import 'package:react/src/react_client/factory_util.dart'; import 'package:test/test.dart'; Map getProps(dynamic elementOrComponent) { - final props = elementOrComponent.props; - - return Map.fromIterable(objectKeys(props), value: (key) => getProperty(props, key)); + InteropProps props; + if (React.isValidElement(elementOrComponent)) { + props = (elementOrComponent as ReactElement).props; + } else { + props = (elementOrComponent as ReactComponent).props; + } + return JsBackedMap.backedBy(props); } bool isDartComponent1(ReactElement element) => From b9881a15dcb4d2034e4da887149b54dad84088d2 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 12 Sep 2023 16:13:10 -0700 Subject: [PATCH 30/53] Add types to top-level DOM factories, clean up casts --- lib/react.dart | 403 +++++++++++++------------ test/factory/common_factory_tests.dart | 2 +- test/factory/dom_factory_test.dart | 15 +- test/react_test_utils_test.dart | 7 +- test/util_test.dart | 9 +- 5 files changed, 214 insertions(+), 222 deletions(-) diff --git a/lib/react.dart b/lib/react.dart index 10f8da50..570f3048 100644 --- a/lib/react.dart +++ b/lib/react.dart @@ -16,7 +16,7 @@ import 'package:react/react_client.dart'; import 'package:react/react_client/react_interop.dart'; import 'package:react/src/context.dart'; import 'package:react/src/react_client/component_registration.dart' as registration_utils; -import 'package:react/src/react_client/private_utils.dart' show validateJsApiThenReturn; +import 'package:react/src/react_client/private_utils.dart' show validateJsApi, validateJsApiThenReturn; export 'package:react/src/context.dart'; export 'package:react/src/prop_validator.dart'; @@ -1513,598 +1513,603 @@ ComponentRegistrar2 registerComponent2 = validateJsApiThenReturn(() => registrat FunctionComponentRegistrar registerFunctionComponent = validateJsApiThenReturn(() => registration_utils.registerFunctionComponent); +ReactDomComponentFactoryProxy _createDomFactory(String tagName) { + validateJsApi(); + return ReactDomComponentFactoryProxy(tagName); +} + /// The HTML `` `AnchorElement`. -dynamic a = validateJsApiThenReturn(() => ReactDomComponentFactoryProxy('a')); +ReactDomComponentFactoryProxy a = _createDomFactory('a'); /// The HTML `` `Element`. -dynamic abbr = validateJsApiThenReturn(() => ReactDomComponentFactoryProxy('abbr')); +ReactDomComponentFactoryProxy abbr = _createDomFactory('abbr'); /// The HTML `
` `Element`. -dynamic address = validateJsApiThenReturn(() => ReactDomComponentFactoryProxy('address')); +ReactDomComponentFactoryProxy address = _createDomFactory('address'); /// The HTML `` `AreaElement`. -dynamic area = validateJsApiThenReturn(() => ReactDomComponentFactoryProxy('area')); +ReactDomComponentFactoryProxy area = _createDomFactory('area'); /// The HTML `
` `Element`. -dynamic article = validateJsApiThenReturn(() => ReactDomComponentFactoryProxy('article')); +ReactDomComponentFactoryProxy article = _createDomFactory('article'); /// The HTML `