Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CPLAT-16997 Fix selector hooks redrawing when values haven't changed #730

Merged
merged 20 commits into from
Feb 3, 2022
Merged
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
64d087e
Fix selectors always returning new objects by conditionally wrapping …
greglittlefield-wf Feb 2, 2022
c01d310
Reuse wrapped functions
greglittlefield-wf Feb 2, 2022
2c68978
Add regression tests
greglittlefield-wf Feb 2, 2022
5e12f20
Fix missing arg causing swallowed RTEs
greglittlefield-wf Feb 2, 2022
4dfb35d
Fix missing arg causing swallowed RTEs for createSelectorHook
greglittlefield-wf Feb 2, 2022
656cfca
Add some shared tests for different selected value types
greglittlefield-wf Feb 2, 2022
df95525
Add test coverage for createSelector, add null case and additional se…
greglittlefield-wf Feb 3, 2022
d472036
Use typed props
greglittlefield-wf Feb 3, 2022
498c594
Fix typo
greglittlefield-wf Feb 3, 2022
2e74a56
Update typing from dynamic to Object to prevent unintentional implici…
greglittlefield-wf Feb 3, 2022
4202bc5
Revert changes in old tests
greglittlefield-wf Feb 3, 2022
f4f3ae4
Remove unused dependency
greglittlefield-wf Feb 3, 2022
5973053
Add more test coverage
greglittlefield-wf Feb 3, 2022
2880847
Cleanup
greglittlefield-wf Feb 3, 2022
7cee6c0
Add test coverage for value identity
greglittlefield-wf Feb 3, 2022
abe3425
Add additional test coverage for connect
greglittlefield-wf Feb 3, 2022
e7cabbd
Add comment
greglittlefield-wf Feb 3, 2022
b199497
Add useSelector example
greglittlefield-wf Feb 3, 2022
b55e993
Update doc comment to not get flagged by dependency_validator
greglittlefield-wf Feb 3, 2022
cb65a0e
Fix copyright headers
greglittlefield-wf Feb 3, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 46 additions & 47 deletions test/over_react_redux/hooks/use_selector_test.dart
Original file line number Diff line number Diff line change
@@ -20,81 +20,50 @@ import 'package:redux/redux.dart';
import 'package:test/test.dart';

import '../../test_util/test_util.dart';
import '../utils.dart';

part 'use_selector_test.over_react.g.dart';

class TestState<T> {
final T interestingValue;
final int otherValue;

TestState({
@required this.interestingValue,
@required this.otherValue,
});

TestState<T> update({
T interestingValue,
int otherValue,
}) =>
TestState(
interestingValue: interestingValue ?? this.interestingValue,
otherValue: otherValue ?? this.otherValue,
);
}

class UpdateInterestingAction {}

class UpdateOtherAction {}

class MyDartObject {
final String name;

MyDartObject(this.name);

@override
String toString() => 'MyDartObject($name)';
}

main() {
void main() {
group('selector hooks', () {
group('when selecting', () {
sharedTests<String>(
sharedSelectorHookTests<String>(
'a primitive value',
initialValue: 'initial',
updatedValue1: 'updated 1',
updatedValue2: 'updated 2',
);

sharedTests<dynamic>(
sharedSelectorHookTests<dynamic>(
'a primitive value including null',
initialValue: null,
updatedValue1: 'updated 1',
updatedValue2: 'updated 2',
);

sharedTests<MyDartObject>(
sharedSelectorHookTests<MyDartObject>(
'a Dart object',
initialValue: MyDartObject('initial'),
updatedValue1: MyDartObject('updated 1'),
updatedValue2: MyDartObject('updated 2'),
);

sharedTests<Map>(
sharedSelectorHookTests<Map>(
'a Dart Map (which shouldn\'t get converted to a JS Map)',
initialValue: {'initial': 'value'},
updatedValue1: {'updated 1': 'value'},
updatedValue2: {'updated 2': 'value'},
);

sharedTests<String Function()>(
sharedSelectorHookTests<String Function()>(
'a Dart function (which requires special interop wrapping)',
initialValue: () => 'initial',
updatedValue1: () => 'updated 1',
updatedValue2: () => 'updated 2',
renderValue: (function) => function(),
);

sharedTests<String Function()>(
sharedSelectorHookTests<String Function()>(
'an allowInteropped Dart function (which should bypass special interop wrapping)',
initialValue: allowInterop(() => 'initial'),
updatedValue1: allowInterop(() => 'updated 1'),
@@ -108,18 +77,11 @@ main() {
Matcher hasAttrs(Map<String, dynamic> attrs) =>
allOf(attrs.entries.map((e) => hasAttr(e.key, e.value)).toList());

Future<void> dispatchAndWait(Store store, dynamic action) async {
final storeChangeFuture = store.onChange.first;
store.dispatch(action);
await storeChangeFuture.timeout(Duration(milliseconds: 100));
await pumpEventQueue();
}

// fixme
// - verify selected value identity?

@isTestGroup
void sharedTests<T>(
void sharedSelectorHookTests<T>(
String name, {
@required T initialValue,
@required T updatedValue1,
@@ -334,6 +296,40 @@ void sharedTests<T>(
});
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests have moved to store_bindings_tests.dart. I started by refactoring them to use custom components so they could be run with multiple types of selected values, and they eventually morphed into tests that could also be shared with connect, thus the new file name.


class TestState<T> {
final T interestingValue;
final int otherValue;

TestState({
@required this.interestingValue,
@required this.otherValue,
});

TestState<T> update({
T interestingValue,
int otherValue,
}) =>
TestState(
interestingValue: interestingValue ?? this.interestingValue,
otherValue: otherValue ?? this.otherValue,
);
}

/// Updates [TestState.interestingValue].
class UpdateInterestingAction {}

/// Updates [TestState.otherValue].
class UpdateOtherAction {}

class MyDartObject {
final String name;

MyDartObject(this.name);

@override
String toString() => 'MyDartObject($name)';
}

// We need this to generate _$TestSelectorConfig for use in non-top-level components.
UiFactory<TestSelectorProps> TestSelector = uiFunction(
(_) {},
@@ -344,6 +340,9 @@ mixin TestSelectorProps on UiProps {
bool Function(dynamic, dynamic) equality;
}

/// A hook that returns a count of calls to a function component's render, specific to that instance.
///
/// The result of the first call is `1`.
int useRenderCount() {
final count = useRef(0);
count.current++;
14 changes: 14 additions & 0 deletions test/over_react_redux/utils.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import 'package:redux/redux.dart';
import 'package:test/test.dart';

/// Dispatches an action on a redux store, waits for the store to emit an onChange event,
/// and then waits for a [pumpEventQueue].
///
/// This can be used to dispatch an action and ensure it has been received by any subscribers,
/// which is helpful for verifying that something _doesn't_ happen in response to a store update.
Future<void> dispatchAndWait(Store store, dynamic action) async {
final storeChangeFuture = store.onChange.first;
store.dispatch(action);
await storeChangeFuture.timeout(Duration(milliseconds: 100));
await pumpEventQueue();
}