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

FED-279: Add improved prop forwarding method #829

Merged
merged 34 commits into from
Aug 14, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
07c20b0
spike for unused props concept
kealjones-wk Aug 1, 2022
e7c1eba
Merge branch 'master' into unused_props
kealjones-wk Jul 6, 2023
69f836f
I heard you liked forwarding props...
kealjones-wk Jul 19, 2023
09cc932
remove `include`
kealjones-wk Jul 20, 2023
638aa33
add some dom tests
kealjones-wk Jul 21, 2023
a1f18b9
Merge branch 'master' into unused_props
kealjones-wk Jul 26, 2023
37aae20
uncomment old tests and fix generic test
kealjones-wk Jul 26, 2023
a6007d7
fix warnings
kealjones-wk Jul 26, 2023
4c7a63f
fix PropsToForward doc comment
kealjones-wk Jul 26, 2023
98fe1d3
formatting
kealjones-wk Jul 26, 2023
4b76cc2
simplify consumedProps assignment in _propsToForward
kealjones-wk Jul 26, 2023
2e30341
remove commented out code
kealjones-wk Jul 26, 2023
7b2799b
format
kealjones-wk Jul 26, 2023
8a5f7d8
fix doc comments
kealjones-wk Jul 26, 2023
91a8572
add prop forwarding demo to web
kealjones-wk Jul 27, 2023
3c39d20
add test for error case
kealjones-wk Aug 9, 2023
fb07ac0
address feedback
kealjones-wk Aug 9, 2023
31a9bad
fix error test
kealjones-wk Aug 9, 2023
f8d89de
add rethrow to propsToForward try catch
kealjones-wk Aug 9, 2023
80b2bab
only run in ddc
kealjones-wk Aug 9, 2023
b20ec36
maybe fix test?
kealjones-wk Aug 9, 2023
a068cbb
fix assertion logic
kealjones-wk Aug 9, 2023
bc1a78c
uncomment out tests
kealjones-wk Aug 9, 2023
1e383db
lets not use such a complex demo
kealjones-wk Aug 10, 2023
e43030d
update docs?
kealjones-wk Aug 10, 2023
5b1de45
remove the line termination change
kealjones-wk Aug 10, 2023
0ad2e6d
Revert "remove the line termination change"
kealjones-wk Aug 10, 2023
d0b6d7d
Revert "update docs?"
kealjones-wk Aug 10, 2023
23c11e9
add other doc updates
kealjones-wk Aug 10, 2023
eef837c
update migration guide?
kealjones-wk Aug 10, 2023
81010e5
another doc update
kealjones-wk Aug 10, 2023
6bfad61
address feedback
kealjones-wk Aug 11, 2023
cfd533e
Update test/over_react/component_declaration/builder_integration_test…
kealjones-wk Aug 14, 2023
3a82784
Fix improper `having` use from suggestion
kealjones-wk Aug 14, 2023
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
85 changes: 85 additions & 0 deletions lib/src/component_declaration/builder_helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,91 @@ abstract class UiProps extends component_base.UiProps with GeneratedClass {
@toBeGenerated PropsMetaCollection get staticMeta => throw UngeneratedError(member: #meta);
}


/*
UiComponent:
..addProps(copyUnconsumedProps())

UiComponent2:
..modifyProps(addUnconsumedProps)

UiFunction and UiComponent2:
final consumedProps = props.staticMeta.forMixins({BarPropsMixin});
...
..addUnconsumedProps(props, consumedProps)


Proposed:
// By default the method not return props for the class it is being called on which makes it super easy and convenient.
// eg. if `props` is of type `MyPropsMixin` then it would be equivalent to something like `props.getUnconsumed(consumed: {MyPropsMixin})`


..addAll(props.getUnconsumed())

..addAll(props.getUnconsumed({MyProps}));
..addAll(props.getUnconsumed(consumed: {MyProps}));
..addAll(props.getUnconsumedProps(consumedPropsMixins: {MyProps}))

..addAll(props.getUnconsumed(exclude: {MyProps}))

*/

extension UnusedProps<T extends UiProps> on T {

/// Returns this instance's props map excluding the keys found in [consumedMixins].
///
/// [consumedMixins] should be a `Set` of PropsMixin `Type`s.
/// If [consumedMixins] is not set it defaults to using the current instances Type.
///
/// __Example:__
///
/// ```dart
/// // within a functional component: `uiFunction<FooPropsMixin>`
/// // filter out the current components props when forwarding to Bar.
/// return (Bar()..addAll(props.getConsumed()))();
/// ```
/// OR
/// ```dart
/// // within a functional component that has multiple mixins on a Props class: `uiFunction<FooProps>`
/// // filter out the Components props when forwarding to Bar.
/// return (Bar()..addAll(props.getConsumed(consumedMixins:{FooPropsMixin}))();
/// ```
///
/// To only add DOM props, use [addUnconsumedDomProps].
///
/// Related: `UiComponent2`'s `addUnconsumedProps`
Map getPropsToForward({Set<Type> exclude, Set<Type> include, bool domOnly = false}) {
final useDefaultExclude = exclude == null && (include == null || (include is Set && include.isEmpty));
try {
Iterable<PropsMeta> includedProps = [];
final unconsumedProps = {};
kealjones-wk marked this conversation as resolved.
Show resolved Hide resolved
Iterable<PropsMeta> consumedProps;
List<List<String>> consumedPropKeys;
final excludedProps = staticMeta.forMixins(useDefaultExclude ? {T} : exclude ?? {});
final excludedPropKeys = excludedProps.map((consumeProps) => consumeProps.keys).toList();

if (include != null) {
kealjones-wk marked this conversation as resolved.
Show resolved Hide resolved
includedProps = staticMeta.allExceptForMixins(include);
consumedProps = includedProps.toList()..removeWhere((element) => excludedPropKeys.any((ex) => element.keys.any((el) => !ex.contains(el))));
consumedPropKeys = consumedProps.map((consumedProps) => consumedProps.keys).toList();
} else {
consumedProps = excludedProps;
consumedPropKeys = consumedProps.map((consumedProps) => consumedProps.keys).toList();
}

forwardUnconsumedPropsV2(props, propsToUpdate: unconsumedProps, keySetsToOmit: consumedPropKeys, onlyCopyDomProps: domOnly);
return unconsumedProps;
} catch(_) {
kealjones-wk marked this conversation as resolved.
Show resolved Hide resolved
if (useDefaultExclude) {
throw ArgumentError('Could not find props meta for type $T.'
' If this is not a props mixin, you need to specify the mixins to be excluded as the second argument to `getUnconsumed`. For example:'
'\n ..addAll(props.getUnconsumed({${T}Mixin}, …)');
}
rethrow;
}
}
}

/// A [dart.collection.MapView]-like class with strongly-typed getters/setters for React state.
///
/// To be used with the over_react builder to generate concrete state implementations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,68 +260,130 @@ void functionComponentTestHelper(UiFactory<TestProps> factory,
const anotherProp = 'this should be filtered';
const className = 'aClassName';

group('using `addUnconsumedProps`', () {
// group('using `addUnconsumedProps`', () {
// TestProps initialProps;
// TestProps secondProps;

// setUp(() {
// initialProps = (factory()
// ..stringProp = stringProp
// ..anotherProp = anotherProp
// );

// secondProps = factory();

// expect(secondProps.stringProp, isNull, reason: 'Test setup sanity check');
// expect(secondProps.anotherProp, isNull, reason: 'Test setup sanity check');
// });

// test('', () {
// secondProps.addUnconsumedProps(initialProps, []);
// expect(secondProps.anotherProp, anotherProp);
// expect(secondProps.stringProp, stringProp);
// });

// test('and consumed props are correctly filtered', () {
// final consumedProps = initialProps.staticMeta.forMixins({TestPropsMixin});
// secondProps.addUnconsumedProps(initialProps, consumedProps);
// expect(secondProps.stringProp, isNull);
// expect(secondProps.anotherProp, anotherProp);
// });
// });

group('using `getPropsToForwardProps`', () {
TestProps initialProps;
TestProps secondProps;
TestPropsMixin secondProps;

setUp(() {
initialProps = (factory()
..stringProp = stringProp
..anotherProp = anotherProp
);

secondProps = factory();
secondProps = initialProps;

expect(secondProps.stringProp, isNull, reason: 'Test setup sanity check');
expect(secondProps.anotherProp, isNull, reason: 'Test setup sanity check');
// expect(secondProps.stringProp, isNull, reason: 'Test setup sanity check');
// expect(secondProps.anotherProp, isNull, reason: 'Test setup sanity check');
});

test('', () {
secondProps.addUnconsumedProps(initialProps, []);
expect(secondProps.anotherProp, anotherProp);
expect(secondProps.stringProp, stringProp);
test('by default excludes props mixin type that it is invoked on', () {
var unconsumedProps = factory(secondProps.getPropsToForward());
expect(unconsumedProps.anotherProp, anotherProp);
expect(unconsumedProps.stringProp, isNull);
});

test('and consumed props are correctly filtered', () {
final consumedProps = initialProps.staticMeta.forMixins({TestPropsMixin});
secondProps.addUnconsumedProps(initialProps, consumedProps);
expect(secondProps.stringProp, isNull);
expect(secondProps.anotherProp, anotherProp);
});
});
group('and props are correctly filtered', () {

group('using `addUnconsumedDomProps`', ()
{
TestProps initialProps;
TestProps secondProps;
group('via exclude', () {

setUp(() {
initialProps = (factory()
..stringProp = stringProp
..anotherProp = anotherProp
..className = className
);
test('for an empty set', () {
var unconsumedProps = factory(initialProps.getPropsToForward(exclude: {}));

secondProps = factory();
expect(unconsumedProps.stringProp, stringProp);
expect(unconsumedProps.anotherProp, anotherProp);
});

expect(secondProps.className, isNull, reason: 'Test setup sanity check');
});
test('for a single value set', () {
var unconsumedProps = factory(initialProps.getPropsToForward(exclude: {ASecondPropsMixin}));

test('', () {
secondProps.addUnconsumedDomProps(initialProps, []);
expect(secondProps.stringProp, isNull);
expect(secondProps.anotherProp, isNull);
expect(secondProps.className, className);
});
expect(unconsumedProps.stringProp, stringProp);
expect(unconsumedProps.anotherProp, isNull);
});

test('and consumed props are correctly filtered', () {
expect(initialProps.className, isNotNull, reason: 'Test setup sanity check');
secondProps.addUnconsumedDomProps(initialProps, [PropsMeta.forSimpleKey('className')]);
expect(secondProps.stringProp, isNull);
expect(secondProps.anotherProp, isNull);
expect(secondProps.className, isNull);
});

group('via include', () {

test('for an empty set', () {
var unconsumedProps = factory(initialProps.getPropsToForward(exclude: {}, include: {}));

expect(unconsumedProps.stringProp, isNull);
expect(unconsumedProps.anotherProp, isNull);
});

test('for a single value set', () {
var unconsumedProps = factory(initialProps.getPropsToForward(include: {ASecondPropsMixin}));

expect(unconsumedProps.stringProp, isNull);
expect(unconsumedProps.anotherProp, anotherProp);
});

});
});
});

// group('using `addUnconsumedDomProps`', ()
// {
// TestProps initialProps;
// TestProps secondProps;

// setUp(() {
// initialProps = (factory()
// ..stringProp = stringProp
// ..anotherProp = anotherProp
// ..className = className
// );

// secondProps = factory();

// expect(secondProps.className, isNull, reason: 'Test setup sanity check');
// });

// test('', () {
// secondProps.addUnconsumedDomProps(initialProps, []);
// expect(secondProps.stringProp, isNull);
// expect(secondProps.anotherProp, isNull);
// expect(secondProps.className, className);
// });

// test('and consumed props are correctly filtered', () {
// expect(initialProps.className, isNotNull, reason: 'Test setup sanity check');
// secondProps.addUnconsumedDomProps(initialProps, [PropsMeta.forSimpleKey('className')]);
// expect(secondProps.stringProp, isNull);
// expect(secondProps.anotherProp, isNull);
// expect(secondProps.className, isNull);
// });
// });
});
}

Expand Down
Loading