From 07c20b0ece92d1fd84c24170b56c72dadbe4fa54 Mon Sep 17 00:00:00 2001 From: Keal Jones Date: Mon, 1 Aug 2022 16:07:35 -0500 Subject: [PATCH 01/32] spike for unused props concept --- .../builder_helpers.dart | 21 +++++++++ web/index.html | 1 + web/uifunction/index.dart | 37 +++++++++++++++ web/uifunction/index.html | 47 +++++++++++++++++++ web/uifunction/src/foo.dart | 47 +++++++++++++++++++ 5 files changed, 153 insertions(+) create mode 100644 web/uifunction/index.dart create mode 100644 web/uifunction/index.html create mode 100644 web/uifunction/src/foo.dart diff --git a/lib/src/component_declaration/builder_helpers.dart b/lib/src/component_declaration/builder_helpers.dart index 6c22a3b28..523e29a4f 100644 --- a/lib/src/component_declaration/builder_helpers.dart +++ b/lib/src/component_declaration/builder_helpers.dart @@ -134,6 +134,27 @@ abstract class UiProps extends component_base.UiProps with GeneratedClass { @toBeGenerated PropsMetaCollection get staticMeta => throw UngeneratedError(member: #meta); } +extension UnusedProps on T { + Map get unused => getUnused(); + + Map getUnused([Set usedPropMixins]) { + try { + final unusedProps = {}; + final usedProps = staticMeta.forMixins(usedPropMixins ?? {T}); + final usedPropKeys = usedProps.map((consumedProps) => consumedProps.keys).toList(); + forwardUnconsumedPropsV2(props, propsToUpdate: unusedProps, keySetsToOmit: usedPropKeys); + return unusedProps; + } catch(_) { + if (usedPropMixins == null) { + throw ArgumentError('Could not find props meta for type $T.' + ' If this is not a props mixin, you need to specify its mixins as the second argument. For example:' + '\n ..addAll(props.getUnused({${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 diff --git a/web/index.html b/web/index.html index d9a6dd257..95cc558e0 100644 --- a/web/index.html +++ b/web/index.html @@ -33,6 +33,7 @@

OverReact Component Demos

diff --git a/web/uifunction/index.dart b/web/uifunction/index.dart new file mode 100644 index 000000000..d1d3f6119 --- /dev/null +++ b/web/uifunction/index.dart @@ -0,0 +1,37 @@ + + +// Copyright 2020 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'dart:html'; + +import 'package:over_react/over_react.dart'; +import 'package:over_react/react_dom.dart' as react_dom; + +import 'src/foo.dart'; + +void main() { + react_dom.render( + StrictMode()( + (Foo() + ..dom.id = 'TopID' + ..foo = 'Foo!' + ..bar = 'BAR!' + ..somethingElse = 'forwarded this unused prop!' + ..['lol'] = true + )() + ), + querySelector('#uifunction'), + ); +} diff --git a/web/uifunction/index.html b/web/uifunction/index.html new file mode 100644 index 000000000..5210c82da --- /dev/null +++ b/web/uifunction/index.html @@ -0,0 +1,47 @@ + + + + + + + + over_react demo components + + + + + + + + +
+

OverReact Component Demos - UiFunction

+
+
+
+ + + + + + + + + + + + diff --git a/web/uifunction/src/foo.dart b/web/uifunction/src/foo.dart new file mode 100644 index 000000000..be05ff16a --- /dev/null +++ b/web/uifunction/src/foo.dart @@ -0,0 +1,47 @@ +import 'dart:html'; +import 'dart:js_util'; + +import 'package:over_react/over_react.dart'; +part 'foo.over_react.g.dart'; + +mixin FooPropsMixin on UiProps { + dynamic foo; +} + +class FooProps = UiProps + with BarPropsMixin, + FooPropsMixin, + SomethingElsePropsMixin; + +final Foo = uiFunction((props) { + log('Foo', props.getUnused({FooPropsMixin})); + return (Bar() + ..addAll(props.getUnused({FooPropsMixin})) + )(); +}, _$FooConfig); + +mixin BarPropsMixin on UiProps { + dynamic bar; +} + +final Bar = uiFunction((props) { + log('Bar', props.unused); + return (SomethingElse() + ..addAll(props.unused) + )(); +}, _$BarConfig); + + +mixin SomethingElsePropsMixin on UiProps { + dynamic somethingElse; +} + +final SomethingElse = uiFunction((props) { + log('SomethingElse', props.getUnused()); + return (Dom.div() + ..addAll(props.getUnused()) + )(props.somethingElse); +}, _$SomethingElseConfig); + + +log(dynamic name, dynamic x) => callMethod(getProperty(window,'console'), 'log', [name, jsify(x)]); From 69f836f8f7df278e15e6e5af9deeceadb5378624 Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Wed, 19 Jul 2023 14:57:40 -0700 Subject: [PATCH 02/32] I heard you liked forwarding props... --- .../builder_helpers.dart | 92 ++++++-- .../function_component_test.dart | 144 ++++++++---- ...over_react_component_declaration_test.dart | 204 ++++++++-------- web/uifunction/index.dart | 10 +- web/uifunction/src/bar.dart | 218 ++++++++++++++++++ web/uifunction/src/foo.dart | 50 ++-- 6 files changed, 541 insertions(+), 177 deletions(-) create mode 100644 web/uifunction/src/bar.dart diff --git a/lib/src/component_declaration/builder_helpers.dart b/lib/src/component_declaration/builder_helpers.dart index 523e29a4f..2a84eac4e 100644 --- a/lib/src/component_declaration/builder_helpers.dart +++ b/lib/src/component_declaration/builder_helpers.dart @@ -134,24 +134,88 @@ 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 on T { - Map get unused => getUnused(); - Map getUnused([Set usedPropMixins]) { + /// 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` + /// // 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` + /// // 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 exclude, Set include, bool domOnly = false}) { + final useDefaultExclude = exclude == null && (include == null || (include is Set && include.isEmpty)); try { - final unusedProps = {}; - final usedProps = staticMeta.forMixins(usedPropMixins ?? {T}); - final usedPropKeys = usedProps.map((consumedProps) => consumedProps.keys).toList(); - forwardUnconsumedPropsV2(props, propsToUpdate: unusedProps, keySetsToOmit: usedPropKeys); - return unusedProps; - } catch(_) { - if (usedPropMixins == null) { - throw ArgumentError('Could not find props meta for type $T.' - ' If this is not a props mixin, you need to specify its mixins as the second argument. For example:' - '\n ..addAll(props.getUnused({${T}Mixin}, …)'); + Iterable includedProps = []; + final unconsumedProps = {}; + Iterable consumedProps; + List> consumedPropKeys; + final excludedProps = staticMeta.forMixins(useDefaultExclude ? {T} : exclude ?? {}); + final excludedPropKeys = excludedProps.map((consumeProps) => consumeProps.keys).toList(); + + if (include != null) { + 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(_) { + 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; } - rethrow; - } } } diff --git a/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart index fb0df94a4..e1a7f9f9d 100644 --- a/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart +++ b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart @@ -260,9 +260,39 @@ void functionComponentTestHelper(UiFactory 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() @@ -270,58 +300,90 @@ void functionComponentTestHelper(UiFactory factory, ..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); + // }); + // }); }); } diff --git a/test/over_react_component_declaration_test.dart b/test/over_react_component_declaration_test.dart index 617a3fbdb..a26e8327e 100644 --- a/test/over_react_component_declaration_test.dart +++ b/test/over_react_component_declaration_test.dart @@ -22,120 +22,120 @@ library over_react_test; import 'package:over_react/over_react.dart'; import 'package:test/test.dart'; -import 'over_react/component_declaration/component_base_test.dart' as component_base_test; -import 'over_react/component_declaration/component_type_checking_test.dart' as component_type_checking_test; -import 'over_react/component_declaration/redux_component_test.dart' as redux_component_test; +// import 'over_react/component_declaration/component_base_test.dart' as component_base_test; +// import 'over_react/component_declaration/component_type_checking_test.dart' as component_type_checking_test; +// import 'over_react/component_declaration/redux_component_test.dart' as redux_component_test; -import 'over_react/component_declaration/flux_component_test/flux_component_test.dart' as flux_component_test; -import 'over_react/component_declaration/flux_component_test/component2/flux_component_test.dart' as component2_flux_component_test; +// import 'over_react/component_declaration/flux_component_test/flux_component_test.dart' as flux_component_test; +// import 'over_react/component_declaration/flux_component_test/component2/flux_component_test.dart' as component2_flux_component_test; -import 'over_react/component_declaration/builder_helpers_test.dart' as builder_helpers_test; -import 'over_react/component_declaration/builder_integration_tests/abstract_accessor_integration_test.dart' as abstract_accessor_integration_test; -import 'over_react/component_declaration/builder_integration_tests/accessor_mixin_integration_test.dart' as accessor_mixin_integration_test; -import 'over_react/component_declaration/builder_integration_tests/component_integration_test.dart' as component_integration_test; -import 'over_react/component_declaration/builder_integration_tests/constant_required_accessor_integration_test.dart' as constant_required_accessor_integration_test; -import 'over_react/component_declaration/builder_integration_tests/do_not_generate_accessor_integration_test.dart' as do_not_generate_accessor_integration_test; -import 'over_react/component_declaration/builder_integration_tests/namespaced_accessor_integration_test.dart' as namespaced_accessor_integration_test; -import 'over_react/component_declaration/builder_integration_tests/private_props_ddc_bug.dart' as private_props_ddc_bug; -import 'over_react/component_declaration/builder_integration_tests/required_accessor_integration_test.dart' as required_accessor_integration_test; -import 'over_react/component_declaration/builder_integration_tests/stateful_component_integration_test.dart' as stateful_component_integration_test; -import 'over_react/component_declaration/builder_integration_tests/unassigned_prop_integration_test.dart' as unassigned_prop_integration_test; -import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/abstract_accessor_integration_test.dart' as backwards_compat_abstract_accessor_integration_test; -import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/accessor_mixin_integration_test.dart' as backwards_compat_accessor_mixin_integration_test; -import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/component_integration_test.dart' as backwards_compat_component_integration_test; -import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/constant_required_accessor_integration_test.dart' as backwards_compat_constant_required_accessor_integration_test; -import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/do_not_generate_accessor_integration_test.dart' as backwards_compat_do_not_generate_accessor_integration_test; -import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/namespaced_accessor_integration_test.dart' as backwards_compat_namespaced_accessor_integration_test; -import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/private_props_ddc_bug.dart' as backwards_compat_private_props_ddc_bug; -import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/required_accessor_integration_test.dart' as backwards_compat_required_accessor_integration_test; -import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/stateful_component_integration_test.dart' as backwards_compat_stateful_component_integration_test; -import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/unassigned_prop_integration_test.dart' as backwards_compat_unassigned_prop_integration_test; -import 'over_react/component_declaration/builder_integration_tests/component2/abstract_accessor_integration_test.dart' as component2_abstract_accessor_integration_test; -import 'over_react/component_declaration/builder_integration_tests/component2/accessor_mixin_integration_test.dart' as component2_accessor_mixin_integration_test; -import 'over_react/component_declaration/builder_integration_tests/component2/annotation_error_integration_test.dart' as annotation_error_integration_test; -import 'over_react/component_declaration/builder_integration_tests/component2/component_integration_test.dart' as component2_component_integration_test; -import 'over_react/component_declaration/builder_integration_tests/component2/constant_required_accessor_integration_test.dart' as component2_constant_required_accessor_integration_test; -import 'over_react/component_declaration/builder_integration_tests/component2/do_not_generate_accessor_integration_test.dart' as component2_do_not_generate_accessor_integration_test; -import 'over_react/component_declaration/builder_integration_tests/component2/namespaced_accessor_integration_test.dart' as component2_namespaced_accessor_integration_test; -import 'over_react/component_declaration/builder_integration_tests/component2/private_props_ddc_bug.dart' as component2_private_props_ddc_bug; -import 'over_react/component_declaration/builder_integration_tests/component2/required_accessor_integration_test.dart' as component2_required_accessor_integration_test; -import 'over_react/component_declaration/builder_integration_tests/component2/stateful_component_integration_test.dart' as component2_stateful_component_integration_test; -import 'over_react/component_declaration/builder_integration_tests/component2/unassigned_prop_integration_test.dart' as component2_unassigned_prop_integration_test; -import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/accessor_mixin_integration_test.dart' as new_boilerplate_accessor_mixin_integration_test; -import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/compact_hoc_syntax_integration_test.dart' as new_boilerplate_compact_hoc_syntax_integration_test; -import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/component_integration_test.dart' as new_boilerplate_component_integration_test; -import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/component_integration_verbose_syntax_test.dart' as new_boilerplate_component_integration_verbose_syntax_test; -import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/constant_required_accessor_integration_test.dart' as new_boilerplate_constant_required_accessor_integration_test; -import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/covariant_accessor_override_integration_test.dart' as new_boilerplate_covariant_accessor_override_integration_test; -import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/do_not_generate_accessor_integration_test.dart' as new_boilerplate_do_not_generate_accessor_integration_test; +// import 'over_react/component_declaration/builder_helpers_test.dart' as builder_helpers_test; +// import 'over_react/component_declaration/builder_integration_tests/abstract_accessor_integration_test.dart' as abstract_accessor_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/accessor_mixin_integration_test.dart' as accessor_mixin_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/component_integration_test.dart' as component_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/constant_required_accessor_integration_test.dart' as constant_required_accessor_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/do_not_generate_accessor_integration_test.dart' as do_not_generate_accessor_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/namespaced_accessor_integration_test.dart' as namespaced_accessor_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/private_props_ddc_bug.dart' as private_props_ddc_bug; +// import 'over_react/component_declaration/builder_integration_tests/required_accessor_integration_test.dart' as required_accessor_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/stateful_component_integration_test.dart' as stateful_component_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/unassigned_prop_integration_test.dart' as unassigned_prop_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/abstract_accessor_integration_test.dart' as backwards_compat_abstract_accessor_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/accessor_mixin_integration_test.dart' as backwards_compat_accessor_mixin_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/component_integration_test.dart' as backwards_compat_component_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/constant_required_accessor_integration_test.dart' as backwards_compat_constant_required_accessor_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/do_not_generate_accessor_integration_test.dart' as backwards_compat_do_not_generate_accessor_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/namespaced_accessor_integration_test.dart' as backwards_compat_namespaced_accessor_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/private_props_ddc_bug.dart' as backwards_compat_private_props_ddc_bug; +// import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/required_accessor_integration_test.dart' as backwards_compat_required_accessor_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/stateful_component_integration_test.dart' as backwards_compat_stateful_component_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/unassigned_prop_integration_test.dart' as backwards_compat_unassigned_prop_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/component2/abstract_accessor_integration_test.dart' as component2_abstract_accessor_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/component2/accessor_mixin_integration_test.dart' as component2_accessor_mixin_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/component2/annotation_error_integration_test.dart' as annotation_error_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/component2/component_integration_test.dart' as component2_component_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/component2/constant_required_accessor_integration_test.dart' as component2_constant_required_accessor_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/component2/do_not_generate_accessor_integration_test.dart' as component2_do_not_generate_accessor_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/component2/namespaced_accessor_integration_test.dart' as component2_namespaced_accessor_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/component2/private_props_ddc_bug.dart' as component2_private_props_ddc_bug; +// import 'over_react/component_declaration/builder_integration_tests/component2/required_accessor_integration_test.dart' as component2_required_accessor_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/component2/stateful_component_integration_test.dart' as component2_stateful_component_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/component2/unassigned_prop_integration_test.dart' as component2_unassigned_prop_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/accessor_mixin_integration_test.dart' as new_boilerplate_accessor_mixin_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/compact_hoc_syntax_integration_test.dart' as new_boilerplate_compact_hoc_syntax_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/component_integration_test.dart' as new_boilerplate_component_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/component_integration_verbose_syntax_test.dart' as new_boilerplate_component_integration_verbose_syntax_test; +// import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/constant_required_accessor_integration_test.dart' as new_boilerplate_constant_required_accessor_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/covariant_accessor_override_integration_test.dart' as new_boilerplate_covariant_accessor_override_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/do_not_generate_accessor_integration_test.dart' as new_boilerplate_do_not_generate_accessor_integration_test; import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart' as new_boilerplate_function_component_integration_test; -import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/namespaced_accessor_integration_test.dart' as new_boilerplate_namespaced_accessor_integration_test; -import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/private_props_ddc_bug.dart' as new_boilerplate_private_props_ddc_bug; -import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/props_map_view_test.dart' as new_boilerplate_props_map_view_test; -import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/props_meta_test.dart' as new_boilerplate_props_meta_test; -import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/required_accessor_integration_test.dart' as new_boilerplate_required_accessor_integration_test; -import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/stateful_component_integration_test.dart' as new_boilerplate_stateful_component_integration_test; -import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/unassigned_prop_integration_test.dart' as new_boilerplate_unassigned_prop_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/namespaced_accessor_integration_test.dart' as new_boilerplate_namespaced_accessor_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/private_props_ddc_bug.dart' as new_boilerplate_private_props_ddc_bug; +// import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/props_map_view_test.dart' as new_boilerplate_props_map_view_test; +// import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/props_meta_test.dart' as new_boilerplate_props_meta_test; +// import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/required_accessor_integration_test.dart' as new_boilerplate_required_accessor_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/stateful_component_integration_test.dart' as new_boilerplate_stateful_component_integration_test; +// import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/unassigned_prop_integration_test.dart' as new_boilerplate_unassigned_prop_integration_test; main() { enableTestMode(); - component_base_test.main(); - component_type_checking_test.main(); - redux_component_test.main(); + // component_base_test.main(); + // component_type_checking_test.main(); + // redux_component_test.main(); - flux_component_test.main(); - component2_flux_component_test.main(); + // flux_component_test.main(); + // component2_flux_component_test.main(); - builder_helpers_test.main(); + // builder_helpers_test.main(); - abstract_accessor_integration_test.main(); - accessor_mixin_integration_test.main(); - component_integration_test.main(); - constant_required_accessor_integration_test.main(); - do_not_generate_accessor_integration_test.main(); - namespaced_accessor_integration_test.main(); - private_props_ddc_bug.main(); - required_accessor_integration_test.main(); - stateful_component_integration_test.main(); - unassigned_prop_integration_test.main(); + // abstract_accessor_integration_test.main(); + // accessor_mixin_integration_test.main(); + // component_integration_test.main(); + // constant_required_accessor_integration_test.main(); + // do_not_generate_accessor_integration_test.main(); + // namespaced_accessor_integration_test.main(); + // private_props_ddc_bug.main(); + // required_accessor_integration_test.main(); + // stateful_component_integration_test.main(); + // unassigned_prop_integration_test.main(); - backwards_compat_abstract_accessor_integration_test.main(); - backwards_compat_accessor_mixin_integration_test.main(); - backwards_compat_do_not_generate_accessor_integration_test.main(); - backwards_compat_component_integration_test.main(); - backwards_compat_constant_required_accessor_integration_test.main(); - backwards_compat_private_props_ddc_bug.main(); - backwards_compat_namespaced_accessor_integration_test.main(); - backwards_compat_required_accessor_integration_test.main(); - backwards_compat_stateful_component_integration_test.main(); - backwards_compat_unassigned_prop_integration_test.main(); + // backwards_compat_abstract_accessor_integration_test.main(); + // backwards_compat_accessor_mixin_integration_test.main(); + // backwards_compat_do_not_generate_accessor_integration_test.main(); + // backwards_compat_component_integration_test.main(); + // backwards_compat_constant_required_accessor_integration_test.main(); + // backwards_compat_private_props_ddc_bug.main(); + // backwards_compat_namespaced_accessor_integration_test.main(); + // backwards_compat_required_accessor_integration_test.main(); + // backwards_compat_stateful_component_integration_test.main(); + // backwards_compat_unassigned_prop_integration_test.main(); - component2_abstract_accessor_integration_test.main(); - component2_accessor_mixin_integration_test.main(); - annotation_error_integration_test.main(); - component2_component_integration_test.main(); - component2_constant_required_accessor_integration_test.main(); - component2_do_not_generate_accessor_integration_test.main(); - component2_namespaced_accessor_integration_test.main(); - component2_private_props_ddc_bug.main(); - component2_required_accessor_integration_test.main(); - component2_stateful_component_integration_test.main(); - component2_unassigned_prop_integration_test.main(); + // component2_abstract_accessor_integration_test.main(); + // component2_accessor_mixin_integration_test.main(); + // annotation_error_integration_test.main(); + // component2_component_integration_test.main(); + // component2_constant_required_accessor_integration_test.main(); + // component2_do_not_generate_accessor_integration_test.main(); + // component2_namespaced_accessor_integration_test.main(); + // component2_private_props_ddc_bug.main(); + // component2_required_accessor_integration_test.main(); + // component2_stateful_component_integration_test.main(); + // component2_unassigned_prop_integration_test.main(); - new_boilerplate_accessor_mixin_integration_test.main(); - new_boilerplate_compact_hoc_syntax_integration_test.main(); - new_boilerplate_component_integration_test.main(); - new_boilerplate_component_integration_verbose_syntax_test.main(); - new_boilerplate_constant_required_accessor_integration_test.main(); - new_boilerplate_covariant_accessor_override_integration_test.main(); - new_boilerplate_do_not_generate_accessor_integration_test.main(); + // new_boilerplate_accessor_mixin_integration_test.main(); + // new_boilerplate_compact_hoc_syntax_integration_test.main(); + // new_boilerplate_component_integration_test.main(); + // new_boilerplate_component_integration_verbose_syntax_test.main(); + // new_boilerplate_constant_required_accessor_integration_test.main(); + // new_boilerplate_covariant_accessor_override_integration_test.main(); + // new_boilerplate_do_not_generate_accessor_integration_test.main(); new_boilerplate_function_component_integration_test.main(); - new_boilerplate_namespaced_accessor_integration_test.main(); - new_boilerplate_private_props_ddc_bug.main(); - new_boilerplate_props_map_view_test.main(); - new_boilerplate_props_meta_test.main(); - new_boilerplate_required_accessor_integration_test.main(); - new_boilerplate_stateful_component_integration_test.main(); - new_boilerplate_unassigned_prop_integration_test.main(); + // new_boilerplate_namespaced_accessor_integration_test.main(); + // new_boilerplate_private_props_ddc_bug.main(); + // new_boilerplate_props_map_view_test.main(); + // new_boilerplate_props_meta_test.main(); + // new_boilerplate_required_accessor_integration_test.main(); + // new_boilerplate_stateful_component_integration_test.main(); + // new_boilerplate_unassigned_prop_integration_test.main(); } diff --git a/web/uifunction/index.dart b/web/uifunction/index.dart index d1d3f6119..5867d8a16 100644 --- a/web/uifunction/index.dart +++ b/web/uifunction/index.dart @@ -20,17 +20,19 @@ import 'package:over_react/over_react.dart'; import 'package:over_react/react_dom.dart' as react_dom; import 'src/foo.dart'; +import 'src/bar.dart'; void main() { react_dom.render( StrictMode()( - (Foo() - ..dom.id = 'TopID' + (FooBar() + ..foo = 'Foo!' ..bar = 'BAR!' ..somethingElse = 'forwarded this unused prop!' - ..['lol'] = true - )() + //..['lol'] = true + )(), + (Button()..id='ButtonID'..isActive = true..['fancy']="true")('HEyyyy') ), querySelector('#uifunction'), ); diff --git a/web/uifunction/src/bar.dart b/web/uifunction/src/bar.dart new file mode 100644 index 000000000..79f6a9245 --- /dev/null +++ b/web/uifunction/src/bar.dart @@ -0,0 +1,218 @@ +// Copyright 2020 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:over_react/over_react.dart'; + +part 'bar.over_react.g.dart'; + +/// Nest one or more `Button` components within a [ListGroup] +/// to render individual items within a list. +/// +/// See: + +UiFactory Button = castUiFactory(_$Button); // ignore: undefined_identifier + +mixin ButtonProps on UiProps { + /// The skin / "context" for the [Button]. + /// + /// See: . + /// + /// Default: [ButtonSkin.PRIMARY] + ButtonSkin skin; + + /// The size of the [Button]. + /// + /// See: . + /// + /// Default: [ButtonSize.DEFAULT] + ButtonSize size; + + /// Whether the [Button] should appear "active". + /// + /// See: + /// + /// Default: false + bool isActive; + + /// Whether the [Button] is disabled. + /// + /// See: + /// + /// Default: false + @Accessor(key: 'disabled', keyNamespace: '') + bool isDisabled; + + /// Whether the [Button] is a block level button -- that which spans the full + /// width of its parent. + /// + /// Default: false + bool isBlock; + + /// The HTML `href` attribute value for the [Button]. + /// + /// If set, the item will render via [Dom.a]. + /// + /// _Proxies [DomPropsMixin.href]_ + @Accessor(keyNamespace: '') + String href; + + /// The HTML `target` attribute value for the [Button]. + /// + /// If set, the item will render via [Dom.a]. + /// + /// _Proxies [DomPropsMixin.target]_ + @Accessor(keyNamespace: '') + String target; + + /// The HTML `type` attribute value for the [Button] when + /// rendered via [Dom.button]. + /// + /// This will only be applied if [href] is not set. + /// + /// _Proxies [DomPropsMixin.type]_ + /// + /// Default: [ButtonType.BUTTON] + String type; +} + +mixin ButtonState on UiState {} + +class ButtonComponent + extends UiStatefulComponent2 { + @override + get defaultProps => (newProps() + ..skin = ButtonSkin.PRIMARY + ..size = ButtonSize.DEFAULT + ..isActive = false + ..isDisabled = false + ..isBlock = false + ..type = 'button' + ); + +// T get props; +// + @override + render() { + return renderButton(props.children); + } + + ReactElement renderButton(dynamic children) { + BuilderOnlyUiFactory factory = buttonDomNodeFactory; + + return (factory() + ..addAll(props.getPropsToForward()) + ..className = getButtonClasses().toClassName() + ..href = props.href + ..target = props.target + ..type = type + ..disabled = isAnchorLink ? null : props.isDisabled + ..addProps(ariaProps() + ..disabled = isAnchorLink ? props.isDisabled : null + ) + )(children); + } + + ClassNameBuilder getButtonClasses() { + return forwardingClassNameBuilder() + ..add('btn') + ..add('btn-block', props.isBlock) + ..add('active', isActive) + ..add('disabled', props.isDisabled) + ..add(props.skin.className) + ..add(props.size.className); + } + + BuilderOnlyUiFactory get buttonDomNodeFactory => isAnchorLink ? Dom.a : Dom.button; + + bool get isAnchorLink => props.href != null; + + bool get isActive => props.isActive; + + String get type => isAnchorLink ? null : props.type; +} + +/// Contextual skin options for a [Button] component. +class ButtonSkin extends ClassNameConstant { + const ButtonSkin._(String name, String className) : super(name, className); + + /// [className] value: 'btn-primary' + static const ButtonSkin PRIMARY = + ButtonSkin._('PRIMARY', 'btn-primary'); + + /// [className] value: 'btn-secondary' + static const ButtonSkin SECONDARY = + ButtonSkin._('SECONDARY', 'btn-secondary'); + + /// [className] value: 'btn-danger' + static const ButtonSkin DANGER = + ButtonSkin._('DANGER', 'btn-danger'); + + /// [className] value: 'btn-success' + static const ButtonSkin SUCCESS = + ButtonSkin._('SUCCESS', 'btn-success'); + + /// [className] value: 'btn-warning' + static const ButtonSkin WARNING = + ButtonSkin._('WARNING', 'btn-warning'); + + /// [className] value: 'btn-info' + static const ButtonSkin INFO = + ButtonSkin._('INFO', 'btn-info'); + + /// [className] value: 'btn-link' + static const ButtonSkin LINK = + ButtonSkin._('LINK', 'btn-link'); + + /// [className] value: 'btn-outline-primary' + static const ButtonSkin PRIMARY_OUTLINE = + ButtonSkin._('PRIMARY_OUTLINE', 'btn-outline-primary'); + + /// [className] value: 'btn-outline-secondary' + static const ButtonSkin SECONDARY_OUTLINE = + ButtonSkin._('SECONDARY_OUTLINE', 'btn-outline-secondary'); + + /// [className] value: 'btn-outline-danger' + static const ButtonSkin DANGER_OUTLINE = + ButtonSkin._('DANGER_OUTLINE', 'btn-outline-danger'); + + /// [className] value: 'btn-outline-success' + static const ButtonSkin SUCCESS_OUTLINE = + ButtonSkin._('SUCCESS_OUTLINE', 'btn-outline-success'); + + /// [className] value: 'btn-outline-warning' + static const ButtonSkin WARNING_OUTLINE = + ButtonSkin._('WARNING_OUTLINE', 'btn-outline-warning'); + + /// [className] value: 'btn-outline-info' + static const ButtonSkin INFO_OUTLINE = + ButtonSkin._('INFO_OUTLINE', 'btn-outline-info'); +} + +/// Size options for a [Button] component. +class ButtonSize extends ClassNameConstant { + const ButtonSize._(String name, String className) : super(name, className); + + /// [className] value: '' + static const ButtonSize DEFAULT = + ButtonSize._('DEFAULT', ''); + + /// [className] value: 'btn-lg' + static const ButtonSize LARGE = + ButtonSize._('LARGE', 'btn-lg'); + + /// [className] value: 'btn-sm' + static const ButtonSize SMALL = + ButtonSize._('SMALL', 'btn-sm'); +} + diff --git a/web/uifunction/src/foo.dart b/web/uifunction/src/foo.dart index be05ff16a..70725fcb2 100644 --- a/web/uifunction/src/foo.dart +++ b/web/uifunction/src/foo.dart @@ -4,31 +4,48 @@ import 'dart:js_util'; import 'package:over_react/over_react.dart'; part 'foo.over_react.g.dart'; -mixin FooPropsMixin on UiProps { - dynamic foo; +mixin FooBarPropsMixin on UiProps { + String foobar; } -class FooProps = UiProps +class FooBarProps = UiProps with BarPropsMixin, FooPropsMixin, SomethingElsePropsMixin; -final Foo = uiFunction((props) { - log('Foo', props.getUnused({FooPropsMixin})); - return (Bar() - ..addAll(props.getUnused({FooPropsMixin})) - )(); +final FooBar = uiFunction((props) { + return Fragment()( + (Foo() + ..addAll(props.getPropsToForward(exclude: {FooPropsMixin})) + )(), + (Bar() + ..addAll(props.getPropsToForward(exclude: {BarPropsMixin})) + )(), + (SomethingElse() + ..addAll(props.getPropsToForward(exclude: {SomethingElsePropsMixin})) + )(), + ); +}, _$FooBarConfig); + +mixin FooPropsMixin on UiProps { + dynamic foo; +} + +final Foo = uiFunction((props) { + if (props.length > 1 ) { + window.console.log(props.props); + } + return props.foo != null ? 'FOO!!!' : 'NO FOO :('; }, _$FooConfig); mixin BarPropsMixin on UiProps { - dynamic bar; + String bar; } final Bar = uiFunction((props) { - log('Bar', props.unused); - return (SomethingElse() - ..addAll(props.unused) - )(); + //var consumedProps = props.staticMeta.forMixins({FooPropsMixin}); + //log('Bar', props.getPropsToForward(exclude: {FooPropsMixin})); + return props.bar != null ? 'BAR!!!' : 'NO BAR :('; }, _$BarConfig); @@ -36,11 +53,12 @@ mixin SomethingElsePropsMixin on UiProps { dynamic somethingElse; } + final SomethingElse = uiFunction((props) { - log('SomethingElse', props.getUnused()); + log('SomethingElse', props.getPropsToForward()); return (Dom.div() - ..addAll(props.getUnused()) - )(props.somethingElse); + ..addAll(props.getPropsToForward()) + )(props.somethingElse != null ? 'We received props.somethingElse! ${props.somethingElse}' : 'props.somethingElse was not forwarded.'); }, _$SomethingElseConfig); From 09cc932d1d5f48c61241af92dfbd5fb41ee86867 Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Thu, 20 Jul 2023 09:43:43 -0700 Subject: [PATCH 03/32] remove `include` --- .../builder_helpers.dart | 46 +++++++------------ .../function_component_test.dart | 20 ++++---- 2 files changed, 26 insertions(+), 40 deletions(-) diff --git a/lib/src/component_declaration/builder_helpers.dart b/lib/src/component_declaration/builder_helpers.dart index 2a84eac4e..e9f768842 100644 --- a/lib/src/component_declaration/builder_helpers.dart +++ b/lib/src/component_declaration/builder_helpers.dart @@ -165,57 +165,43 @@ Proposed: extension UnusedProps on T { - /// Returns this instance's props map excluding the keys found in [consumedMixins]. + /// Returns this instance's props map excluding the keys found in [exclude]. /// - /// [consumedMixins] should be a `Set` of PropsMixin `Type`s. - /// If [consumedMixins] is not set it defaults to using the current instances Type. + /// [exclude] should be a `Set` of PropsMixin `Type`s. + /// If [exclude] is not set it defaults to using the current instances Type. /// /// __Example:__ /// /// ```dart /// // within a functional component: `uiFunction` /// // filter out the current components props when forwarding to Bar. - /// return (Bar()..addAll(props.getConsumed()))(); + /// return (Bar()..addAll(props.getPropsToForward()))(); /// ``` /// OR /// ```dart /// // within a functional component that has multiple mixins on a Props class: `uiFunction` /// // filter out the Components props when forwarding to Bar. - /// return (Bar()..addAll(props.getConsumed(consumedMixins:{FooPropsMixin}))(); + /// return (Bar()..addAll(props.getPropsToForward(exclude: {FooPropsMixin}))(); /// ``` /// /// To only add DOM props, use [addUnconsumedDomProps]. /// /// Related: `UiComponent2`'s `addUnconsumedProps` - Map getPropsToForward({Set exclude, Set include, bool domOnly = false}) { - final useDefaultExclude = exclude == null && (include == null || (include is Set && include.isEmpty)); + Map getPropsToForward({Set exclude, bool domOnly = false}) { try { - Iterable includedProps = []; final unconsumedProps = {}; - Iterable consumedProps; - List> consumedPropKeys; - final excludedProps = staticMeta.forMixins(useDefaultExclude ? {T} : exclude ?? {}); - final excludedPropKeys = excludedProps.map((consumeProps) => consumeProps.keys).toList(); - - if (include != null) { - 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); + final consumedProps = staticMeta.forMixins(exclude ?? {T}); + final consumedPropKeys = consumedProps.map((consumedProps) => consumedProps.keys); + forwardUnconsumedPropsV2(props, propsToUpdate: unconsumedProps, keySetsToOmit: consumedPropKeys); return unconsumedProps; - } catch(_) { - 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; + } catch(_) { + if (exclude == null) { + throw ArgumentError('Could not find props meta for type $T.' + ' If this is not a props mixin, you need to specify its mixins as the second argument. For example:' + '\n ..addAll(props.getPropsToForward(exclude: {${T}Mixin})'); } + rethrow; + } } } diff --git a/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart index e1a7f9f9d..3a279205a 100644 --- a/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart +++ b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart @@ -293,11 +293,13 @@ void functionComponentTestHelper(UiFactory factory, group('using `getPropsToForwardProps`', () { TestProps initialProps; TestPropsMixin secondProps; + final testId = 'testId'; setUp(() { initialProps = (factory() ..stringProp = stringProp ..anotherProp = anotherProp + ..id = testId ); secondProps = initialProps; @@ -323,32 +325,30 @@ void functionComponentTestHelper(UiFactory factory, expect(unconsumedProps.anotherProp, anotherProp); }); - test('for a single value set', () { + test('for a single value in set', () { var unconsumedProps = factory(initialProps.getPropsToForward(exclude: {ASecondPropsMixin})); expect(unconsumedProps.stringProp, stringProp); expect(unconsumedProps.anotherProp, isNull); }); - }); - - group('via include', () { - - test('for an empty set', () { - var unconsumedProps = factory(initialProps.getPropsToForward(exclude: {}, include: {})); + test('for multiple values in set', () { + var unconsumedProps = factory(initialProps.getPropsToForward(exclude: {ASecondPropsMixin, TestPropsMixin})); expect(unconsumedProps.stringProp, isNull); expect(unconsumedProps.anotherProp, isNull); }); - test('for a single value set', () { - var unconsumedProps = factory(initialProps.getPropsToForward(include: {ASecondPropsMixin})); + test('for dom only ', () { + var unconsumedProps = factory(initialProps.getPropsToForward(domOnly: true)); expect(unconsumedProps.stringProp, isNull); - expect(unconsumedProps.anotherProp, anotherProp); + expect(unconsumedProps.anotherProp, isNull); + expect(unconsumedProps.id, testId); }); }); + }); }); From 638aa33298db65e19a869b45d28a353d675207b6 Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Fri, 21 Jul 2023 14:14:56 -0700 Subject: [PATCH 04/32] add some dom tests --- .../builder_helpers.dart | 58 +++++-- .../function_component_test.dart | 153 +++++++++++------- web/index.html | 2 +- web/uifunction/index.dart | 4 +- web/uifunction/src/foo.dart | 45 ++---- web/uifunction/src/foobar.dart | 23 +++ 6 files changed, 179 insertions(+), 106 deletions(-) create mode 100644 web/uifunction/src/foobar.dart diff --git a/lib/src/component_declaration/builder_helpers.dart b/lib/src/component_declaration/builder_helpers.dart index e9f768842..e6d226c5d 100644 --- a/lib/src/component_declaration/builder_helpers.dart +++ b/lib/src/component_declaration/builder_helpers.dart @@ -163,9 +163,10 @@ Proposed: */ -extension UnusedProps on T { +extension PropsToForward on T { - /// Returns this instance's props map excluding the keys found in [exclude]. + + /// Returns a copy of this instance's props excluding the keys found in [exclude]. /// /// [exclude] should be a `Set` of PropsMixin `Type`s. /// If [exclude] is not set it defaults to using the current instances Type. @@ -188,20 +189,53 @@ extension UnusedProps on T { /// /// Related: `UiComponent2`'s `addUnconsumedProps` Map getPropsToForward({Set exclude, bool domOnly = false}) { + return _propsToForward(exclude: exclude, domOnly: domOnly, propsToUpdate: {}); + } + + + /// A utility function to be used with `modifyProps` to add props excluding the keys found in [exclude]. + /// + /// [exclude] should be a `Set` of PropsMixin `Type`s. + /// If [exclude] is not set it defaults to using the current instances Type. + /// + /// __Example:__ + /// + /// ```dart + /// // within a functional component: `uiFunction` + /// // filter out the current components props when forwarding to Bar. + /// return (Bar()..addAll(props.getPropsToForward()))(); + /// ``` + /// OR + /// ```dart + /// // within a functional component that has multiple mixins on a Props class: `uiFunction` + /// // filter out the Components props when forwarding to Bar. + /// return (Bar()..addAll(props.getPropsToForward(exclude: {FooPropsMixin}))(); + /// ``` + /// + /// To only add DOM props, use [addUnconsumedDomProps]. + /// + /// Related: `UiComponent2`'s `addUnconsumedProps` + PropsModifier addPropsToForward({Set exclude, bool domOnly = false}) { + return (Map props) { + _propsToForward(exclude: exclude, domOnly: domOnly, propsToUpdate: props); + }; + } + + Map _propsToForward({Set exclude, bool domOnly = false, Map propsToUpdate}) { + Iterable consumedProps; try { - final unconsumedProps = {}; - final consumedProps = staticMeta.forMixins(exclude ?? {T}); - final consumedPropKeys = consumedProps.map((consumedProps) => consumedProps.keys); - forwardUnconsumedPropsV2(props, propsToUpdate: unconsumedProps, keySetsToOmit: consumedPropKeys); - return unconsumedProps; + consumedProps = exclude == null ? [staticMeta.forMixin(T)] : staticMeta.forMixins(exclude); } catch(_) { - if (exclude == null) { - throw ArgumentError('Could not find props meta for type $T.' + // If [domOnly] is `true`, it is alright for the meta lookup to fail, otherwise throw the error. + assert(exclude == null && domOnly == true, ArgumentError('Could not find props meta for type $T.' ' If this is not a props mixin, you need to specify its mixins as the second argument. For example:' - '\n ..addAll(props.getPropsToForward(exclude: {${T}Mixin})'); - } - rethrow; + '\n ..addAll(props.getPropsToForward(exclude: {${T}Mixin})').message); + //rethrow; } + final consumedPropKeys = consumedProps?.map((consumedProps) => consumedProps.keys); + //print(consumedPropKeys); + forwardUnconsumedPropsV2(props, propsToUpdate: propsToUpdate, keySetsToOmit: consumedPropKeys, onlyCopyDomProps: domOnly); + return propsToUpdate; } } diff --git a/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart index 3a279205a..61c7b1966 100644 --- a/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart +++ b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart @@ -290,67 +290,8 @@ void functionComponentTestHelper(UiFactory factory, // }); // }); - group('using `getPropsToForwardProps`', () { - TestProps initialProps; - TestPropsMixin secondProps; - final testId = 'testId'; - - setUp(() { - initialProps = (factory() - ..stringProp = stringProp - ..anotherProp = anotherProp - ..id = testId - ); - - secondProps = initialProps; - - // expect(secondProps.stringProp, isNull, reason: 'Test setup sanity check'); - // expect(secondProps.anotherProp, isNull, reason: 'Test setup sanity check'); - }); - - 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); - }); - - group('and props are correctly filtered', () { - - group('via exclude', () { - - test('for an empty set', () { - var unconsumedProps = factory(initialProps.getPropsToForward(exclude: {})); - - expect(unconsumedProps.stringProp, stringProp); - expect(unconsumedProps.anotherProp, anotherProp); - }); - - test('for a single value in set', () { - var unconsumedProps = factory(initialProps.getPropsToForward(exclude: {ASecondPropsMixin})); - - expect(unconsumedProps.stringProp, stringProp); - expect(unconsumedProps.anotherProp, isNull); - }); - - test('for multiple values in set', () { - var unconsumedProps = factory(initialProps.getPropsToForward(exclude: {ASecondPropsMixin, TestPropsMixin})); - - expect(unconsumedProps.stringProp, isNull); - expect(unconsumedProps.anotherProp, isNull); - }); - - test('for dom only ', () { - var unconsumedProps = factory(initialProps.getPropsToForward(domOnly: true)); - - expect(unconsumedProps.stringProp, isNull); - expect(unconsumedProps.anotherProp, isNull); - expect(unconsumedProps.id, testId); - }); - - }); - - }); - }); + testPropsToForward(factory: factory, modifyProps: true); + testPropsToForward(factory: factory, modifyProps: false); // group('using `addUnconsumedDomProps`', () // { @@ -387,6 +328,86 @@ void functionComponentTestHelper(UiFactory factory, }); } +testPropsToForward({UiFactory factory, bool modifyProps = false}) { + group(modifyProps ? 'using `modifyProps(props.addPropsToForward)`' : 'using `getPropsToForwardProps`', () { + TestProps initialProps; + TestPropsMixin secondProps; + const stringProp = 'stringProp'; + const anotherProp = 'anotherProp'; + const idAttributeValue = 'idAttributeValue'; + const aRandomDataAttributeValue = 'aRandomDataAttributeValue'; + const anAriaLabelPropValue = 'anAriaLabelPropValue'; + + setUp(() { + initialProps = (factory() + ..stringProp = stringProp + ..anotherProp = anotherProp + ..aRandomDataAttribute = aRandomDataAttributeValue + ..anAriaLabelAlias = anAriaLabelPropValue + ..id = idAttributeValue + ); + + secondProps = initialProps; + }); + + // TODO generics dont work :( + // test('by default excludes props mixin type that it is invoked on', () { + // var unconsumedProps = _propsToForward(props: secondProps, factory: factory, modifyProps: modifyProps); + // expect(unconsumedProps.anotherProp, anotherProp); + // expect(unconsumedProps.stringProp, isNull); + // }); + + group('and props are correctly filtered', () { + test('for an empty set', () { + var unconsumedProps = _propsToForward(exclude: {}, props: initialProps, factory: factory, modifyProps: modifyProps); + + expect(unconsumedProps.stringProp, stringProp); + expect(unconsumedProps.anotherProp, anotherProp); + }); + + test('for a single value in set', () { + var unconsumedProps = _propsToForward(exclude: {ASecondPropsMixin}, props: initialProps, factory: factory, modifyProps: modifyProps); + + expect(unconsumedProps.stringProp, stringProp); + expect(unconsumedProps.anotherProp, isNull); + }); + + test('for multiple values in set', () { + var unconsumedProps = _propsToForward(exclude: {ASecondPropsMixin, TestPropsMixin}, props: initialProps, factory: factory, modifyProps: modifyProps); + + expect(unconsumedProps.stringProp, isNull); + expect(unconsumedProps.anotherProp, isNull); + }); + + test('excludes dom attributes that are part of a mixin with `@Accessor` annotations ', () { + var unconsumedProps = _propsToForward(exclude: {DomAccessorPropsMixin}, props: initialProps, factory: factory, modifyProps: modifyProps); + + expect(unconsumedProps.stringProp, stringProp); + expect(unconsumedProps.anotherProp, anotherProp); + expect(unconsumedProps.aRandomDataAttribute, isNull); + expect(unconsumedProps.anAriaLabelAlias, isNull); + }); + + test('for dom only ', () { + var unconsumedProps = _propsToForward(domOnly: true, props: initialProps, factory: factory, modifyProps: modifyProps); + + expect(unconsumedProps.stringProp, isNull); + expect(unconsumedProps.anotherProp, isNull); + expect(unconsumedProps.aRandomDataAttribute, aRandomDataAttributeValue); + expect(unconsumedProps.anAriaLabelAlias, anAriaLabelPropValue); + expect(unconsumedProps.id, idAttributeValue); + }); + }); + }); +} + +TestProps _propsToForward({UiFactory factory, T props, bool modifyProps = false, Set exclude, bool domOnly = false }) { + if (modifyProps == true) { + return factory()..modifyProps(props.addPropsToForward(exclude: exclude, domOnly: domOnly)); + } + return factory(props.getPropsToForward(exclude: exclude, domOnly: domOnly)); +} + UiFactory BasicUiForwardRef = uiForwardRef( (props, ref) { return (Dom.div() @@ -553,4 +574,12 @@ mixin AThirdPropsMixin on UiProps { String aPropsFromAThirdMixin; } -class TestProps = UiProps with TestPropsMixin, ASecondPropsMixin, AThirdPropsMixin; +mixin DomAccessorPropsMixin on UiProps { + @Accessor(key: 'data-random', keyNamespace: '') + String aRandomDataAttribute; + + @Accessor(key: 'aria-label', keyNamespace: '') + String anAriaLabelAlias; +} + +class TestProps = UiProps with TestPropsMixin, ASecondPropsMixin, AThirdPropsMixin, DomAccessorPropsMixin; diff --git a/web/index.html b/web/index.html index 95cc558e0..478cb460c 100644 --- a/web/index.html +++ b/web/index.html @@ -33,7 +33,7 @@

OverReact Component Demos

diff --git a/web/uifunction/index.dart b/web/uifunction/index.dart index 5867d8a16..a423aa30c 100644 --- a/web/uifunction/index.dart +++ b/web/uifunction/index.dart @@ -19,6 +19,7 @@ import 'dart:html'; import 'package:over_react/over_react.dart'; import 'package:over_react/react_dom.dart' as react_dom; +import 'src/foobar.dart'; import 'src/foo.dart'; import 'src/bar.dart'; @@ -26,11 +27,10 @@ void main() { react_dom.render( StrictMode()( (FooBar() - ..foo = 'Foo!' ..bar = 'BAR!' ..somethingElse = 'forwarded this unused prop!' - //..['lol'] = true + ..lol = 'hello!!!' )(), (Button()..id='ButtonID'..isActive = true..['fancy']="true")('HEyyyy') ), diff --git a/web/uifunction/src/foo.dart b/web/uifunction/src/foo.dart index 70725fcb2..1e2564e45 100644 --- a/web/uifunction/src/foo.dart +++ b/web/uifunction/src/foo.dart @@ -4,48 +4,35 @@ import 'dart:js_util'; import 'package:over_react/over_react.dart'; part 'foo.over_react.g.dart'; -mixin FooBarPropsMixin on UiProps { - String foobar; +mixin FooPropsMixin on UiProps { + dynamic foo; } -class FooBarProps = UiProps +class FooProps = UiProps with BarPropsMixin, FooPropsMixin, SomethingElsePropsMixin; -final FooBar = uiFunction((props) { - return Fragment()( - (Foo() - ..addAll(props.getPropsToForward(exclude: {FooPropsMixin})) - )(), - (Bar() - ..addAll(props.getPropsToForward(exclude: {BarPropsMixin})) - )(), - (SomethingElse() - ..addAll(props.getPropsToForward(exclude: {SomethingElsePropsMixin})) - )(), - ); -}, _$FooBarConfig); - -mixin FooPropsMixin on UiProps { - dynamic foo; -} - -final Foo = uiFunction((props) { - if (props.length > 1 ) { - window.console.log(props.props); - } - return props.foo != null ? 'FOO!!!' : 'NO FOO :('; +final Foo = uiFunction((props) { + return (Bar() + ..addAll(props.getPropsToForward(exclude: {FooPropsMixin})) + )(); }, _$FooConfig); mixin BarPropsMixin on UiProps { String bar; + + @Accessor(key:'data-lol', keyNamespace: '') + String lol; } final Bar = uiFunction((props) { - //var consumedProps = props.staticMeta.forMixins({FooPropsMixin}); - //log('Bar', props.getPropsToForward(exclude: {FooPropsMixin})); - return props.bar != null ? 'BAR!!!' : 'NO BAR :('; + log('Bar', props.getPropsToForward()); + return Fragment()( + (Dom.div()..modifyProps(props.addPropsToForward(exclude: {BarPropsMixin}, domOnly: true)))(), + (SomethingElse() + ..modifyProps(props.addPropsToForward()) + )()); }, _$BarConfig); diff --git a/web/uifunction/src/foobar.dart b/web/uifunction/src/foobar.dart new file mode 100644 index 000000000..3eb4407a8 --- /dev/null +++ b/web/uifunction/src/foobar.dart @@ -0,0 +1,23 @@ +import 'dart:html'; +import 'dart:js_util'; + +import 'package:over_react/over_react.dart'; + +import 'foo.dart'; +part 'foobar.over_react.g.dart'; + +mixin FooBarPropsMixin on UiProps { + dynamic foobar; +} + +class FooBarProps = UiProps + with FooBarPropsMixin, + BarPropsMixin, + FooPropsMixin, + SomethingElsePropsMixin; + +final FooBar = uiFunction((props) { + return (Foo() + ..addAll(props.getPropsToForward(exclude: {FooBarPropsMixin})) + )(); +}, _$FooBarConfig); From 37aae200bf6b676ee294780b4c9f5fcad5bd87f0 Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Wed, 26 Jul 2023 14:30:01 -0700 Subject: [PATCH 05/32] uncomment old tests and fix generic test --- .../function_component_test.dart | 139 +++++++++--------- 1 file changed, 72 insertions(+), 67 deletions(-) diff --git a/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart index 61c7b1966..4dc544d7b 100644 --- a/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart +++ b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart @@ -260,71 +260,71 @@ void functionComponentTestHelper(UiFactory factory, const anotherProp = 'this should be filtered'; const className = 'aClassName'; - // 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 `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); + }); + }); testPropsToForward(factory: factory, modifyProps: true); testPropsToForward(factory: factory, modifyProps: false); - // 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); - // }); - // }); + 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); + }); + }); }); } @@ -350,12 +350,17 @@ testPropsToForward({UiFactory factory, bool modifyProps = false}) { secondProps = initialProps; }); - // TODO generics dont work :( - // test('by default excludes props mixin type that it is invoked on', () { - // var unconsumedProps = _propsToForward(props: secondProps, factory: factory, modifyProps: modifyProps); - // expect(unconsumedProps.anotherProp, anotherProp); - // expect(unconsumedProps.stringProp, isNull); - // }); + test('by default excludes props mixin type that it is invoked on', () { + TestProps unconsumedProps; + if (modifyProps == true) { + unconsumedProps = factory()..modifyProps(secondProps.addPropsToForward()); + } else { + unconsumedProps = factory(secondProps.getPropsToForward()); + } + + expect(unconsumedProps.anotherProp, anotherProp); + expect(unconsumedProps.stringProp, isNull); + }); group('and props are correctly filtered', () { test('for an empty set', () { From a6007d7d844ce39f4cb125d3e9981bda83170abd Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Wed, 26 Jul 2023 14:32:29 -0700 Subject: [PATCH 06/32] fix warnings --- web/uifunction/index.dart | 1 - web/uifunction/src/foobar.dart | 3 --- 2 files changed, 4 deletions(-) diff --git a/web/uifunction/index.dart b/web/uifunction/index.dart index a423aa30c..92d72f447 100644 --- a/web/uifunction/index.dart +++ b/web/uifunction/index.dart @@ -20,7 +20,6 @@ import 'package:over_react/over_react.dart'; import 'package:over_react/react_dom.dart' as react_dom; import 'src/foobar.dart'; -import 'src/foo.dart'; import 'src/bar.dart'; void main() { diff --git a/web/uifunction/src/foobar.dart b/web/uifunction/src/foobar.dart index 3eb4407a8..da6d5013b 100644 --- a/web/uifunction/src/foobar.dart +++ b/web/uifunction/src/foobar.dart @@ -1,6 +1,3 @@ -import 'dart:html'; -import 'dart:js_util'; - import 'package:over_react/over_react.dart'; import 'foo.dart'; From 4c7a63f69d64a433bc49679e29d6a3d7b4ba417c Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Wed, 26 Jul 2023 14:40:28 -0700 Subject: [PATCH 07/32] fix PropsToForward doc comment --- .../builder_helpers.dart | 30 +------------------ 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/lib/src/component_declaration/builder_helpers.dart b/lib/src/component_declaration/builder_helpers.dart index e6d226c5d..d4eeba391 100644 --- a/lib/src/component_declaration/builder_helpers.dart +++ b/lib/src/component_declaration/builder_helpers.dart @@ -134,35 +134,7 @@ 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})) - -*/ - +/// Helper static extension methods to make forwarding props easier. extension PropsToForward on T { From 98fe1d31da1ba3010be21f3e424bfecc950aa091 Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Wed, 26 Jul 2023 15:01:15 -0700 Subject: [PATCH 08/32] formatting --- lib/src/component_declaration/builder_helpers.dart | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/src/component_declaration/builder_helpers.dart b/lib/src/component_declaration/builder_helpers.dart index d4eeba391..d4332aa24 100644 --- a/lib/src/component_declaration/builder_helpers.dart +++ b/lib/src/component_declaration/builder_helpers.dart @@ -137,7 +137,6 @@ abstract class UiProps extends component_base.UiProps with GeneratedClass { /// Helper static extension methods to make forwarding props easier. extension PropsToForward on T { - /// Returns a copy of this instance's props excluding the keys found in [exclude]. /// /// [exclude] should be a `Set` of PropsMixin `Type`s. @@ -164,7 +163,6 @@ extension PropsToForward on T { return _propsToForward(exclude: exclude, domOnly: domOnly, propsToUpdate: {}); } - /// A utility function to be used with `modifyProps` to add props excluding the keys found in [exclude]. /// /// [exclude] should be a `Set` of PropsMixin `Type`s. @@ -196,7 +194,7 @@ extension PropsToForward on T { Map _propsToForward({Set exclude, bool domOnly = false, Map propsToUpdate}) { Iterable consumedProps; try { - consumedProps = exclude == null ? [staticMeta.forMixin(T)] : staticMeta.forMixins(exclude); + consumedProps = exclude == null ? [staticMeta.forMixin(T)] : staticMeta.forMixins(exclude).toList(); } catch(_) { // If [domOnly] is `true`, it is alright for the meta lookup to fail, otherwise throw the error. assert(exclude == null && domOnly == true, ArgumentError('Could not find props meta for type $T.' @@ -205,8 +203,12 @@ extension PropsToForward on T { //rethrow; } final consumedPropKeys = consumedProps?.map((consumedProps) => consumedProps.keys); - //print(consumedPropKeys); - forwardUnconsumedPropsV2(props, propsToUpdate: propsToUpdate, keySetsToOmit: consumedPropKeys, onlyCopyDomProps: domOnly); + forwardUnconsumedPropsV2( + props, + propsToUpdate: propsToUpdate, + keySetsToOmit: consumedPropKeys, + onlyCopyDomProps: domOnly, + ); return propsToUpdate; } } From 4b76cc2e2ff01b7303b32448337cce255b004169 Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Wed, 26 Jul 2023 15:10:17 -0700 Subject: [PATCH 09/32] simplify consumedProps assignment in _propsToForward --- lib/src/component_declaration/builder_helpers.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/component_declaration/builder_helpers.dart b/lib/src/component_declaration/builder_helpers.dart index d4332aa24..b5bffc4e4 100644 --- a/lib/src/component_declaration/builder_helpers.dart +++ b/lib/src/component_declaration/builder_helpers.dart @@ -194,7 +194,7 @@ extension PropsToForward on T { Map _propsToForward({Set exclude, bool domOnly = false, Map propsToUpdate}) { Iterable consumedProps; try { - consumedProps = exclude == null ? [staticMeta.forMixin(T)] : staticMeta.forMixins(exclude).toList(); + consumedProps = staticMeta.forMixins(exclude ?? {T}).toList(); } catch(_) { // If [domOnly] is `true`, it is alright for the meta lookup to fail, otherwise throw the error. assert(exclude == null && domOnly == true, ArgumentError('Could not find props meta for type $T.' From 2e30341b7c5ba016ae7385dc5852c857ddf2da46 Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Wed, 26 Jul 2023 15:11:55 -0700 Subject: [PATCH 10/32] remove commented out code --- lib/src/component_declaration/builder_helpers.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/component_declaration/builder_helpers.dart b/lib/src/component_declaration/builder_helpers.dart index b5bffc4e4..599c37de3 100644 --- a/lib/src/component_declaration/builder_helpers.dart +++ b/lib/src/component_declaration/builder_helpers.dart @@ -200,7 +200,6 @@ extension PropsToForward on T { assert(exclude == null && domOnly == true, ArgumentError('Could not find props meta for type $T.' ' If this is not a props mixin, you need to specify its mixins as the second argument. For example:' '\n ..addAll(props.getPropsToForward(exclude: {${T}Mixin})').message); - //rethrow; } final consumedPropKeys = consumedProps?.map((consumedProps) => consumedProps.keys); forwardUnconsumedPropsV2( From 7b2799bd3c3cf93c0a8b5e57771c108f4d57bae9 Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Wed, 26 Jul 2023 15:12:08 -0700 Subject: [PATCH 11/32] format --- lib/src/component_declaration/builder_helpers.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/component_declaration/builder_helpers.dart b/lib/src/component_declaration/builder_helpers.dart index 599c37de3..caf9db647 100644 --- a/lib/src/component_declaration/builder_helpers.dart +++ b/lib/src/component_declaration/builder_helpers.dart @@ -196,10 +196,10 @@ extension PropsToForward on T { try { consumedProps = staticMeta.forMixins(exclude ?? {T}).toList(); } catch(_) { - // If [domOnly] is `true`, it is alright for the meta lookup to fail, otherwise throw the error. - assert(exclude == null && domOnly == true, ArgumentError('Could not find props meta for type $T.' - ' If this is not a props mixin, you need to specify its mixins as the second argument. For example:' - '\n ..addAll(props.getPropsToForward(exclude: {${T}Mixin})').message); + // If [domOnly] is `true`, it is alright for the meta lookup to fail, otherwise throw the error. + assert(exclude == null && domOnly == true, ArgumentError('Could not find props meta for type $T.' + ' If this is not a props mixin, you need to specify its mixins as the second argument. For example:' + '\n ..addAll(props.getPropsToForward(exclude: {${T}Mixin})').message); } final consumedPropKeys = consumedProps?.map((consumedProps) => consumedProps.keys); forwardUnconsumedPropsV2( From 8a5f7d8ed0b6682c4ea50a98da3b7d49aa59e476 Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Wed, 26 Jul 2023 15:13:26 -0700 Subject: [PATCH 12/32] fix doc comments --- lib/src/component_declaration/builder_helpers.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/component_declaration/builder_helpers.dart b/lib/src/component_declaration/builder_helpers.dart index caf9db647..e343c0472 100644 --- a/lib/src/component_declaration/builder_helpers.dart +++ b/lib/src/component_declaration/builder_helpers.dart @@ -156,7 +156,7 @@ extension PropsToForward on T { /// return (Bar()..addAll(props.getPropsToForward(exclude: {FooPropsMixin}))(); /// ``` /// - /// To only add DOM props, use [addUnconsumedDomProps]. + /// To only add DOM props, use the [domOnly] named argument. /// /// Related: `UiComponent2`'s `addUnconsumedProps` Map getPropsToForward({Set exclude, bool domOnly = false}) { @@ -173,16 +173,16 @@ extension PropsToForward on T { /// ```dart /// // within a functional component: `uiFunction` /// // filter out the current components props when forwarding to Bar. - /// return (Bar()..addAll(props.getPropsToForward()))(); + /// return (Bar()..modifyProps(props.addPropsToForward()))(); /// ``` /// OR /// ```dart /// // within a functional component that has multiple mixins on a Props class: `uiFunction` /// // filter out the Components props when forwarding to Bar. - /// return (Bar()..addAll(props.getPropsToForward(exclude: {FooPropsMixin}))(); + /// return (Bar()..modifyProps(props.addPropsToForward(exclude: {FooPropsMixin}))(); /// ``` /// - /// To only add DOM props, use [addUnconsumedDomProps]. + /// To only add DOM props, use the [domOnly] named argument. /// /// Related: `UiComponent2`'s `addUnconsumedProps` PropsModifier addPropsToForward({Set exclude, bool domOnly = false}) { From 91a857204c100f3a30caeb3d8668246c92e2e868 Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Thu, 27 Jul 2023 12:19:08 -0700 Subject: [PATCH 13/32] add prop forwarding demo to web --- .../builder_helpers.dart | 2 +- web/uifunction/index.dart | 15 +- web/uifunction/index.html | 26 +++ web/uifunction/src/bar.dart | 217 ++---------------- web/uifunction/src/baz.dart | 18 ++ web/uifunction/src/button.dart | 56 +++++ web/uifunction/src/component_wrapper.dart | 162 +++++++++++++ web/uifunction/src/foo.dart | 54 ++--- web/uifunction/src/foobar.dart | 24 +- web/uifunction/src/utils.dart | 29 +++ 10 files changed, 357 insertions(+), 246 deletions(-) create mode 100644 web/uifunction/src/baz.dart create mode 100644 web/uifunction/src/button.dart create mode 100644 web/uifunction/src/component_wrapper.dart create mode 100644 web/uifunction/src/utils.dart diff --git a/lib/src/component_declaration/builder_helpers.dart b/lib/src/component_declaration/builder_helpers.dart index e343c0472..e10bc846d 100644 --- a/lib/src/component_declaration/builder_helpers.dart +++ b/lib/src/component_declaration/builder_helpers.dart @@ -192,7 +192,7 @@ extension PropsToForward on T { } Map _propsToForward({Set exclude, bool domOnly = false, Map propsToUpdate}) { - Iterable consumedProps; + Iterable consumedProps = []; try { consumedProps = staticMeta.forMixins(exclude ?? {T}).toList(); } catch(_) { diff --git a/web/uifunction/index.dart b/web/uifunction/index.dart index 92d72f447..b67b8aebf 100644 --- a/web/uifunction/index.dart +++ b/web/uifunction/index.dart @@ -20,18 +20,17 @@ import 'package:over_react/over_react.dart'; import 'package:over_react/react_dom.dart' as react_dom; import 'src/foobar.dart'; -import 'src/bar.dart'; - void main() { react_dom.render( StrictMode()( - (FooBar() - ..foo = 'Foo!' - ..bar = 'BAR!' - ..somethingElse = 'forwarded this unused prop!' - ..lol = 'hello!!!' + (FooBarDemo() + ..foobar = 'FooBar' + ..foo = 'Foo' + ..bar = 'Bar' + ..baz = 'Baz' + ..isFancy = true + ..className = 'fancy' )(), - (Button()..id='ButtonID'..isActive = true..['fancy']="true")('HEyyyy') ), querySelector('#uifunction'), ); diff --git a/web/uifunction/index.html b/web/uifunction/index.html index 5210c82da..9cc886b11 100644 --- a/web/uifunction/index.html +++ b/web/uifunction/index.html @@ -26,6 +26,32 @@ + + + +
diff --git a/web/uifunction/src/bar.dart b/web/uifunction/src/bar.dart index 79f6a9245..71d71d9bc 100644 --- a/web/uifunction/src/bar.dart +++ b/web/uifunction/src/bar.dart @@ -14,205 +14,34 @@ import 'package:over_react/over_react.dart'; -part 'bar.over_react.g.dart'; - -/// Nest one or more `Button` components within a [ListGroup] -/// to render individual items within a list. -/// -/// See: - -UiFactory Button = castUiFactory(_$Button); // ignore: undefined_identifier - -mixin ButtonProps on UiProps { - /// The skin / "context" for the [Button]. - /// - /// See: . - /// - /// Default: [ButtonSkin.PRIMARY] - ButtonSkin skin; - - /// The size of the [Button]. - /// - /// See: . - /// - /// Default: [ButtonSize.DEFAULT] - ButtonSize size; - - /// Whether the [Button] should appear "active". - /// - /// See: - /// - /// Default: false - bool isActive; - - /// Whether the [Button] is disabled. - /// - /// See: - /// - /// Default: false - @Accessor(key: 'disabled', keyNamespace: '') - bool isDisabled; - - /// Whether the [Button] is a block level button -- that which spans the full - /// width of its parent. - /// - /// Default: false - bool isBlock; - - /// The HTML `href` attribute value for the [Button]. - /// - /// If set, the item will render via [Dom.a]. - /// - /// _Proxies [DomPropsMixin.href]_ - @Accessor(keyNamespace: '') - String href; - - /// The HTML `target` attribute value for the [Button]. - /// - /// If set, the item will render via [Dom.a]. - /// - /// _Proxies [DomPropsMixin.target]_ - @Accessor(keyNamespace: '') - String target; - - /// The HTML `type` attribute value for the [Button] when - /// rendered via [Dom.button]. - /// - /// This will only be applied if [href] is not set. - /// - /// _Proxies [DomPropsMixin.type]_ - /// - /// Default: [ButtonType.BUTTON] - String type; -} - -mixin ButtonState on UiState {} - -class ButtonComponent - extends UiStatefulComponent2 { - @override - get defaultProps => (newProps() - ..skin = ButtonSkin.PRIMARY - ..size = ButtonSize.DEFAULT - ..isActive = false - ..isDisabled = false - ..isBlock = false - ..type = 'button' - ); - -// T get props; -// - @override - render() { - return renderButton(props.children); - } - - ReactElement renderButton(dynamic children) { - BuilderOnlyUiFactory factory = buttonDomNodeFactory; - - return (factory() - ..addAll(props.getPropsToForward()) - ..className = getButtonClasses().toClassName() - ..href = props.href - ..target = props.target - ..type = type - ..disabled = isAnchorLink ? null : props.isDisabled - ..addProps(ariaProps() - ..disabled = isAnchorLink ? props.isDisabled : null - ) - )(children); - } - - ClassNameBuilder getButtonClasses() { - return forwardingClassNameBuilder() - ..add('btn') - ..add('btn-block', props.isBlock) - ..add('active', isActive) - ..add('disabled', props.isDisabled) - ..add(props.skin.className) - ..add(props.size.className); - } +import 'baz.dart'; +import 'button.dart'; +import 'component_wrapper.dart'; +import 'utils.dart'; - BuilderOnlyUiFactory get buttonDomNodeFactory => isAnchorLink ? Dom.a : Dom.button; - - bool get isAnchorLink => props.href != null; - - bool get isActive => props.isActive; - - String get type => isAnchorLink ? null : props.type; -} - -/// Contextual skin options for a [Button] component. -class ButtonSkin extends ClassNameConstant { - const ButtonSkin._(String name, String className) : super(name, className); - - /// [className] value: 'btn-primary' - static const ButtonSkin PRIMARY = - ButtonSkin._('PRIMARY', 'btn-primary'); - - /// [className] value: 'btn-secondary' - static const ButtonSkin SECONDARY = - ButtonSkin._('SECONDARY', 'btn-secondary'); - - /// [className] value: 'btn-danger' - static const ButtonSkin DANGER = - ButtonSkin._('DANGER', 'btn-danger'); - - /// [className] value: 'btn-success' - static const ButtonSkin SUCCESS = - ButtonSkin._('SUCCESS', 'btn-success'); - - /// [className] value: 'btn-warning' - static const ButtonSkin WARNING = - ButtonSkin._('WARNING', 'btn-warning'); - - /// [className] value: 'btn-info' - static const ButtonSkin INFO = - ButtonSkin._('INFO', 'btn-info'); - - /// [className] value: 'btn-link' - static const ButtonSkin LINK = - ButtonSkin._('LINK', 'btn-link'); - - /// [className] value: 'btn-outline-primary' - static const ButtonSkin PRIMARY_OUTLINE = - ButtonSkin._('PRIMARY_OUTLINE', 'btn-outline-primary'); - - /// [className] value: 'btn-outline-secondary' - static const ButtonSkin SECONDARY_OUTLINE = - ButtonSkin._('SECONDARY_OUTLINE', 'btn-outline-secondary'); - - /// [className] value: 'btn-outline-danger' - static const ButtonSkin DANGER_OUTLINE = - ButtonSkin._('DANGER_OUTLINE', 'btn-outline-danger'); - - /// [className] value: 'btn-outline-success' - static const ButtonSkin SUCCESS_OUTLINE = - ButtonSkin._('SUCCESS_OUTLINE', 'btn-outline-success'); +part 'bar.over_react.g.dart'; - /// [className] value: 'btn-outline-warning' - static const ButtonSkin WARNING_OUTLINE = - ButtonSkin._('WARNING_OUTLINE', 'btn-outline-warning'); +mixin BarPropsMixin on UiProps { + String bar; - /// [className] value: 'btn-outline-info' - static const ButtonSkin INFO_OUTLINE = - ButtonSkin._('INFO_OUTLINE', 'btn-outline-info'); + @Accessor(key:'data-is-fancy', keyNamespace: '') + bool isFancy; } -/// Size options for a [Button] component. -class ButtonSize extends ClassNameConstant { - const ButtonSize._(String name, String className) : super(name, className); +const excludePropsMixins = {BarPropsMixin}; - /// [className] value: '' - static const ButtonSize DEFAULT = - ButtonSize._('DEFAULT', ''); +final BarDemo = createVisualComponent(Bar, excludedMixins: excludePropsMixins); - /// [className] value: 'btn-lg' - static const ButtonSize LARGE = - ButtonSize._('LARGE', 'btn-lg'); - - /// [className] value: 'btn-sm' - static const ButtonSize SMALL = - ButtonSize._('SMALL', 'btn-sm'); -} +final Bar = uiFunction((props) { + final randomState = useState(true); + return Dom.div()( + (Button()..onClick = (e) => randomState.set(!randomState.value))('UPDATE STATE'), + (BazDemo() + ..addAll(props.getPropsToForward(exclude: excludePropsMixins)) + )(), + (DomDivDemo() + ..modifyProps(props.addPropsToForward(exclude: {}, domOnly: true)) + )('This div should have a `data-is-fancy` attribute!'), + ); +}, _$BarConfig); diff --git a/web/uifunction/src/baz.dart b/web/uifunction/src/baz.dart new file mode 100644 index 000000000..c367e1c84 --- /dev/null +++ b/web/uifunction/src/baz.dart @@ -0,0 +1,18 @@ +import 'package:over_react/over_react.dart'; + +import 'component_wrapper.dart'; +import 'utils.dart'; + +part 'baz.over_react.g.dart'; + +mixin BazPropsMixin on UiProps { + dynamic baz; +} + +final BazDemo = createVisualComponent(Baz, domOnly: true); + +final Baz = uiFunction((props) { + return (DomDivDemo() + ..addAll(props.getPropsToForward(domOnly: true)) + )(props.baz != null ? 'We received props.Baz! ${props.baz}' : 'props.Baz was not forwarded.'); +}, _$BazConfig); diff --git a/web/uifunction/src/button.dart b/web/uifunction/src/button.dart new file mode 100644 index 000000000..18845ee88 --- /dev/null +++ b/web/uifunction/src/button.dart @@ -0,0 +1,56 @@ +// Copyright 2020 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'dart:js_util'; + +import 'package:over_react/over_react.dart'; + +part 'button.over_react.g.dart'; + +mixin ButtonPropsMixin on UiProps {} + +final Button = uiFunction((props) { + final buttonStyles = { + 'display': 'inline-block', + 'color': 'rgba(255, 255, 255, 0.75)', + 'marginBottom': '10px', + 'borderRadius': '.25rem', + 'backgroundColor': '#0075DB', + 'fontWeight': 'bold', + 'fontSize': 'smaller', + 'border': 'none', + 'padding': '10px 20px', + 'cursor': 'pointer', + ...(props.style ?? {}) + }; + return (Dom.button() + ..modifyProps(props.addPropsToForward(domOnly: true)) + ..style = { + 'display': 'inline-block', + 'color': 'rgba(255, 255, 255, 0.75)', + 'marginBottom': '10px', + 'borderRadius': '5px', + 'backgroundColor': '#0075DB', + 'fontWeight': 'bold', + 'fontSize': 'smaller', + 'border': 'none', + 'padding': '10px 20px', + 'cursor': 'pointer', + 'outline': 'none', + ...(props.style ?? {}) + } + ..onMouseOver = ((e) => setProperty(getProperty(e.currentTarget, 'style'), 'backgroundColor', 'rgb(0, 98, 184)')) + ..onMouseLeave = ((e) => setProperty(getProperty(e.currentTarget, 'style'), 'backgroundColor', '#0075DB')) + )(props.children); +}, _$ButtonConfig); diff --git a/web/uifunction/src/component_wrapper.dart b/web/uifunction/src/component_wrapper.dart new file mode 100644 index 000000000..599a0bd41 --- /dev/null +++ b/web/uifunction/src/component_wrapper.dart @@ -0,0 +1,162 @@ +import 'dart:html'; +import 'dart:js_util'; +import 'dart:math'; + +import 'package:over_react/over_react.dart'; + +part 'component_wrapper.over_react.g.dart'; + +final containerStyles = { + 'position': 'relative', + 'border': '2px solid #ddd', + 'padding': '20px', + 'borderRadius': '5px', + 'backgroundColor': '#1a191c', + 'color': '#e7e9ec', + 'WebkitFontSmoothing': 'antialiased', + 'MozOsxFontSmoothing': 'grayscale', +}; + +UiFactory createVisualComponent(UiFactory otherFactory, {Set excludedMixins = const {}, bool domOnly = false}) { + final blankFactoryProps = otherFactory(); + final blankFactoryReactElement = blankFactoryProps(); + final componentName = getProperty(blankFactoryProps?.componentFactory, 'displayName') ?? getReactElementDisplayName(blankFactoryReactElement); + UiFactory VisualComponentHoc = uiFunction( + (props) { + var isDomComponent = true; + List consumedPropKeys = []; + final consumedPropKeysForToForward = []; + if (props is! DomProps) { + isDomComponent = false; + final consumedProps = props.staticMeta.forMixins(excludedMixins ?? {T}); + consumedPropKeys = consumedProps.map((v) => v.keys).expand((i) => i).toList(); + consumedPropKeysForToForward.addAll(consumedPropKeys); + } + props.removeWhere((key, value) => consumedPropKeysForToForward.contains(key)); + final componentElement = (otherFactory()..addProps(props))(props.children) as ReactElement; + window.console.log(componentElement); + return Dom.div()( + (Dom.div()..className = 'component-container'..style = containerStyles)( + (Dom.span() + ..className = 'rerender' + ..key=Random().nextInt(1000000) + ..style = { + 'position':'absolute', + 'left': '1.25rem', + 'top': '0.5rem', + 'color': 'EE0000', + 'fontWeight': 'bold', + 'fontSize': 'smaller', + } + )( + 'RENDER' + ), + (Dom.span()..className = 'component-name'..style = {'fontWeight': 'bold', 'paddingBottom': '10px', 'fontSize': '2rem'})(componentName), + //(MapDisplay()..name = 'STATE'..backgroundColor = '#2A8300'..mapToDisplay = Map.from(componentElement.))(), + (MapDisplay() + ..name = 'CONSUMED PROPS' + ..backgroundColor = '#2A8300' + ..mapToDisplay = (Map.from(props)..removeWhere((key, value) => !(consumedPropKeys?.contains(key) ?? true))..remove('children')) + )(), + (MapDisplay() + ..name = isDomComponent ? 'CONSUMED PROPS' : 'PROPS TO FORWARD' + ..backgroundColor = isDomComponent ? '#2A8300' : '#1c4498' + ..mapToDisplay = (JsBackedMap.fromJs(componentElement.props) + ..removeWhere((key, value) => consumedPropKeysForToForward.contains(key)) + ..remove('children')) + )(), + (Dom.div()..className = 'component-render')(componentElement), + ), + ); + }, + UiFactoryConfig( + displayName: 'VisualComponentHoc($componentName)', + propsFactory: PropsFactory.fromUiFactory(otherFactory), + ), + ); + + return VisualComponentHoc; +} + +String getReactElementDisplayName(ReactElement element) { + final elementType = element?.type; + final elementTypeName = getProperty(elementType, 'name'); + if (elementType != null && elementType is String) { + return elementType; + } else if (elementTypeName != null && elementTypeName is String) { + return elementTypeName; + } + return 'Anonymous'; +} + +mixin MapDisplayPropsMixin on UiProps { + String name; + String backgroundColor; + Map mapToDisplay; +} + +final MapDisplay = uiFunction((props) { + final backgroundColor = props.backgroundColor ?? '#2070f3'; + final name = props.name ?? 'PROPS'; + final mapToDisplay = props.mapToDisplay ?? {}; + + if (mapToDisplay.isEmpty) { + return null; + } + + final mapDisplay = []; + + mapToDisplay.forEach((key, value) { + mapDisplay.add((Dom.div()..className = 'prop-entry'..key = '$key')('$key: $value')); + }); + + return (Dom.div() + ..className = 'prop-list-container' + ..style = { + 'backgroundColor': backgroundColor, + 'display': 'flex', + 'borderRadius': '5px', + 'overflow': 'hidden', + 'marginBottom': '10px', + 'position': 'relative', + } + )( + (Dom.span() + ..className = 'prop-list-name' + ..style = const { + 'padding': '10px', + 'alignItems': 'center', + 'justifyContent': 'center', + 'display': 'flex', + 'color': 'rgba(255, 255, 255, .75)', + 'fontWeight': 'bold', + 'fontSize': 'smaller', + } + )(name), + (Dom.div() + ..id = 'prop-entries-container' + ..style = const { + 'position': 'relative', + 'padding': '10px', + 'fontWeight': '400', + 'fontFamily': 'Fira Code', + 'flex': '1', + } + )( + (Dom.div() + ..id = 'prop-entries-light-background' + ..style = const { + 'width': '100%', + 'height': '100%', + 'position': 'absolute', + 'top': 0, + 'left': 0, + 'background': '#b7b7b7', + 'mixBlendMode': 'soft-light', + 'opacity': '.75', + } + )(), + mapDisplay, + ), + ); +}, _$MapDisplayConfig); diff --git a/web/uifunction/src/foo.dart b/web/uifunction/src/foo.dart index 1e2564e45..be229fd3d 100644 --- a/web/uifunction/src/foo.dart +++ b/web/uifunction/src/foo.dart @@ -1,52 +1,30 @@ -import 'dart:html'; -import 'dart:js_util'; - import 'package:over_react/over_react.dart'; -part 'foo.over_react.g.dart'; -mixin FooPropsMixin on UiProps { - dynamic foo; -} +import 'button.dart'; +import 'component_wrapper.dart'; +import 'utils.dart'; -class FooProps = UiProps - with BarPropsMixin, - FooPropsMixin, - SomethingElsePropsMixin; +part 'foo.over_react.g.dart'; -final Foo = uiFunction((props) { - return (Bar() - ..addAll(props.getPropsToForward(exclude: {FooPropsMixin})) - )(); -}, _$FooConfig); +// This is what it would default too when you dont include an `exclude` argument. +const excludeMixins = {FooPropsMixin}; -mixin BarPropsMixin on UiProps { - String bar; +final FooDemo = createVisualComponent(Foo, excludedMixins: excludeMixins); - @Accessor(key:'data-lol', keyNamespace: '') - String lol; +mixin FooPropsMixin on UiProps { + dynamic foo; } -final Bar = uiFunction((props) { - log('Bar', props.getPropsToForward()); - return Fragment()( - (Dom.div()..modifyProps(props.addPropsToForward(exclude: {BarPropsMixin}, domOnly: true)))(), - (SomethingElse() - ..modifyProps(props.addPropsToForward()) +final Foo = uiFunction((props) { + final randomState = useState(true); + return Dom.div()( + (Button()..onClick = (e) => randomState.set(!randomState.value))('UPDATE STATE'), + (DomDivDemo() + ..addAll(props.getPropsToForward(domOnly: true)) )()); -}, _$BarConfig); - +}, _$FooConfig); -mixin SomethingElsePropsMixin on UiProps { - dynamic somethingElse; -} -final SomethingElse = uiFunction((props) { - log('SomethingElse', props.getPropsToForward()); - return (Dom.div() - ..addAll(props.getPropsToForward()) - )(props.somethingElse != null ? 'We received props.somethingElse! ${props.somethingElse}' : 'props.somethingElse was not forwarded.'); -}, _$SomethingElseConfig); -log(dynamic name, dynamic x) => callMethod(getProperty(window,'console'), 'log', [name, jsify(x)]); diff --git a/web/uifunction/src/foobar.dart b/web/uifunction/src/foobar.dart index da6d5013b..7d06f0f6e 100644 --- a/web/uifunction/src/foobar.dart +++ b/web/uifunction/src/foobar.dart @@ -1,20 +1,34 @@ import 'package:over_react/over_react.dart'; +import 'bar.dart'; +import 'baz.dart'; import 'foo.dart'; +import 'component_wrapper.dart'; + part 'foobar.over_react.g.dart'; +const excludeMixins = {FooBarPropsMixin}; + +final FooBarDemo = createVisualComponent(FooBar, excludedMixins: excludeMixins); + mixin FooBarPropsMixin on UiProps { dynamic foobar; } class FooBarProps = UiProps with FooBarPropsMixin, - BarPropsMixin, FooPropsMixin, - SomethingElsePropsMixin; + BarPropsMixin, + BazPropsMixin; + final FooBar = uiFunction((props) { - return (Foo() - ..addAll(props.getPropsToForward(exclude: {FooBarPropsMixin})) - )(); + return (Dom.div()..style = {'display': 'flex'})( + (FooDemo() + ..addAll(props.getPropsToForward(exclude: excludeMixins)) + )(), + (BarDemo() + ..modifyProps(props.addPropsToForward(exclude: excludeMixins)) + )(), + ); }, _$FooBarConfig); diff --git a/web/uifunction/src/utils.dart b/web/uifunction/src/utils.dart new file mode 100644 index 000000000..320b28908 --- /dev/null +++ b/web/uifunction/src/utils.dart @@ -0,0 +1,29 @@ +import 'dart:html'; +import 'dart:js_util'; + +import 'package:over_react/over_react.dart'; +import 'package:react/react_client/js_interop_helpers.dart'; + +import 'component_wrapper.dart'; + +const undefined = Undefined(); +class Undefined { + const Undefined(); +} + +log([c1 = undefined, c2 = undefined, c3 = undefined, c4 = undefined, c5 = undefined, c6 = undefined, c7 = undefined, c8 = undefined, c9 = undefined, c10 = undefined, c11 = undefined, c12 = undefined, c13 = undefined, c14 = undefined, c15 = undefined, c16 = undefined, c17 = undefined, c18 = undefined, c19 = undefined, c20 = undefined, c21 = undefined, c22 = undefined, c23 = undefined, c24 = undefined, c25 = undefined, c26 = undefined, c27 = undefined, c28 = undefined, c29 = undefined, c30 = undefined, c31 = undefined, c32 = undefined, c33 = undefined, c34 = undefined, c35 = undefined, c36 = undefined, c37 = undefined, c38 = undefined, c39 = undefined, c40 = undefined]) { + final arguments = [c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15, c16, c17, c18, c19, c20, c21, c22, c23, c24, c25, c26, c27, c28, c29, c30, c31, c32, c33, c34, c35, c36, c37, c38, c39, c40] + .takeWhile((child) => !identical(child, undefined)) + .toList() + ..map((element) { + try { return jsifyAndAllowInterop(element); } catch (e){} + return element; + }); + return callMethod(getProperty(window,'console'), 'log', [...arguments]); +} + +group(String groupName) => window.console.group(groupName); + +groupEnd() => window.console.groupEnd(); + +final DomDivDemo = createVisualComponent(Dom.div, domOnly: true); From 3c39d20ad4fc75a4d01b3688bf0b0f7b05cd0574 Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Wed, 9 Aug 2023 08:25:21 -0700 Subject: [PATCH 14/32] add test for error case --- .../new_boilerplate/function_component_test.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart index 4dc544d7b..b5599ebfc 100644 --- a/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart +++ b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart @@ -360,6 +360,7 @@ testPropsToForward({UiFactory factory, bool modifyProps = false}) { expect(unconsumedProps.anotherProp, anotherProp); expect(unconsumedProps.stringProp, isNull); + expect(unconsumedProps.id, idAttributeValue); }); group('and props are correctly filtered', () { @@ -368,6 +369,7 @@ testPropsToForward({UiFactory factory, bool modifyProps = false}) { expect(unconsumedProps.stringProp, stringProp); expect(unconsumedProps.anotherProp, anotherProp); + expect(unconsumedProps.id, idAttributeValue); }); test('for a single value in set', () { @@ -375,6 +377,7 @@ testPropsToForward({UiFactory factory, bool modifyProps = false}) { expect(unconsumedProps.stringProp, stringProp); expect(unconsumedProps.anotherProp, isNull); + expect(unconsumedProps.id, idAttributeValue); }); test('for multiple values in set', () { @@ -382,6 +385,7 @@ testPropsToForward({UiFactory factory, bool modifyProps = false}) { expect(unconsumedProps.stringProp, isNull); expect(unconsumedProps.anotherProp, isNull); + expect(unconsumedProps.id, idAttributeValue); }); test('excludes dom attributes that are part of a mixin with `@Accessor` annotations ', () { @@ -391,6 +395,7 @@ testPropsToForward({UiFactory factory, bool modifyProps = false}) { expect(unconsumedProps.anotherProp, anotherProp); expect(unconsumedProps.aRandomDataAttribute, isNull); expect(unconsumedProps.anAriaLabelAlias, isNull); + expect(unconsumedProps.id, idAttributeValue); }); test('for dom only ', () { @@ -403,6 +408,11 @@ testPropsToForward({UiFactory factory, bool modifyProps = false}) { expect(unconsumedProps.id, idAttributeValue); }); }); + + test('throws an error when not providing an exclude argument and the props class is NOT a mixin', () { + expect(() => _propsToForward(domOnly: true, props: initialProps, factory: factory, modifyProps: modifyProps), + throwsArgumentError); + }); }); } From fb07ac00f7e0fa51f24b1fbbde499b4567a32fb8 Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Wed, 9 Aug 2023 08:25:29 -0700 Subject: [PATCH 15/32] address feedback --- .../builder_helpers.dart | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/lib/src/component_declaration/builder_helpers.dart b/lib/src/component_declaration/builder_helpers.dart index e10bc846d..919cda038 100644 --- a/lib/src/component_declaration/builder_helpers.dart +++ b/lib/src/component_declaration/builder_helpers.dart @@ -140,20 +140,39 @@ extension PropsToForward on T { /// Returns a copy of this instance's props excluding the keys found in [exclude]. /// /// [exclude] should be a `Set` of PropsMixin `Type`s. - /// If [exclude] is not set it defaults to using the current instances Type. + /// If [exclude] is not set, it defaults to using the current instance's Type. /// /// __Example:__ /// + /// Component with a single props mixin: /// ```dart - /// // within a functional component: `uiFunction` - /// // filter out the current components props when forwarding to Bar. - /// return (Bar()..addAll(props.getPropsToForward()))(); + /// mixin FooPropsMixin on UiProps { + /// String foo; + /// } + /// + /// UiFactory Foo = uiFunction((props) { + /// return (Bar() + /// // Filter out props declared in FooPropsMixin + /// // (used as the default for `exclude` since that's what `props` is statically typed as) + /// // when forwarding to Bar. + /// ..addAll(props.getPropsToForward()) + /// )(); + /// }); /// ``` - /// OR + /// + /// Component with a more than one props mixin: /// ```dart - /// // within a functional component that has multiple mixins on a Props class: `uiFunction` - /// // filter out the Components props when forwarding to Bar. - /// return (Bar()..addAll(props.getPropsToForward(exclude: {FooPropsMixin}))(); + /// mixin FooPropsMixin on UiProps { + /// String foo; + /// } + /// class FooProps = UiProps with BarProps, FooPropsMixin; + /// + /// UiFactory Foo = uiFunction((props) { + /// return (Bar() + /// // Filter out props declared in FooPropsMixin when forwarding to Bar. + /// ..addAll(props.getPropsToForward({FooPropsMixin})) + /// )(); + /// }); /// ``` /// /// To only add DOM props, use the [domOnly] named argument. From 31a9bade111caec71fe324dc305ab12117eff6a2 Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Wed, 9 Aug 2023 08:44:33 -0700 Subject: [PATCH 16/32] fix error test --- .../new_boilerplate/function_component_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart index b5599ebfc..088ad50d9 100644 --- a/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart +++ b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart @@ -409,8 +409,8 @@ testPropsToForward({UiFactory factory, bool modifyProps = false}) { }); }); - test('throws an error when not providing an exclude argument and the props class is NOT a mixin', () { - expect(() => _propsToForward(domOnly: true, props: initialProps, factory: factory, modifyProps: modifyProps), + test('which throws an error when not providing an exclude argument and the props class is NOT a mixin and `domOnly` is NOT `true`', () { + expect(() => _propsToForward(props: initialProps, factory: factory, modifyProps: modifyProps), throwsArgumentError); }); }); From f8d89de248bd7e9a2c5a3802004bb7e29efb9d6c Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Wed, 9 Aug 2023 08:50:14 -0700 Subject: [PATCH 17/32] add rethrow to propsToForward try catch --- lib/src/component_declaration/builder_helpers.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/src/component_declaration/builder_helpers.dart b/lib/src/component_declaration/builder_helpers.dart index 919cda038..b7fcbe767 100644 --- a/lib/src/component_declaration/builder_helpers.dart +++ b/lib/src/component_declaration/builder_helpers.dart @@ -219,6 +219,9 @@ extension PropsToForward on T { assert(exclude == null && domOnly == true, ArgumentError('Could not find props meta for type $T.' ' If this is not a props mixin, you need to specify its mixins as the second argument. For example:' '\n ..addAll(props.getPropsToForward(exclude: {${T}Mixin})').message); + if (exclude == null && domOnly == false) { + rethrow; + } } final consumedPropKeys = consumedProps?.map((consumedProps) => consumedProps.keys); forwardUnconsumedPropsV2( From 80b2bab3ef211f3a6365a9b370b1afbe32fb37c5 Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Wed, 9 Aug 2023 15:13:13 -0700 Subject: [PATCH 18/32] only run in ddc --- lib/src/component_declaration/builder_helpers.dart | 4 +--- .../new_boilerplate/function_component_test.dart | 6 ++++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/src/component_declaration/builder_helpers.dart b/lib/src/component_declaration/builder_helpers.dart index b7fcbe767..9cd669fec 100644 --- a/lib/src/component_declaration/builder_helpers.dart +++ b/lib/src/component_declaration/builder_helpers.dart @@ -219,9 +219,7 @@ extension PropsToForward on T { assert(exclude == null && domOnly == true, ArgumentError('Could not find props meta for type $T.' ' If this is not a props mixin, you need to specify its mixins as the second argument. For example:' '\n ..addAll(props.getPropsToForward(exclude: {${T}Mixin})').message); - if (exclude == null && domOnly == false) { - rethrow; - } + rethrow; } final consumedPropKeys = consumedProps?.map((consumedProps) => consumedProps.keys); forwardUnconsumedPropsV2( diff --git a/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart index 088ad50d9..c051efce2 100644 --- a/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart +++ b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart @@ -14,6 +14,7 @@ @TestOn('browser') +import 'dart:developer'; import 'dart:html'; import 'package:over_react/over_react.dart'; @@ -410,9 +411,10 @@ testPropsToForward({UiFactory factory, bool modifyProps = false}) { }); test('which throws an error when not providing an exclude argument and the props class is NOT a mixin and `domOnly` is NOT `true`', () { - expect(() => _propsToForward(props: initialProps, factory: factory, modifyProps: modifyProps), + debugger(); + expect(() => _propsToForward(exclude: null, props: initialProps, factory: factory, modifyProps: modifyProps), throwsArgumentError); - }); + }, tags: 'ddc'); }); } From b20ec362e2b32e6cd9b6cef200a2363b50527e20 Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Wed, 9 Aug 2023 15:19:36 -0700 Subject: [PATCH 19/32] maybe fix test? --- .../new_boilerplate/function_component_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart index c051efce2..f5abbb0c9 100644 --- a/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart +++ b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart @@ -413,7 +413,7 @@ testPropsToForward({UiFactory factory, bool modifyProps = false}) { test('which throws an error when not providing an exclude argument and the props class is NOT a mixin and `domOnly` is NOT `true`', () { debugger(); expect(() => _propsToForward(exclude: null, props: initialProps, factory: factory, modifyProps: modifyProps), - throwsArgumentError); + throwsA(isA())); }, tags: 'ddc'); }); } From a068cbbede9a47065296220c02bf7fda297ad03b Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Wed, 9 Aug 2023 15:45:24 -0700 Subject: [PATCH 20/32] fix assertion logic --- lib/src/component_declaration/builder_helpers.dart | 2 +- .../new_boilerplate/function_component_test.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/component_declaration/builder_helpers.dart b/lib/src/component_declaration/builder_helpers.dart index 9cd669fec..cb35412e7 100644 --- a/lib/src/component_declaration/builder_helpers.dart +++ b/lib/src/component_declaration/builder_helpers.dart @@ -216,7 +216,7 @@ extension PropsToForward on T { consumedProps = staticMeta.forMixins(exclude ?? {T}).toList(); } catch(_) { // If [domOnly] is `true`, it is alright for the meta lookup to fail, otherwise throw the error. - assert(exclude == null && domOnly == true, ArgumentError('Could not find props meta for type $T.' + assert(exclude != null, ArgumentError('Could not find props meta for type $T.' ' If this is not a props mixin, you need to specify its mixins as the second argument. For example:' '\n ..addAll(props.getPropsToForward(exclude: {${T}Mixin})').message); rethrow; diff --git a/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart index f5abbb0c9..33ab35ca9 100644 --- a/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart +++ b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart @@ -400,7 +400,7 @@ testPropsToForward({UiFactory factory, bool modifyProps = false}) { }); test('for dom only ', () { - var unconsumedProps = _propsToForward(domOnly: true, props: initialProps, factory: factory, modifyProps: modifyProps); + var unconsumedProps = _propsToForward(exclude: {}, domOnly: true, props: initialProps, factory: factory, modifyProps: modifyProps); expect(unconsumedProps.stringProp, isNull); expect(unconsumedProps.anotherProp, isNull); From bc1a78cf2acbc164e97e762460f05adb9a1ea378 Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Wed, 9 Aug 2023 15:46:24 -0700 Subject: [PATCH 21/32] uncomment out tests --- ...over_react_component_declaration_test.dart | 204 +++++++++--------- 1 file changed, 102 insertions(+), 102 deletions(-) diff --git a/test/over_react_component_declaration_test.dart b/test/over_react_component_declaration_test.dart index a26e8327e..617a3fbdb 100644 --- a/test/over_react_component_declaration_test.dart +++ b/test/over_react_component_declaration_test.dart @@ -22,120 +22,120 @@ library over_react_test; import 'package:over_react/over_react.dart'; import 'package:test/test.dart'; -// import 'over_react/component_declaration/component_base_test.dart' as component_base_test; -// import 'over_react/component_declaration/component_type_checking_test.dart' as component_type_checking_test; -// import 'over_react/component_declaration/redux_component_test.dart' as redux_component_test; +import 'over_react/component_declaration/component_base_test.dart' as component_base_test; +import 'over_react/component_declaration/component_type_checking_test.dart' as component_type_checking_test; +import 'over_react/component_declaration/redux_component_test.dart' as redux_component_test; -// import 'over_react/component_declaration/flux_component_test/flux_component_test.dart' as flux_component_test; -// import 'over_react/component_declaration/flux_component_test/component2/flux_component_test.dart' as component2_flux_component_test; +import 'over_react/component_declaration/flux_component_test/flux_component_test.dart' as flux_component_test; +import 'over_react/component_declaration/flux_component_test/component2/flux_component_test.dart' as component2_flux_component_test; -// import 'over_react/component_declaration/builder_helpers_test.dart' as builder_helpers_test; -// import 'over_react/component_declaration/builder_integration_tests/abstract_accessor_integration_test.dart' as abstract_accessor_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/accessor_mixin_integration_test.dart' as accessor_mixin_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/component_integration_test.dart' as component_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/constant_required_accessor_integration_test.dart' as constant_required_accessor_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/do_not_generate_accessor_integration_test.dart' as do_not_generate_accessor_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/namespaced_accessor_integration_test.dart' as namespaced_accessor_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/private_props_ddc_bug.dart' as private_props_ddc_bug; -// import 'over_react/component_declaration/builder_integration_tests/required_accessor_integration_test.dart' as required_accessor_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/stateful_component_integration_test.dart' as stateful_component_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/unassigned_prop_integration_test.dart' as unassigned_prop_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/abstract_accessor_integration_test.dart' as backwards_compat_abstract_accessor_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/accessor_mixin_integration_test.dart' as backwards_compat_accessor_mixin_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/component_integration_test.dart' as backwards_compat_component_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/constant_required_accessor_integration_test.dart' as backwards_compat_constant_required_accessor_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/do_not_generate_accessor_integration_test.dart' as backwards_compat_do_not_generate_accessor_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/namespaced_accessor_integration_test.dart' as backwards_compat_namespaced_accessor_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/private_props_ddc_bug.dart' as backwards_compat_private_props_ddc_bug; -// import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/required_accessor_integration_test.dart' as backwards_compat_required_accessor_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/stateful_component_integration_test.dart' as backwards_compat_stateful_component_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/unassigned_prop_integration_test.dart' as backwards_compat_unassigned_prop_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/component2/abstract_accessor_integration_test.dart' as component2_abstract_accessor_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/component2/accessor_mixin_integration_test.dart' as component2_accessor_mixin_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/component2/annotation_error_integration_test.dart' as annotation_error_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/component2/component_integration_test.dart' as component2_component_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/component2/constant_required_accessor_integration_test.dart' as component2_constant_required_accessor_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/component2/do_not_generate_accessor_integration_test.dart' as component2_do_not_generate_accessor_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/component2/namespaced_accessor_integration_test.dart' as component2_namespaced_accessor_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/component2/private_props_ddc_bug.dart' as component2_private_props_ddc_bug; -// import 'over_react/component_declaration/builder_integration_tests/component2/required_accessor_integration_test.dart' as component2_required_accessor_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/component2/stateful_component_integration_test.dart' as component2_stateful_component_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/component2/unassigned_prop_integration_test.dart' as component2_unassigned_prop_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/accessor_mixin_integration_test.dart' as new_boilerplate_accessor_mixin_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/compact_hoc_syntax_integration_test.dart' as new_boilerplate_compact_hoc_syntax_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/component_integration_test.dart' as new_boilerplate_component_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/component_integration_verbose_syntax_test.dart' as new_boilerplate_component_integration_verbose_syntax_test; -// import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/constant_required_accessor_integration_test.dart' as new_boilerplate_constant_required_accessor_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/covariant_accessor_override_integration_test.dart' as new_boilerplate_covariant_accessor_override_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/do_not_generate_accessor_integration_test.dart' as new_boilerplate_do_not_generate_accessor_integration_test; +import 'over_react/component_declaration/builder_helpers_test.dart' as builder_helpers_test; +import 'over_react/component_declaration/builder_integration_tests/abstract_accessor_integration_test.dart' as abstract_accessor_integration_test; +import 'over_react/component_declaration/builder_integration_tests/accessor_mixin_integration_test.dart' as accessor_mixin_integration_test; +import 'over_react/component_declaration/builder_integration_tests/component_integration_test.dart' as component_integration_test; +import 'over_react/component_declaration/builder_integration_tests/constant_required_accessor_integration_test.dart' as constant_required_accessor_integration_test; +import 'over_react/component_declaration/builder_integration_tests/do_not_generate_accessor_integration_test.dart' as do_not_generate_accessor_integration_test; +import 'over_react/component_declaration/builder_integration_tests/namespaced_accessor_integration_test.dart' as namespaced_accessor_integration_test; +import 'over_react/component_declaration/builder_integration_tests/private_props_ddc_bug.dart' as private_props_ddc_bug; +import 'over_react/component_declaration/builder_integration_tests/required_accessor_integration_test.dart' as required_accessor_integration_test; +import 'over_react/component_declaration/builder_integration_tests/stateful_component_integration_test.dart' as stateful_component_integration_test; +import 'over_react/component_declaration/builder_integration_tests/unassigned_prop_integration_test.dart' as unassigned_prop_integration_test; +import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/abstract_accessor_integration_test.dart' as backwards_compat_abstract_accessor_integration_test; +import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/accessor_mixin_integration_test.dart' as backwards_compat_accessor_mixin_integration_test; +import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/component_integration_test.dart' as backwards_compat_component_integration_test; +import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/constant_required_accessor_integration_test.dart' as backwards_compat_constant_required_accessor_integration_test; +import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/do_not_generate_accessor_integration_test.dart' as backwards_compat_do_not_generate_accessor_integration_test; +import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/namespaced_accessor_integration_test.dart' as backwards_compat_namespaced_accessor_integration_test; +import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/private_props_ddc_bug.dart' as backwards_compat_private_props_ddc_bug; +import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/required_accessor_integration_test.dart' as backwards_compat_required_accessor_integration_test; +import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/stateful_component_integration_test.dart' as backwards_compat_stateful_component_integration_test; +import 'over_react/component_declaration/builder_integration_tests/backwards_compatible/unassigned_prop_integration_test.dart' as backwards_compat_unassigned_prop_integration_test; +import 'over_react/component_declaration/builder_integration_tests/component2/abstract_accessor_integration_test.dart' as component2_abstract_accessor_integration_test; +import 'over_react/component_declaration/builder_integration_tests/component2/accessor_mixin_integration_test.dart' as component2_accessor_mixin_integration_test; +import 'over_react/component_declaration/builder_integration_tests/component2/annotation_error_integration_test.dart' as annotation_error_integration_test; +import 'over_react/component_declaration/builder_integration_tests/component2/component_integration_test.dart' as component2_component_integration_test; +import 'over_react/component_declaration/builder_integration_tests/component2/constant_required_accessor_integration_test.dart' as component2_constant_required_accessor_integration_test; +import 'over_react/component_declaration/builder_integration_tests/component2/do_not_generate_accessor_integration_test.dart' as component2_do_not_generate_accessor_integration_test; +import 'over_react/component_declaration/builder_integration_tests/component2/namespaced_accessor_integration_test.dart' as component2_namespaced_accessor_integration_test; +import 'over_react/component_declaration/builder_integration_tests/component2/private_props_ddc_bug.dart' as component2_private_props_ddc_bug; +import 'over_react/component_declaration/builder_integration_tests/component2/required_accessor_integration_test.dart' as component2_required_accessor_integration_test; +import 'over_react/component_declaration/builder_integration_tests/component2/stateful_component_integration_test.dart' as component2_stateful_component_integration_test; +import 'over_react/component_declaration/builder_integration_tests/component2/unassigned_prop_integration_test.dart' as component2_unassigned_prop_integration_test; +import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/accessor_mixin_integration_test.dart' as new_boilerplate_accessor_mixin_integration_test; +import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/compact_hoc_syntax_integration_test.dart' as new_boilerplate_compact_hoc_syntax_integration_test; +import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/component_integration_test.dart' as new_boilerplate_component_integration_test; +import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/component_integration_verbose_syntax_test.dart' as new_boilerplate_component_integration_verbose_syntax_test; +import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/constant_required_accessor_integration_test.dart' as new_boilerplate_constant_required_accessor_integration_test; +import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/covariant_accessor_override_integration_test.dart' as new_boilerplate_covariant_accessor_override_integration_test; +import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/do_not_generate_accessor_integration_test.dart' as new_boilerplate_do_not_generate_accessor_integration_test; import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart' as new_boilerplate_function_component_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/namespaced_accessor_integration_test.dart' as new_boilerplate_namespaced_accessor_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/private_props_ddc_bug.dart' as new_boilerplate_private_props_ddc_bug; -// import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/props_map_view_test.dart' as new_boilerplate_props_map_view_test; -// import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/props_meta_test.dart' as new_boilerplate_props_meta_test; -// import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/required_accessor_integration_test.dart' as new_boilerplate_required_accessor_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/stateful_component_integration_test.dart' as new_boilerplate_stateful_component_integration_test; -// import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/unassigned_prop_integration_test.dart' as new_boilerplate_unassigned_prop_integration_test; +import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/namespaced_accessor_integration_test.dart' as new_boilerplate_namespaced_accessor_integration_test; +import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/private_props_ddc_bug.dart' as new_boilerplate_private_props_ddc_bug; +import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/props_map_view_test.dart' as new_boilerplate_props_map_view_test; +import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/props_meta_test.dart' as new_boilerplate_props_meta_test; +import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/required_accessor_integration_test.dart' as new_boilerplate_required_accessor_integration_test; +import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/stateful_component_integration_test.dart' as new_boilerplate_stateful_component_integration_test; +import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/unassigned_prop_integration_test.dart' as new_boilerplate_unassigned_prop_integration_test; main() { enableTestMode(); - // component_base_test.main(); - // component_type_checking_test.main(); - // redux_component_test.main(); + component_base_test.main(); + component_type_checking_test.main(); + redux_component_test.main(); - // flux_component_test.main(); - // component2_flux_component_test.main(); + flux_component_test.main(); + component2_flux_component_test.main(); - // builder_helpers_test.main(); + builder_helpers_test.main(); - // abstract_accessor_integration_test.main(); - // accessor_mixin_integration_test.main(); - // component_integration_test.main(); - // constant_required_accessor_integration_test.main(); - // do_not_generate_accessor_integration_test.main(); - // namespaced_accessor_integration_test.main(); - // private_props_ddc_bug.main(); - // required_accessor_integration_test.main(); - // stateful_component_integration_test.main(); - // unassigned_prop_integration_test.main(); + abstract_accessor_integration_test.main(); + accessor_mixin_integration_test.main(); + component_integration_test.main(); + constant_required_accessor_integration_test.main(); + do_not_generate_accessor_integration_test.main(); + namespaced_accessor_integration_test.main(); + private_props_ddc_bug.main(); + required_accessor_integration_test.main(); + stateful_component_integration_test.main(); + unassigned_prop_integration_test.main(); - // backwards_compat_abstract_accessor_integration_test.main(); - // backwards_compat_accessor_mixin_integration_test.main(); - // backwards_compat_do_not_generate_accessor_integration_test.main(); - // backwards_compat_component_integration_test.main(); - // backwards_compat_constant_required_accessor_integration_test.main(); - // backwards_compat_private_props_ddc_bug.main(); - // backwards_compat_namespaced_accessor_integration_test.main(); - // backwards_compat_required_accessor_integration_test.main(); - // backwards_compat_stateful_component_integration_test.main(); - // backwards_compat_unassigned_prop_integration_test.main(); + backwards_compat_abstract_accessor_integration_test.main(); + backwards_compat_accessor_mixin_integration_test.main(); + backwards_compat_do_not_generate_accessor_integration_test.main(); + backwards_compat_component_integration_test.main(); + backwards_compat_constant_required_accessor_integration_test.main(); + backwards_compat_private_props_ddc_bug.main(); + backwards_compat_namespaced_accessor_integration_test.main(); + backwards_compat_required_accessor_integration_test.main(); + backwards_compat_stateful_component_integration_test.main(); + backwards_compat_unassigned_prop_integration_test.main(); - // component2_abstract_accessor_integration_test.main(); - // component2_accessor_mixin_integration_test.main(); - // annotation_error_integration_test.main(); - // component2_component_integration_test.main(); - // component2_constant_required_accessor_integration_test.main(); - // component2_do_not_generate_accessor_integration_test.main(); - // component2_namespaced_accessor_integration_test.main(); - // component2_private_props_ddc_bug.main(); - // component2_required_accessor_integration_test.main(); - // component2_stateful_component_integration_test.main(); - // component2_unassigned_prop_integration_test.main(); + component2_abstract_accessor_integration_test.main(); + component2_accessor_mixin_integration_test.main(); + annotation_error_integration_test.main(); + component2_component_integration_test.main(); + component2_constant_required_accessor_integration_test.main(); + component2_do_not_generate_accessor_integration_test.main(); + component2_namespaced_accessor_integration_test.main(); + component2_private_props_ddc_bug.main(); + component2_required_accessor_integration_test.main(); + component2_stateful_component_integration_test.main(); + component2_unassigned_prop_integration_test.main(); - // new_boilerplate_accessor_mixin_integration_test.main(); - // new_boilerplate_compact_hoc_syntax_integration_test.main(); - // new_boilerplate_component_integration_test.main(); - // new_boilerplate_component_integration_verbose_syntax_test.main(); - // new_boilerplate_constant_required_accessor_integration_test.main(); - // new_boilerplate_covariant_accessor_override_integration_test.main(); - // new_boilerplate_do_not_generate_accessor_integration_test.main(); + new_boilerplate_accessor_mixin_integration_test.main(); + new_boilerplate_compact_hoc_syntax_integration_test.main(); + new_boilerplate_component_integration_test.main(); + new_boilerplate_component_integration_verbose_syntax_test.main(); + new_boilerplate_constant_required_accessor_integration_test.main(); + new_boilerplate_covariant_accessor_override_integration_test.main(); + new_boilerplate_do_not_generate_accessor_integration_test.main(); new_boilerplate_function_component_integration_test.main(); - // new_boilerplate_namespaced_accessor_integration_test.main(); - // new_boilerplate_private_props_ddc_bug.main(); - // new_boilerplate_props_map_view_test.main(); - // new_boilerplate_props_meta_test.main(); - // new_boilerplate_required_accessor_integration_test.main(); - // new_boilerplate_stateful_component_integration_test.main(); - // new_boilerplate_unassigned_prop_integration_test.main(); + new_boilerplate_namespaced_accessor_integration_test.main(); + new_boilerplate_private_props_ddc_bug.main(); + new_boilerplate_props_map_view_test.main(); + new_boilerplate_props_meta_test.main(); + new_boilerplate_required_accessor_integration_test.main(); + new_boilerplate_stateful_component_integration_test.main(); + new_boilerplate_unassigned_prop_integration_test.main(); } From 1e383db5fa79b635a8e9a186810413fdb3aed1e8 Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Thu, 10 Aug 2023 10:30:21 -0700 Subject: [PATCH 22/32] lets not use such a complex demo --- web/index.html | 1 - web/uifunction/index.dart | 37 ----- web/uifunction/index.html | 73 ---------- web/uifunction/src/bar.dart | 47 ------- web/uifunction/src/baz.dart | 18 --- web/uifunction/src/button.dart | 56 -------- web/uifunction/src/component_wrapper.dart | 162 ---------------------- web/uifunction/src/foo.dart | 30 ---- web/uifunction/src/foobar.dart | 34 ----- web/uifunction/src/utils.dart | 29 ---- 10 files changed, 487 deletions(-) delete mode 100644 web/uifunction/index.dart delete mode 100644 web/uifunction/index.html delete mode 100644 web/uifunction/src/bar.dart delete mode 100644 web/uifunction/src/baz.dart delete mode 100644 web/uifunction/src/button.dart delete mode 100644 web/uifunction/src/component_wrapper.dart delete mode 100644 web/uifunction/src/foo.dart delete mode 100644 web/uifunction/src/foobar.dart delete mode 100644 web/uifunction/src/utils.dart diff --git a/web/index.html b/web/index.html index 478cb460c..d9a6dd257 100644 --- a/web/index.html +++ b/web/index.html @@ -33,7 +33,6 @@

OverReact Component Demos

diff --git a/web/uifunction/index.dart b/web/uifunction/index.dart deleted file mode 100644 index b67b8aebf..000000000 --- a/web/uifunction/index.dart +++ /dev/null @@ -1,37 +0,0 @@ - - -// Copyright 2020 Workiva Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import 'dart:html'; - -import 'package:over_react/over_react.dart'; -import 'package:over_react/react_dom.dart' as react_dom; - -import 'src/foobar.dart'; -void main() { - react_dom.render( - StrictMode()( - (FooBarDemo() - ..foobar = 'FooBar' - ..foo = 'Foo' - ..bar = 'Bar' - ..baz = 'Baz' - ..isFancy = true - ..className = 'fancy' - )(), - ), - querySelector('#uifunction'), - ); -} diff --git a/web/uifunction/index.html b/web/uifunction/index.html deleted file mode 100644 index 9cc886b11..000000000 --- a/web/uifunction/index.html +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - over_react demo components - - - - - - - - - - - - -
-

OverReact Component Demos - UiFunction

-
-
-
- - - - - - - - - - - - diff --git a/web/uifunction/src/bar.dart b/web/uifunction/src/bar.dart deleted file mode 100644 index 71d71d9bc..000000000 --- a/web/uifunction/src/bar.dart +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2020 Workiva Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import 'package:over_react/over_react.dart'; - -import 'baz.dart'; -import 'button.dart'; -import 'component_wrapper.dart'; -import 'utils.dart'; - -part 'bar.over_react.g.dart'; - -mixin BarPropsMixin on UiProps { - String bar; - - @Accessor(key:'data-is-fancy', keyNamespace: '') - bool isFancy; -} - -const excludePropsMixins = {BarPropsMixin}; - -final BarDemo = createVisualComponent(Bar, excludedMixins: excludePropsMixins); - - -final Bar = uiFunction((props) { - final randomState = useState(true); - return Dom.div()( - (Button()..onClick = (e) => randomState.set(!randomState.value))('UPDATE STATE'), - (BazDemo() - ..addAll(props.getPropsToForward(exclude: excludePropsMixins)) - )(), - (DomDivDemo() - ..modifyProps(props.addPropsToForward(exclude: {}, domOnly: true)) - )('This div should have a `data-is-fancy` attribute!'), - ); -}, _$BarConfig); diff --git a/web/uifunction/src/baz.dart b/web/uifunction/src/baz.dart deleted file mode 100644 index c367e1c84..000000000 --- a/web/uifunction/src/baz.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:over_react/over_react.dart'; - -import 'component_wrapper.dart'; -import 'utils.dart'; - -part 'baz.over_react.g.dart'; - -mixin BazPropsMixin on UiProps { - dynamic baz; -} - -final BazDemo = createVisualComponent(Baz, domOnly: true); - -final Baz = uiFunction((props) { - return (DomDivDemo() - ..addAll(props.getPropsToForward(domOnly: true)) - )(props.baz != null ? 'We received props.Baz! ${props.baz}' : 'props.Baz was not forwarded.'); -}, _$BazConfig); diff --git a/web/uifunction/src/button.dart b/web/uifunction/src/button.dart deleted file mode 100644 index 18845ee88..000000000 --- a/web/uifunction/src/button.dart +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2020 Workiva Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import 'dart:js_util'; - -import 'package:over_react/over_react.dart'; - -part 'button.over_react.g.dart'; - -mixin ButtonPropsMixin on UiProps {} - -final Button = uiFunction((props) { - final buttonStyles = { - 'display': 'inline-block', - 'color': 'rgba(255, 255, 255, 0.75)', - 'marginBottom': '10px', - 'borderRadius': '.25rem', - 'backgroundColor': '#0075DB', - 'fontWeight': 'bold', - 'fontSize': 'smaller', - 'border': 'none', - 'padding': '10px 20px', - 'cursor': 'pointer', - ...(props.style ?? {}) - }; - return (Dom.button() - ..modifyProps(props.addPropsToForward(domOnly: true)) - ..style = { - 'display': 'inline-block', - 'color': 'rgba(255, 255, 255, 0.75)', - 'marginBottom': '10px', - 'borderRadius': '5px', - 'backgroundColor': '#0075DB', - 'fontWeight': 'bold', - 'fontSize': 'smaller', - 'border': 'none', - 'padding': '10px 20px', - 'cursor': 'pointer', - 'outline': 'none', - ...(props.style ?? {}) - } - ..onMouseOver = ((e) => setProperty(getProperty(e.currentTarget, 'style'), 'backgroundColor', 'rgb(0, 98, 184)')) - ..onMouseLeave = ((e) => setProperty(getProperty(e.currentTarget, 'style'), 'backgroundColor', '#0075DB')) - )(props.children); -}, _$ButtonConfig); diff --git a/web/uifunction/src/component_wrapper.dart b/web/uifunction/src/component_wrapper.dart deleted file mode 100644 index 599a0bd41..000000000 --- a/web/uifunction/src/component_wrapper.dart +++ /dev/null @@ -1,162 +0,0 @@ -import 'dart:html'; -import 'dart:js_util'; -import 'dart:math'; - -import 'package:over_react/over_react.dart'; - -part 'component_wrapper.over_react.g.dart'; - -final containerStyles = { - 'position': 'relative', - 'border': '2px solid #ddd', - 'padding': '20px', - 'borderRadius': '5px', - 'backgroundColor': '#1a191c', - 'color': '#e7e9ec', - 'WebkitFontSmoothing': 'antialiased', - 'MozOsxFontSmoothing': 'grayscale', -}; - -UiFactory createVisualComponent(UiFactory otherFactory, {Set excludedMixins = const {}, bool domOnly = false}) { - final blankFactoryProps = otherFactory(); - final blankFactoryReactElement = blankFactoryProps(); - final componentName = getProperty(blankFactoryProps?.componentFactory, 'displayName') ?? getReactElementDisplayName(blankFactoryReactElement); - UiFactory VisualComponentHoc = uiFunction( - (props) { - var isDomComponent = true; - List consumedPropKeys = []; - final consumedPropKeysForToForward = []; - if (props is! DomProps) { - isDomComponent = false; - final consumedProps = props.staticMeta.forMixins(excludedMixins ?? {T}); - consumedPropKeys = consumedProps.map((v) => v.keys).expand((i) => i).toList(); - consumedPropKeysForToForward.addAll(consumedPropKeys); - } - props.removeWhere((key, value) => consumedPropKeysForToForward.contains(key)); - final componentElement = (otherFactory()..addProps(props))(props.children) as ReactElement; - window.console.log(componentElement); - return Dom.div()( - (Dom.div()..className = 'component-container'..style = containerStyles)( - (Dom.span() - ..className = 'rerender' - ..key=Random().nextInt(1000000) - ..style = { - 'position':'absolute', - 'left': '1.25rem', - 'top': '0.5rem', - 'color': 'EE0000', - 'fontWeight': 'bold', - 'fontSize': 'smaller', - } - )( - 'RENDER' - ), - (Dom.span()..className = 'component-name'..style = {'fontWeight': 'bold', 'paddingBottom': '10px', 'fontSize': '2rem'})(componentName), - //(MapDisplay()..name = 'STATE'..backgroundColor = '#2A8300'..mapToDisplay = Map.from(componentElement.))(), - (MapDisplay() - ..name = 'CONSUMED PROPS' - ..backgroundColor = '#2A8300' - ..mapToDisplay = (Map.from(props)..removeWhere((key, value) => !(consumedPropKeys?.contains(key) ?? true))..remove('children')) - )(), - (MapDisplay() - ..name = isDomComponent ? 'CONSUMED PROPS' : 'PROPS TO FORWARD' - ..backgroundColor = isDomComponent ? '#2A8300' : '#1c4498' - ..mapToDisplay = (JsBackedMap.fromJs(componentElement.props) - ..removeWhere((key, value) => consumedPropKeysForToForward.contains(key)) - ..remove('children')) - )(), - (Dom.div()..className = 'component-render')(componentElement), - ), - ); - }, - UiFactoryConfig( - displayName: 'VisualComponentHoc($componentName)', - propsFactory: PropsFactory.fromUiFactory(otherFactory), - ), - ); - - return VisualComponentHoc; -} - -String getReactElementDisplayName(ReactElement element) { - final elementType = element?.type; - final elementTypeName = getProperty(elementType, 'name'); - if (elementType != null && elementType is String) { - return elementType; - } else if (elementTypeName != null && elementTypeName is String) { - return elementTypeName; - } - return 'Anonymous'; -} - -mixin MapDisplayPropsMixin on UiProps { - String name; - String backgroundColor; - Map mapToDisplay; -} - -final MapDisplay = uiFunction((props) { - final backgroundColor = props.backgroundColor ?? '#2070f3'; - final name = props.name ?? 'PROPS'; - final mapToDisplay = props.mapToDisplay ?? {}; - - if (mapToDisplay.isEmpty) { - return null; - } - - final mapDisplay = []; - - mapToDisplay.forEach((key, value) { - mapDisplay.add((Dom.div()..className = 'prop-entry'..key = '$key')('$key: $value')); - }); - - return (Dom.div() - ..className = 'prop-list-container' - ..style = { - 'backgroundColor': backgroundColor, - 'display': 'flex', - 'borderRadius': '5px', - 'overflow': 'hidden', - 'marginBottom': '10px', - 'position': 'relative', - } - )( - (Dom.span() - ..className = 'prop-list-name' - ..style = const { - 'padding': '10px', - 'alignItems': 'center', - 'justifyContent': 'center', - 'display': 'flex', - 'color': 'rgba(255, 255, 255, .75)', - 'fontWeight': 'bold', - 'fontSize': 'smaller', - } - )(name), - (Dom.div() - ..id = 'prop-entries-container' - ..style = const { - 'position': 'relative', - 'padding': '10px', - 'fontWeight': '400', - 'fontFamily': 'Fira Code', - 'flex': '1', - } - )( - (Dom.div() - ..id = 'prop-entries-light-background' - ..style = const { - 'width': '100%', - 'height': '100%', - 'position': 'absolute', - 'top': 0, - 'left': 0, - 'background': '#b7b7b7', - 'mixBlendMode': 'soft-light', - 'opacity': '.75', - } - )(), - mapDisplay, - ), - ); -}, _$MapDisplayConfig); diff --git a/web/uifunction/src/foo.dart b/web/uifunction/src/foo.dart deleted file mode 100644 index be229fd3d..000000000 --- a/web/uifunction/src/foo.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:over_react/over_react.dart'; - -import 'button.dart'; -import 'component_wrapper.dart'; -import 'utils.dart'; - -part 'foo.over_react.g.dart'; - -// This is what it would default too when you dont include an `exclude` argument. -const excludeMixins = {FooPropsMixin}; - -final FooDemo = createVisualComponent(Foo, excludedMixins: excludeMixins); - -mixin FooPropsMixin on UiProps { - dynamic foo; -} - -final Foo = uiFunction((props) { - final randomState = useState(true); - return Dom.div()( - (Button()..onClick = (e) => randomState.set(!randomState.value))('UPDATE STATE'), - (DomDivDemo() - ..addAll(props.getPropsToForward(domOnly: true)) - )()); -}, _$FooConfig); - - - - - diff --git a/web/uifunction/src/foobar.dart b/web/uifunction/src/foobar.dart deleted file mode 100644 index 7d06f0f6e..000000000 --- a/web/uifunction/src/foobar.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:over_react/over_react.dart'; - -import 'bar.dart'; -import 'baz.dart'; -import 'foo.dart'; -import 'component_wrapper.dart'; - -part 'foobar.over_react.g.dart'; - -const excludeMixins = {FooBarPropsMixin}; - -final FooBarDemo = createVisualComponent(FooBar, excludedMixins: excludeMixins); - -mixin FooBarPropsMixin on UiProps { - dynamic foobar; -} - -class FooBarProps = UiProps - with FooBarPropsMixin, - FooPropsMixin, - BarPropsMixin, - BazPropsMixin; - - -final FooBar = uiFunction((props) { - return (Dom.div()..style = {'display': 'flex'})( - (FooDemo() - ..addAll(props.getPropsToForward(exclude: excludeMixins)) - )(), - (BarDemo() - ..modifyProps(props.addPropsToForward(exclude: excludeMixins)) - )(), - ); -}, _$FooBarConfig); diff --git a/web/uifunction/src/utils.dart b/web/uifunction/src/utils.dart deleted file mode 100644 index 320b28908..000000000 --- a/web/uifunction/src/utils.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'dart:html'; -import 'dart:js_util'; - -import 'package:over_react/over_react.dart'; -import 'package:react/react_client/js_interop_helpers.dart'; - -import 'component_wrapper.dart'; - -const undefined = Undefined(); -class Undefined { - const Undefined(); -} - -log([c1 = undefined, c2 = undefined, c3 = undefined, c4 = undefined, c5 = undefined, c6 = undefined, c7 = undefined, c8 = undefined, c9 = undefined, c10 = undefined, c11 = undefined, c12 = undefined, c13 = undefined, c14 = undefined, c15 = undefined, c16 = undefined, c17 = undefined, c18 = undefined, c19 = undefined, c20 = undefined, c21 = undefined, c22 = undefined, c23 = undefined, c24 = undefined, c25 = undefined, c26 = undefined, c27 = undefined, c28 = undefined, c29 = undefined, c30 = undefined, c31 = undefined, c32 = undefined, c33 = undefined, c34 = undefined, c35 = undefined, c36 = undefined, c37 = undefined, c38 = undefined, c39 = undefined, c40 = undefined]) { - final arguments = [c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15, c16, c17, c18, c19, c20, c21, c22, c23, c24, c25, c26, c27, c28, c29, c30, c31, c32, c33, c34, c35, c36, c37, c38, c39, c40] - .takeWhile((child) => !identical(child, undefined)) - .toList() - ..map((element) { - try { return jsifyAndAllowInterop(element); } catch (e){} - return element; - }); - return callMethod(getProperty(window,'console'), 'log', [...arguments]); -} - -group(String groupName) => window.console.group(groupName); - -groupEnd() => window.console.groupEnd(); - -final DomDivDemo = createVisualComponent(Dom.div, domOnly: true); From e43030db3080301c557900a5ccc605995e0464c6 Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Thu, 10 Aug 2023 12:48:54 -0700 Subject: [PATCH 23/32] update docs? --- doc/new_boilerplate_migration.md | 477 ++++++++++-------- .../src/functional_consumed_props.dart | 4 +- .../builder/src/new_class_consumed_props.dart | 6 +- .../builder_helpers.dart | 39 +- 4 files changed, 308 insertions(+), 218 deletions(-) diff --git a/doc/new_boilerplate_migration.md b/doc/new_boilerplate_migration.md index c1face774..b490c26f5 100644 --- a/doc/new_boilerplate_migration.md +++ b/doc/new_boilerplate_migration.md @@ -23,18 +23,18 @@ _Preview of new boilerplate:_ ## Background -While converting the boilerplate for OverReact component declaration from Dart 1 -transformers to Dart 2 builders, we encountered several constraints that made us -choose between dropping backwards compatibility (mainly support for props class +While converting the boilerplate for OverReact component declaration from Dart 1 +transformers to Dart 2 builders, we encountered several constraints that made us +choose between dropping backwards compatibility (mainly support for props class inheritance), and a less-than-optimal boilerplate. -To make the Dart 2 transition as smooth as possible, we chose to keep the new -boilerplate version as backwards-compatible as possible, while compromising the -cleanliness of the boilerplate. In time, we found that this wasn't great from a +To make the Dart 2 transition as smooth as possible, we chose to keep the new +boilerplate version as backwards-compatible as possible, while compromising the +cleanliness of the boilerplate. In time, we found that this wasn't great from a user experience or from a tooling perspective. -Knowing this, and having dropped support for Dart 1, we now have the opportunity -to implement an improved version of OverReact boilerplate that fixes issues +Knowing this, and having dropped support for Dart 1, we now have the opportunity +to implement an improved version of OverReact boilerplate that fixes issues introduced in the latest version, as well as other miscellaneous ones. ### Problems with Previous Boilerplate @@ -51,21 +51,21 @@ class _$FooProps extends BarProps { } // Generated in .over_react.g.dart -class FooProps extends _$FooProps with _$FooPropsAccessorsMixin {
 +class FooProps extends _$FooProps with _$FooPropsAccessorsMixin { static const PropsMeta meta = ...; - ...
 + ... } ``` -In using the build packages's `build_to: cache` to generate public APIs, command-line -tools like `dartanalyzer` and `dartdoc` don't function properly for packages that do this. +In using the build packages's `build_to: cache` to generate public APIs, command-line +tools like `dartanalyzer` and `dartdoc` don't function properly for packages that do this. -This pattern can also degrade the dev experience, by requiring a build before code is -statically valid, and also requiring rebuilds in some cases to consume public API +This pattern can also degrade the dev experience, by requiring a build before code is +statically valid, and also requiring rebuilds in some cases to consume public API updates during their development (e.g., writing new component props classes). -The transitional _(backwards-compatible with Dart 1)_ boilerplate, currently used in -almost all repos, does not have these issues, as it requires users to stub in these +The transitional _(backwards-compatible with Dart 1)_ boilerplate, currently used in +almost all repos, does not have these issues, as it requires users to stub in these public classes: ```dart @@ -81,20 +81,20 @@ class FooProps with // ignore: mixin_of_non_class, undefined_class _$FooPropsAccessorsMixin { - // ignore: const_initialized_with_non_constant_value, undefined_class, undefined_identifier 
 + // ignore: const_initialized_with_non_constant_value, undefined_class, undefined_identifier static const PropsMeta meta = _$metaForPanelTitleProps; -
} +} ``` -This is overly verbose, confusing, and error-prone. Authoring components should be +This is overly verbose, confusing, and error-prone. Authoring components should be simple and easy. #### Inheritance -Props are declared as fields, and we generate the accessor (AKA getters/setters) +Props are declared as fields, and we generate the accessor (AKA getters/setters) implementations that are to be used when reading and writing props. -If the consumer authors the public-facing class, we have to do this in new +If the consumer authors the public-facing class, we have to do this in new generated subclasses to be able to override the field implementation. ```dart @@ -113,7 +113,7 @@ mixin $FooPropsAccessors on FooProps { class $FooProps = FooProps with $FooPropsAccessors; ``` -However, if consumers were to extend from the authored class, they wouldn't +However, if consumers were to extend from the authored class, they wouldn't inherit these generated fields. ```dart @@ -133,91 +133,91 @@ test() { #### Technical Constraints: -1. We cannot use resolved AST to generate components because it slows down +1. We cannot use resolved AST to generate components because it slows down the build too much. - In other words, we have access to the structure of the code within a - given file but not its full semantic meaning, and cannot resolve + In other words, we have access to the structure of the code within a + given file but not its full semantic meaning, and cannot resolve references it makes to code in other files. - - For instance, we can look at a class and see the name of the class it - extends from and the methods it declares, but we won't be able to know - where the parent class comes from, what type(s) the parent implements, + + For instance, we can look at a class and see the name of the class it + extends from and the methods it declares, but we won't be able to know + where the parent class comes from, what type(s) the parent implements, or which member(s) the parent declares. 2. User-authored code must reference generated code somehow to "wire it up". - Since generated code can be output only to new files, component + Since generated code can be output only to new files, component registration / wiring of generated code requires either: - - 1. a centralized, generated registry that maps components to generated - component code, and that must be generated for and consumed in that + + 1. a centralized, generated registry that maps components to generated + component code, and that must be generated for and consumed in that main() method of all consuming apps' entrypoints. - - 2. a user-authored entrypoint (field initializer, method invocation, - constructor, etc.) that imports (or pulls in via a part) and references + + 2. a user-authored entrypoint (field initializer, method invocation, + constructor, etc.) that imports (or pulls in via a part) and references generated code (what we have now). #### Self-Imposed Constraints: 1. Keep component declarations as terse and user-friendly as possible. -2. Use `build_to: cache` (for more information, see: +2. Use `build_to: cache` (for more information, see: [pkg:build docs](https://github.com/dart-lang/build/blob/master/docs/builder_author_faq.md#when-should-a-builder-build-to-cache-vs-source)). - - `build_to:cache` should be used when generated code is dependent on the - library's underlying implementation. This may not be strictly the case - today, but if we commit to `build_to: cache`, we will have more - flexibility in the future to make improvements or fix bugs to OverReact - code generation without requiring a (very expensive) breaking change. - - It would also result in improvements to the builder being propagated - immediately as soon as they're consumed by wdesk, as opposed to having + + `build_to:cache` should be used when generated code is dependent on the + library's underlying implementation. This may not be strictly the case + today, but if we commit to `build_to: cache`, we will have more + flexibility in the future to make improvements or fix bugs to OverReact + code generation without requiring a (very expensive) breaking change. + + It would also result in improvements to the builder being propagated + immediately as soon as they're consumed by wdesk, as opposed to having to regenerate code and release within every consumer library. 3. Make source code statically analyzable without running a build. - The build docs instruct not to use build_to: cache to generate public - APIs, and command-line tools like `dartanalyzer` and `dartdoc` don't - function properly for packages that do this. - - Generating public APIs can also degrade the dev experience, by requiring - a build before code is statically valid, and also requiring rebuilds in - some cases to consume public API updates during their development (e.g., + The build docs instruct not to use build_to: cache to generate public + APIs, and command-line tools like `dartanalyzer` and `dartdoc` don't + function properly for packages that do this. + + Generating public APIs can also degrade the dev experience, by requiring + a build before code is statically valid, and also requiring rebuilds in + some cases to consume public API updates during their development (e.g., writing new component props classes). - -4. Provide some means of sharing props/state declarations between + +4. Provide some means of sharing props/state declarations between components. - Being able to share props/state between multiple components is useful, - especially when composing them together. We also have many legacy - components that currently share props, and want to make it possible + Being able to share props/state between multiple components is useful, + especially when composing them together. We also have many legacy + components that currently share props, and want to make it possible to upgrade them. - -5. Provide a simple migration path for _most_ components in our consumer + +5. Provide a simple migration path for _most_ components in our consumer ecosystems. - We can support new/old boilerplate at the same time, and slowly phase - out the old as we migrate over to it using + We can support new/old boilerplate at the same time, and slowly phase + out the old as we migrate over to it using [our `boilerplate_upgrade` codemod](#upgrading-existing-code). - - For cases that don't migrate cleanly within the Workiva ecosystem, - we can use the Wdesk versioning policy to replace them with APIs that + + For cases that don't migrate cleanly within the Workiva ecosystem, + we can use the Wdesk versioning policy to replace them with APIs that use the new boilerplate in major versions or using versioned APIs. - + 6. Only support Component2 components. - The builder has different code paths for Component/Component2, and - supporting an additional boilerplate for both would increase code - complexity and effort needed to build/test it. + The builder has different code paths for Component/Component2, and + supporting an additional boilerplate for both would increase code + complexity and effort needed to build/test it. ### Updates #### Add `castUiFactory` Utility -A utility called `castUiFactory` has been added that prevent implicit cast errors -(which are no longer ignorable as of Dart 2.9) on factory declarations. All that needs to be done is to wrap the generated -factory with `castUiFactory`, so that it can infer the typing from the left hand side and cast the factory (considered +A utility called `castUiFactory` has been added that prevent implicit cast errors +(which are no longer ignorable as of Dart 2.9) on factory declarations. All that needs to be done is to wrap the generated +factory with `castUiFactory`, so that it can infer the typing from the left hand side and cast the factory (considered "dynamic" before code generation is run) to the correct type. ```diff @@ -229,10 +229,10 @@ UiFactory Foo = #### Remove Annotations -`@Factory()`, `@Props()` and `@Component()` annotations add additional +`@Factory()`, `@Props()` and `@Component()` annotations add additional visual clutter to the boilerplate, and are redundant since the factory/ -props/component declarations already have a consistent/restricted -structure and naming scheme that makes it clear to the builder parsing +props/component declarations already have a consistent/restricted +structure and naming scheme that makes it clear to the builder parsing logic that a component is being defined, and what each part is. ```diff @@ -263,7 +263,7 @@ class _$FooProps extends BarProps { #### Ignore Ungenerated Warnings Project-Wide -Right now, we have to add `// ignore: uri_has_not_been_generated` to each +Right now, we have to add `// ignore: uri_has_not_been_generated` to each component library on the part/import that references generated code. Ignoring this hint globally within analysis_options.yaml: @@ -271,10 +271,10 @@ Ignoring this hint globally within analysis_options.yaml: ```yaml analyzer: errors: - uri_has_not_been_generated: ignore + uri_has_not_been_generated: ignore ``` -Allows individual ignores to be omitted, which will reduce clutter in +Allows individual ignores to be omitted, which will reduce clutter in the component boilerplate. ```diff @@ -288,14 +288,14 @@ This warning is also ignored by default in [workiva_analysis_options](https://gi _Constraints_: -* Props classes must directly subclass UiProps, only inheriting other props +* Props classes must directly subclass UiProps, only inheriting other props via mixins. - * This requires consumers to include every single mixin within their `with` - clause, allowing the builder to mix in the generated code corresponding + * This requires consumers to include every single mixin within their `with` + clause, allowing the builder to mix in the generated code corresponding to those mixins. - -* Props can only be declared in mixins. + +* Props can only be declared in mixins. * This ensures they can be inherited by other props classes (by mixing them in, since you can no longer inherit them via subclassing) and provides consistency around how props are declared. @@ -362,7 +362,7 @@ import 'package:over_react/over_react.dart'; part 'foo.over_react.g.dart'; -UiFactory Foo = +UiFactory Foo = castUiFactory(_$Foo); // ignore: undefined_identifier mixin FooProps on UiProps { @@ -377,17 +377,17 @@ class FooComponent extends UiComponent2 { ##### Props Meta Changes -Props meta will be generated as an overridden getter on the component -as opposed to the current static field, and will allow similar access +Props meta will be generated as an overridden getter on the component +as opposed to the current static field, and will allow similar access of prop keys as before. -This eliminates the current `meta` portion of the boilerplate which has +This eliminates the current `meta` portion of the boilerplate which has to reference more generated code. -Prop meta from all mixins can be accessed, allowing us to default +Prop meta from all mixins can be accessed, allowing us to default `consumedProps` to all props statically accessible from that component. -Consumption: +Consumption: ```dart @Props() @@ -454,7 +454,7 @@ class PropsMetaCollection implements PropsMeta { /// Returns the metadata for only the prop fields declared in [mixinType]. PropsMeta forMixin(Type mixinType) { final meta = _metaByMixin[mixinType]; - assert(meta != null, + assert(meta != null, 'No meta found for $mixinType;' 'it likely isn\'t mixed in by the props class.') return meta ?? const PropsMeta(fields: [], keys: []); @@ -463,11 +463,11 @@ class PropsMetaCollection implements PropsMeta { // PropsMeta overrides @override - List get keys => + List get keys => _metaByMixin.values.expand((meta) => meta.keys).toList(); @override - List get fields => + List get fields => _metaByMixin.values.expand((meta) => meta.fields).toList(); @override @@ -477,12 +477,12 @@ class PropsMetaCollection implements PropsMeta { ##### Props MapViews -Since props mixins can only be consumed by other generated code, the -existing props map view consumption pattern, whereby props mixins are +Since props mixins can only be consumed by other generated code, the +existing props map view consumption pattern, whereby props mixins are consumed in user-authored MapView subclasses, cannot be supported. -Instead, props map views will be declared similarly to a component, -with a factory and props mixin/class, but no component. +Instead, props map views will be declared similarly to a component, +with a factory and props mixin/class, but no component. ```diff import 'package:over_react/over_react.dart'; @@ -522,19 +522,19 @@ class FooProps extends UiProps { class FooComponent extends UiComponent { render() { // { - // FooProps.foo: 1, - // FooProps.bar: 2, + // FooProps.foo: 1, + // FooProps.bar: 2, // data-a-dom-prop: 3, // onClick: 4, // someArbitraryProp: 5, // } print(props); - + // { // data-a-dom-prop: 3, // onClick: 4, // someArbitraryProp: 5, - // } + // } print(copyUnconsumedProps()); } } @@ -552,21 +552,21 @@ class FooProps extends OtherPropsMixin { class FooComponent extends UiComponent { render() { // { - // FooProps.foo: 1, - // FooProps.bar: 2, + // FooProps.foo: 1, + // FooProps.bar: 2, // data-a-dom-prop: 3, // onClick: 4, // someArbitraryProp: 5, - // OtherPropsMixin.other: 6, + // OtherPropsMixin.other: 6, // } print(props); - + // { // data-a-dom-prop: 3, // onClick: 4, // someArbitraryProp: 5, // OtherPropsMixin.other: 6, - // } + // } print(copyUnconsumedProps()); } } @@ -586,7 +586,7 @@ get consumedProps => [ #### Updated default behavior in the mixin-based syntax With the new mixin-based syntax, props cannot be declared directly within props classes, so if we kept using the consumed -props behavior from the legacy syntax, they wouldn't have any consumed props by default. (Unless we picked a mixin or something, +props behavior from the legacy syntax, they wouldn't have any consumed props by default. (Unless we picked a mixin or something, which could get confusing) This would mean that any props class that consumes the props of a single mixin would need to override consumedProps, @@ -621,7 +621,7 @@ class FooComponent ... { } ``` -To help optimize this use-case, as well as to make whether props are consumed or not more consistent across different +To help optimize this use-case, as well as to make whether props are consumed or not more consistent across different forms of the new syntax, we decided to __consume props from all props mixins by default__, if consumedProps is not overridden. So, taking the above example again, the new behavior would be: @@ -636,20 +636,20 @@ class FooProps = UiProps with FooPropsMixin, OtherPropsMixin; class FooComponent extends UiComponent { render() { // { - // FooProps.foo: 1, - // FooProps.bar: 2, + // FooProps.foo: 1, + // FooProps.bar: 2, // data-a-dom-prop: 3, // onClick: 4, // someArbitraryProp: 5, - // OtherPropsMixin.other: 6, + // OtherPropsMixin.other: 6, // } print(props); - + // { // data-a-dom-prop: 3, // onClick: 4, // someArbitraryProp: 5, - // } + // } print(copyUnconsumedProps()); } } @@ -661,16 +661,16 @@ For example: - Consuming all props except for a few mixins: - Before: + Before: ```dart - @Props() - class FooProps extends UiProps with - AProps, - BProps, - CProps, - NoConsumeProps { ... } - - @Component() + @Props() + class FooProps extends UiProps with + AProps, + BProps, + CProps, + NoConsumeProps { ... } + + @Component() class FooComponent extends UiComponent { @override consumedProps => [ @@ -679,36 +679,36 @@ For example: BProps.meta, CProps.meta, ]; - + ... } ``` After: ```dart class FooProps = UiProps with - FooPropsMixin - AProps, - BProps, - CProps, - NoConsumeProps; - + FooPropsMixin + AProps, + BProps, + CProps, + NoConsumeProps; + class FooComponent extends UiComponent { @override - consumedProps => + consumedProps => propsMeta.allExceptForMixins({NoConsumeProps}), - + ... } ``` #### Why didn't we do this earlier? -We couldn't "consume" props from other classes by default, since we didn't have full knowledge of all the +We couldn't "consume" props from other classes by default, since we didn't have full knowledge of all the props classes and mixins inherited by a given props class's superclass (due to not having a resolved AST in our builder, for performance reasons). However, in the new mixin-based syntax, props classes must explicitly mix in all props mixins they inherit from, -so we're able to easily tell at build time what they all are, and thus don't have that same restriction. +so we're able to easily tell at build time what they all are, and thus don't have that same restriction. ### Examples @@ -720,20 +720,20 @@ Most code within over_react has been updated to use this new boilerplate, includ ## Function Component Boilerplate -### Function Component Constraints +### Function Component Constraints -Includes all of [the constraints listed in the Boilerplate Updates section](#design-constraints), +Includes all of [the constraints listed in the Boilerplate Updates section](#design-constraints), ignoring parts about backwards-compatibility. * Should be as visually uncluttered as possible. * Should not wrap excessively for longer component names. -* Should be easy to transition between having and not having default +* Should be easy to transition between having and not having default props, and boilerplate shouldn't change shape drastically when doing so. -* Function calls using generated functions should be avoided since they -don't allow generic type inference of the `props` arg in the function +* Function calls using generated functions should be avoided since they +don't allow generic type inference of the `props` arg in the function closure. ### Syntax @@ -745,25 +745,25 @@ part 'foo.over_react.g.dart'; UiFactory Foo = uiFunction( (props) { - return 'foo: ${props.foo}'; + return 'foo: ${props.foo}'; }, _$FooConfig, // ignore: undefined_identifier -); +); mixin FooProps on UiProps { String foo; } ``` -Here, `uiFunction` gets a generic parameter of `FooProps` inferred +Here, `uiFunction` gets a generic parameter of `FooProps` inferred from the LHS typing, allowing props to be statically typed as `FooProps`. -The generated `$FooConfig` is passed in as an argument, and serves -as the entrypoint to the generated code. +The generated `$FooConfig` is passed in as an argument, and serves +as the entrypoint to the generated code. #### With Default Props -`defaultProps` on function components is +`defaultProps` on function components is [already deprecated](https://github.com/facebook/react/pull/16210). Instead, we use null-aware operators to default null values. This provides almost the @@ -775,48 +775,123 @@ UiFactory Foo = uiFunction( (props) { final foo = props.foo ?? 'default foo value'; - return 'foo: $foo'; + return 'foo: $foo'; }, _$FooConfig, // ignore: undefined_identifier -); +); ``` -#### With Consumed Props +#### With Prop Forwarding (fka Consumed Props) + +Because functional components have no instance that track consumed props, the syntax for forwarding props +changes within functional components. -Because functional components have no instance that track consumed props, the syntax for passing unconsumed -props changes within functional components. +`UiProps` exposes 2 APIs `getPropsToForward` & `addPropsToForward` that can be used to forward props +that have not been used to a child component. -`UiProps` exposes a field `staticMeta` that can be used to generate an iterable containing props meta for specific mixins. -This is similar to accessing `propsMeta` within a class based component. Using the iterable returned from `staticMeta`'s -APIs (such as `forMixins`), we can generate unconsumed props and pass them to a child component. +##### getPropsToForward +`getPropsToForward` will return a `Map` of props removing the props found in the `exclude` argument. +`exclude` is optional and will default to a `Set` with the type that `props` is statically typed as, +this only works with `mixin .. on UiProps` types. If your function component uses a Props `class` then +you must include an `exclude` argument. -This is done like so: +Component with a single props mixin: ```dart mixin FooPropsMixin on UiProps { - String passedProp; + String foo; } -mixin BarPropsMixin on UiProps { - String nonPassedProp; +UiFactory Foo = uiFunction((props) { + return (Bar() + // Filter out props declared in FooPropsMixin + // (used as the default for `exclude` since that's what `props` is statically typed as) + // when forwarding to Bar. + ..addAll(props.getPropsToForward()) + )(); +}); +``` + +Component with a more than one props mixin: +```dart +mixin FooPropsMixin on UiProps { + String foo; } -class FooBarProps = UiProps with BarPropsMixin, FooPropsMixin; +class FooProps = UiProps with BarProps, FooPropsMixin; -UiFactory FooBar = uiFunction( - (props) { - final consumedProps = props.staticMeta.forMixins({BarPropsMixin}); +UiFactory Foo = uiFunction((props) { + return (Bar() + // Filter out props declared in FooPropsMixin when forwarding to Bar. + ..addAll(props.getPropsToForward(exclude: {FooPropsMixin})) + )(); +}); +``` - return (Foo()..addUnconsumedProps(props, consumedProps))(); - }, - _$FooBarConfig, // ignore: undefined_identifier -); +`domOnly` - to forward DOM props only: +```dart +mixin FooPropsMixin on UiProps { + String foo; +} -UiFactory Foo = uiFunction( - (props) { - return 'foo: ${props.passedProp}'; - }, - _$FooConfig, // ignore: undefined_identifier -); +UiFactory Foo = uiFunction((props) { + return (Dom.div() + // Forward only DOM based props & Filter out props declared in FooPropsMixin + // (used as the default for `exclude` since that's what `props` is statically typed as) + // when forwarding to Bar. + ..addAll(props.getPropsToForward(domOnly: true)) + )(); +}); +``` + +##### addPropsToForward +`addPropsToForward` has the same function signature as `getPropsToForward` but is meant to be used with the `UiProps` method `modifyProps`. + +Component with a single props mixin: +```dart +mixin FooPropsMixin on UiProps { + String foo; +} + +UiFactory Foo = uiFunction((props) { + return (Bar() + // Filter out props declared in FooPropsMixin + // (used as the default for `exclude` since that's what `props` is statically typed as) + // when forwarding to Bar. + ..modifyProps(props.addPropsToForward()) + )(); +}); +``` + +Component with a more than one props mixin: +```dart +mixin FooPropsMixin on UiProps { + String foo; +} + +class FooProps = UiProps with BarProps, FooPropsMixin; + +UiFactory Foo = uiFunction((props) { + return (Bar() + // Filter out props declared in FooPropsMixin when forwarding to Bar. + ..modifyProps(props.addPropsToForward(exclude: {FooPropsMixin})) + )(); +}); +``` + +`domOnly` - to forward DOM props only: +```dart +mixin FooPropsMixin on UiProps { + String foo; +} + +UiFactory Foo = uiFunction((props) { + return (Dom.div() + // Forward only DOM based props & Filter out props declared in FooPropsMixin + // (used as the default for `exclude` since that's what `props` is statically typed as) + // when forwarding to Bar. + ..modifyProps(props.addPropsToForward(domOnly: true)) + )(); +}); ``` #### With UiProps @@ -824,21 +899,21 @@ UiFactory Foo = uiFunction( ```dart UiFactory Foo = uiFunction( (props) { - return 'id: ${props.id}'; - }, + return 'id: ${props.id}'; + }, UiFactoryConfig( displayName: 'Foo', ), ); ``` -#### With propTypes (Coming soon!) +#### With propTypes (~Coming soon!~ lol no its not) ```dart UiFactory Foo = uiFunction( (props) { - return 'foo: ${props.foo}'; - }, + return 'foo: ${props.foo}'; + }, _$FooConfig, // ignore: undefined_identifier getPropTypes: (keyFor) => { keyFor((p) => p.foo): (props, info) { @@ -850,7 +925,7 @@ UiFactory Foo = uiFunction( ); ``` -`getPropTypes` provides a way to set up prop validation within the +`getPropTypes` provides a way to set up prop validation within the same variable initializer. #### Local function components using just a props mixin - no top-level Factory necessary (Coming soon!) @@ -864,27 +939,27 @@ mixin FooProps on UiProps { String foo; } -// Example function; this can look like anything and doesn't have to -// be declared in this file. -UiFactory createFooHoc(UiFactory otherFactory) { - Object closureVariable; - // ... +// Example function; this can look like anything and doesn't have to +// be declared in this file. +UiFactory createFooHoc(UiFactory otherFactory) { + Object closureVariable; + // ... UiFactory FooHoc = uiFunction( - (props) { - return otherFactory()( - Dom.div()('closureVariable: ${closureVariable}'), - Dom.div()('prop foo: ${props.foo}'), - ); + (props) { + return otherFactory()( + Dom.div()('closureVariable: ${closureVariable}'), + Dom.div()('prop foo: ${props.foo}'), + ); }, UiFactoryConfig( displayName: 'FooHoc', propsFactory: PropsFactory.fromUiFactory(Foo), ), - ); + ); - return FooHoc; -} + return FooHoc; +} ``` #### With forwardRef @@ -918,14 +993,14 @@ To update your repository to the new boilerplate, there are two steps: If you are already using the mixin based boilerplate, skip to [Upgrade to the New Factory Syntax](#upgrade-to-the-new-factory-syntax). ### Upgrade to the Mixin Based Boilerplate -You can use [over_react_codemod](https://github.com/Workiva/over_react_codemod)'s -`boilerplate_upgrade` executable to make this step easier. This codemod goes -through the repository and updates the boilerplate as necessary. While -the codemod will handle many basic updates, it will still need to be -supplemented with some manual checks and refactoring. - -If you are migrating a Workiva library, before running the codemod, -run `semver_audit` inside your repository and save the report using the +You can use [over_react_codemod](https://github.com/Workiva/over_react_codemod)'s +`boilerplate_upgrade` executable to make this step easier. This codemod goes +through the repository and updates the boilerplate as necessary. While +the codemod will handle many basic updates, it will still need to be +supplemented with some manual checks and refactoring. + +If you are migrating a Workiva library, before running the codemod, +run `semver_audit` inside your repository and save the report using the following commands: 1. `pub global activate semver_audit --hosted-url=https://pub.workiva.org` @@ -934,16 +1009,16 @@ following commands: This will allow the codemod to check whether or not components are public API. -If you are migrating a library outside of the Workiva ecosystem, run the command +If you are migrating a library outside of the Workiva ecosystem, run the command below with the `--treat-all-components-as-private` flag. -Then, run the codemod by following the directions within +Then, run the codemod by following the directions within [the executable](https://github.com/Workiva/over_react_codemod/blob/master/lib/src/executables/boilerplate_upgrade.dart#L32) from the root of your local copy of the repository. #### Flags -When running the command `pub global run over_react_codemod:boilerplate_upgrade` +When running the command `pub global run over_react_codemod:boilerplate_upgrade` to update your components, there are two flags that can be used: * `--treat-all-components-as-private`: assumes that all components are not @@ -956,11 +1031,11 @@ superclasses to be upgraded to the new boilerplate. Without this flag, all class with external superclasses _will not_ be upgraded. ### Upgrade to the new factory syntax -Similar to step number 1, there is a codemod to assist with this. After activating over_react_codemod, within your +Similar to step number 1, there is a codemod to assist with this. After activating over_react_codemod, within your project, run: ```bash pub global run over_react_codemod:dart2_9_upgrade ``` -This upgrade is considered very minor, and while manual intervention may be necessary, we are not +This upgrade is considered very minor, and while manual intervention may be necessary, we are not aware of any edge cases that will be notably difficult. diff --git a/example/builder/src/functional_consumed_props.dart b/example/builder/src/functional_consumed_props.dart index fcb2ac455..3c6d164f7 100644 --- a/example/builder/src/functional_consumed_props.dart +++ b/example/builder/src/functional_consumed_props.dart @@ -28,14 +28,12 @@ mixin SharedPropsMixin on UiProps { class SomeParentProps = UiProps with ParentOnlyPropsMixin, SharedPropsMixin; UiFactory SomeParent = uiFunction((props) { - final consumedProps = props.staticMeta.forMixins({ParentOnlyPropsMixin}); - return ( Dom.div()( Dom.div()( 'The parent prop is: ${props.aParentProp}', ), - (SomeChild()..addUnconsumedProps(props, consumedProps))(), + (SomeChild()..addAll(props.getPropsToForward(exclude: {ParentOnlyPropsMixin})))(), ) ); }, diff --git a/example/builder/src/new_class_consumed_props.dart b/example/builder/src/new_class_consumed_props.dart index 4309b1fd9..f41ccd5c7 100644 --- a/example/builder/src/new_class_consumed_props.dart +++ b/example/builder/src/new_class_consumed_props.dart @@ -32,14 +32,12 @@ class SomeParentProps = UiProps with ParentOnlyPropsMixin, SharedPropsMixin; class SomeClassParentComponent extends UiComponent2 { @override render() { - final meta = props.staticMeta.forMixins({ParentOnlyPropsMixin}); - return ( Dom.div()( Dom.div()( 'The parent prop is: ${props.aParentProp}', ), - (SomeClassChild()..addUnconsumedProps(props, meta))(), + (SomeClassChild()..modifyProps(props.addPropsToForward(exclude: {ParentOnlyPropsMixin})))(), ) ); } @@ -58,4 +56,4 @@ class SomeClassChildComponent extends UiComponent2 { ) ); } -} \ No newline at end of file +} diff --git a/lib/src/component_declaration/builder_helpers.dart b/lib/src/component_declaration/builder_helpers.dart index cb35412e7..b14f2f90b 100644 --- a/lib/src/component_declaration/builder_helpers.dart +++ b/lib/src/component_declaration/builder_helpers.dart @@ -167,10 +167,10 @@ extension PropsToForward on T { /// } /// class FooProps = UiProps with BarProps, FooPropsMixin; /// - /// UiFactory Foo = uiFunction((props) { + /// UiFactory Foo = uiFunction((props) { /// return (Bar() /// // Filter out props declared in FooPropsMixin when forwarding to Bar. - /// ..addAll(props.getPropsToForward({FooPropsMixin})) + /// ..addAll(props.getPropsToForward(exclude: {FooPropsMixin})) /// )(); /// }); /// ``` @@ -185,20 +185,39 @@ extension PropsToForward on T { /// A utility function to be used with `modifyProps` to add props excluding the keys found in [exclude]. /// /// [exclude] should be a `Set` of PropsMixin `Type`s. - /// If [exclude] is not set it defaults to using the current instances Type. + /// If [exclude] is not set, it defaults to using the current instance's Type. /// /// __Example:__ /// + /// Component with a single props mixin: /// ```dart - /// // within a functional component: `uiFunction` - /// // filter out the current components props when forwarding to Bar. - /// return (Bar()..modifyProps(props.addPropsToForward()))(); + /// mixin FooPropsMixin on UiProps { + /// String foo; + /// } + /// + /// UiFactory Foo = uiFunction((props) { + /// return (Bar() + /// // Filter out props declared in FooPropsMixin + /// // (used as the default for `exclude` since that's what `props` is statically typed as) + /// // when forwarding to Bar. + /// ..modifyProps(props.addPropsToForward()) + /// )(); + /// }); /// ``` - /// OR + /// + /// Component with a more than one props mixin: /// ```dart - /// // within a functional component that has multiple mixins on a Props class: `uiFunction` - /// // filter out the Components props when forwarding to Bar. - /// return (Bar()..modifyProps(props.addPropsToForward(exclude: {FooPropsMixin}))(); + /// mixin FooPropsMixin on UiProps { + /// String foo; + /// } + /// class FooProps = UiProps with BarProps, FooPropsMixin; + /// + /// UiFactory Foo = uiFunction((props) { + /// return (Bar() + /// // Filter out props declared in FooPropsMixin when forwarding to Bar. + /// ..modifyProps(props.addPropsToForward(exclude: {FooPropsMixin})) + /// )(); + /// }); /// ``` /// /// To only add DOM props, use the [domOnly] named argument. From 5b1de452c7146602bfd66163b6d8474c740753d6 Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Thu, 10 Aug 2023 12:55:41 -0700 Subject: [PATCH 24/32] remove the line termination change --- doc/new_boilerplate_migration.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/new_boilerplate_migration.md b/doc/new_boilerplate_migration.md index b490c26f5..d31f020ea 100644 --- a/doc/new_boilerplate_migration.md +++ b/doc/new_boilerplate_migration.md @@ -51,9 +51,9 @@ class _$FooProps extends BarProps { } // Generated in .over_react.g.dart -class FooProps extends _$FooProps with _$FooPropsAccessorsMixin { +class FooProps extends _$FooProps with _$FooPropsAccessorsMixin {
 static const PropsMeta meta = ...; - ... + ...
 } ``` @@ -81,9 +81,9 @@ class FooProps with // ignore: mixin_of_non_class, undefined_class _$FooPropsAccessorsMixin { - // ignore: const_initialized_with_non_constant_value, undefined_class, undefined_identifier + // ignore: const_initialized_with_non_constant_value, undefined_class, undefined_identifier 
 static const PropsMeta meta = _$metaForPanelTitleProps; -} +
} ``` This is overly verbose, confusing, and error-prone. Authoring components should be @@ -907,7 +907,7 @@ UiFactory Foo = uiFunction( ); ``` -#### With propTypes (~Coming soon!~ lol no its not) +#### With propTypes (Coming soon!) ```dart UiFactory Foo = uiFunction( From 0ad2e6d78e6dee527e9d1e6932e84348d2a47600 Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Thu, 10 Aug 2023 13:05:21 -0700 Subject: [PATCH 25/32] Revert "remove the line termination change" This reverts commit 5b1de452c7146602bfd66163b6d8474c740753d6. --- doc/new_boilerplate_migration.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/new_boilerplate_migration.md b/doc/new_boilerplate_migration.md index d31f020ea..b490c26f5 100644 --- a/doc/new_boilerplate_migration.md +++ b/doc/new_boilerplate_migration.md @@ -51,9 +51,9 @@ class _$FooProps extends BarProps { } // Generated in .over_react.g.dart -class FooProps extends _$FooProps with _$FooPropsAccessorsMixin {
 +class FooProps extends _$FooProps with _$FooPropsAccessorsMixin { static const PropsMeta meta = ...; - ...
 + ... } ``` @@ -81,9 +81,9 @@ class FooProps with // ignore: mixin_of_non_class, undefined_class _$FooPropsAccessorsMixin { - // ignore: const_initialized_with_non_constant_value, undefined_class, undefined_identifier 
 + // ignore: const_initialized_with_non_constant_value, undefined_class, undefined_identifier static const PropsMeta meta = _$metaForPanelTitleProps; -
} +} ``` This is overly verbose, confusing, and error-prone. Authoring components should be @@ -907,7 +907,7 @@ UiFactory Foo = uiFunction( ); ``` -#### With propTypes (Coming soon!) +#### With propTypes (~Coming soon!~ lol no its not) ```dart UiFactory Foo = uiFunction( From d0b6d7d1bee612cb414f06292efc85edf49d3d31 Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Thu, 10 Aug 2023 13:05:27 -0700 Subject: [PATCH 26/32] Revert "update docs?" This reverts commit e43030db3080301c557900a5ccc605995e0464c6. --- doc/new_boilerplate_migration.md | 477 ++++++++---------- .../src/functional_consumed_props.dart | 4 +- .../builder/src/new_class_consumed_props.dart | 6 +- .../builder_helpers.dart | 39 +- 4 files changed, 218 insertions(+), 308 deletions(-) diff --git a/doc/new_boilerplate_migration.md b/doc/new_boilerplate_migration.md index b490c26f5..c1face774 100644 --- a/doc/new_boilerplate_migration.md +++ b/doc/new_boilerplate_migration.md @@ -23,18 +23,18 @@ _Preview of new boilerplate:_ ## Background -While converting the boilerplate for OverReact component declaration from Dart 1 -transformers to Dart 2 builders, we encountered several constraints that made us -choose between dropping backwards compatibility (mainly support for props class +While converting the boilerplate for OverReact component declaration from Dart 1 +transformers to Dart 2 builders, we encountered several constraints that made us +choose between dropping backwards compatibility (mainly support for props class inheritance), and a less-than-optimal boilerplate. -To make the Dart 2 transition as smooth as possible, we chose to keep the new -boilerplate version as backwards-compatible as possible, while compromising the -cleanliness of the boilerplate. In time, we found that this wasn't great from a +To make the Dart 2 transition as smooth as possible, we chose to keep the new +boilerplate version as backwards-compatible as possible, while compromising the +cleanliness of the boilerplate. In time, we found that this wasn't great from a user experience or from a tooling perspective. -Knowing this, and having dropped support for Dart 1, we now have the opportunity -to implement an improved version of OverReact boilerplate that fixes issues +Knowing this, and having dropped support for Dart 1, we now have the opportunity +to implement an improved version of OverReact boilerplate that fixes issues introduced in the latest version, as well as other miscellaneous ones. ### Problems with Previous Boilerplate @@ -51,21 +51,21 @@ class _$FooProps extends BarProps { } // Generated in .over_react.g.dart -class FooProps extends _$FooProps with _$FooPropsAccessorsMixin { +class FooProps extends _$FooProps with _$FooPropsAccessorsMixin {
 static const PropsMeta meta = ...; - ... + ...
 } ``` -In using the build packages's `build_to: cache` to generate public APIs, command-line -tools like `dartanalyzer` and `dartdoc` don't function properly for packages that do this. +In using the build packages's `build_to: cache` to generate public APIs, command-line +tools like `dartanalyzer` and `dartdoc` don't function properly for packages that do this. -This pattern can also degrade the dev experience, by requiring a build before code is -statically valid, and also requiring rebuilds in some cases to consume public API +This pattern can also degrade the dev experience, by requiring a build before code is +statically valid, and also requiring rebuilds in some cases to consume public API updates during their development (e.g., writing new component props classes). -The transitional _(backwards-compatible with Dart 1)_ boilerplate, currently used in -almost all repos, does not have these issues, as it requires users to stub in these +The transitional _(backwards-compatible with Dart 1)_ boilerplate, currently used in +almost all repos, does not have these issues, as it requires users to stub in these public classes: ```dart @@ -81,20 +81,20 @@ class FooProps with // ignore: mixin_of_non_class, undefined_class _$FooPropsAccessorsMixin { - // ignore: const_initialized_with_non_constant_value, undefined_class, undefined_identifier + // ignore: const_initialized_with_non_constant_value, undefined_class, undefined_identifier 
 static const PropsMeta meta = _$metaForPanelTitleProps; -} +
} ``` -This is overly verbose, confusing, and error-prone. Authoring components should be +This is overly verbose, confusing, and error-prone. Authoring components should be simple and easy. #### Inheritance -Props are declared as fields, and we generate the accessor (AKA getters/setters) +Props are declared as fields, and we generate the accessor (AKA getters/setters) implementations that are to be used when reading and writing props. -If the consumer authors the public-facing class, we have to do this in new +If the consumer authors the public-facing class, we have to do this in new generated subclasses to be able to override the field implementation. ```dart @@ -113,7 +113,7 @@ mixin $FooPropsAccessors on FooProps { class $FooProps = FooProps with $FooPropsAccessors; ``` -However, if consumers were to extend from the authored class, they wouldn't +However, if consumers were to extend from the authored class, they wouldn't inherit these generated fields. ```dart @@ -133,91 +133,91 @@ test() { #### Technical Constraints: -1. We cannot use resolved AST to generate components because it slows down +1. We cannot use resolved AST to generate components because it slows down the build too much. - In other words, we have access to the structure of the code within a - given file but not its full semantic meaning, and cannot resolve + In other words, we have access to the structure of the code within a + given file but not its full semantic meaning, and cannot resolve references it makes to code in other files. - - For instance, we can look at a class and see the name of the class it - extends from and the methods it declares, but we won't be able to know - where the parent class comes from, what type(s) the parent implements, + + For instance, we can look at a class and see the name of the class it + extends from and the methods it declares, but we won't be able to know + where the parent class comes from, what type(s) the parent implements, or which member(s) the parent declares. 2. User-authored code must reference generated code somehow to "wire it up". - Since generated code can be output only to new files, component + Since generated code can be output only to new files, component registration / wiring of generated code requires either: - - 1. a centralized, generated registry that maps components to generated - component code, and that must be generated for and consumed in that + + 1. a centralized, generated registry that maps components to generated + component code, and that must be generated for and consumed in that main() method of all consuming apps' entrypoints. - - 2. a user-authored entrypoint (field initializer, method invocation, - constructor, etc.) that imports (or pulls in via a part) and references + + 2. a user-authored entrypoint (field initializer, method invocation, + constructor, etc.) that imports (or pulls in via a part) and references generated code (what we have now). #### Self-Imposed Constraints: 1. Keep component declarations as terse and user-friendly as possible. -2. Use `build_to: cache` (for more information, see: +2. Use `build_to: cache` (for more information, see: [pkg:build docs](https://github.com/dart-lang/build/blob/master/docs/builder_author_faq.md#when-should-a-builder-build-to-cache-vs-source)). - - `build_to:cache` should be used when generated code is dependent on the - library's underlying implementation. This may not be strictly the case - today, but if we commit to `build_to: cache`, we will have more - flexibility in the future to make improvements or fix bugs to OverReact - code generation without requiring a (very expensive) breaking change. - - It would also result in improvements to the builder being propagated - immediately as soon as they're consumed by wdesk, as opposed to having + + `build_to:cache` should be used when generated code is dependent on the + library's underlying implementation. This may not be strictly the case + today, but if we commit to `build_to: cache`, we will have more + flexibility in the future to make improvements or fix bugs to OverReact + code generation without requiring a (very expensive) breaking change. + + It would also result in improvements to the builder being propagated + immediately as soon as they're consumed by wdesk, as opposed to having to regenerate code and release within every consumer library. 3. Make source code statically analyzable without running a build. - The build docs instruct not to use build_to: cache to generate public - APIs, and command-line tools like `dartanalyzer` and `dartdoc` don't - function properly for packages that do this. - - Generating public APIs can also degrade the dev experience, by requiring - a build before code is statically valid, and also requiring rebuilds in - some cases to consume public API updates during their development (e.g., + The build docs instruct not to use build_to: cache to generate public + APIs, and command-line tools like `dartanalyzer` and `dartdoc` don't + function properly for packages that do this. + + Generating public APIs can also degrade the dev experience, by requiring + a build before code is statically valid, and also requiring rebuilds in + some cases to consume public API updates during their development (e.g., writing new component props classes). - -4. Provide some means of sharing props/state declarations between + +4. Provide some means of sharing props/state declarations between components. - Being able to share props/state between multiple components is useful, - especially when composing them together. We also have many legacy - components that currently share props, and want to make it possible + Being able to share props/state between multiple components is useful, + especially when composing them together. We also have many legacy + components that currently share props, and want to make it possible to upgrade them. - -5. Provide a simple migration path for _most_ components in our consumer + +5. Provide a simple migration path for _most_ components in our consumer ecosystems. - We can support new/old boilerplate at the same time, and slowly phase - out the old as we migrate over to it using + We can support new/old boilerplate at the same time, and slowly phase + out the old as we migrate over to it using [our `boilerplate_upgrade` codemod](#upgrading-existing-code). - - For cases that don't migrate cleanly within the Workiva ecosystem, - we can use the Wdesk versioning policy to replace them with APIs that + + For cases that don't migrate cleanly within the Workiva ecosystem, + we can use the Wdesk versioning policy to replace them with APIs that use the new boilerplate in major versions or using versioned APIs. - + 6. Only support Component2 components. - The builder has different code paths for Component/Component2, and - supporting an additional boilerplate for both would increase code - complexity and effort needed to build/test it. + The builder has different code paths for Component/Component2, and + supporting an additional boilerplate for both would increase code + complexity and effort needed to build/test it. ### Updates #### Add `castUiFactory` Utility -A utility called `castUiFactory` has been added that prevent implicit cast errors -(which are no longer ignorable as of Dart 2.9) on factory declarations. All that needs to be done is to wrap the generated -factory with `castUiFactory`, so that it can infer the typing from the left hand side and cast the factory (considered +A utility called `castUiFactory` has been added that prevent implicit cast errors +(which are no longer ignorable as of Dart 2.9) on factory declarations. All that needs to be done is to wrap the generated +factory with `castUiFactory`, so that it can infer the typing from the left hand side and cast the factory (considered "dynamic" before code generation is run) to the correct type. ```diff @@ -229,10 +229,10 @@ UiFactory Foo = #### Remove Annotations -`@Factory()`, `@Props()` and `@Component()` annotations add additional +`@Factory()`, `@Props()` and `@Component()` annotations add additional visual clutter to the boilerplate, and are redundant since the factory/ -props/component declarations already have a consistent/restricted -structure and naming scheme that makes it clear to the builder parsing +props/component declarations already have a consistent/restricted +structure and naming scheme that makes it clear to the builder parsing logic that a component is being defined, and what each part is. ```diff @@ -263,7 +263,7 @@ class _$FooProps extends BarProps { #### Ignore Ungenerated Warnings Project-Wide -Right now, we have to add `// ignore: uri_has_not_been_generated` to each +Right now, we have to add `// ignore: uri_has_not_been_generated` to each component library on the part/import that references generated code. Ignoring this hint globally within analysis_options.yaml: @@ -271,10 +271,10 @@ Ignoring this hint globally within analysis_options.yaml: ```yaml analyzer: errors: - uri_has_not_been_generated: ignore + uri_has_not_been_generated: ignore ``` -Allows individual ignores to be omitted, which will reduce clutter in +Allows individual ignores to be omitted, which will reduce clutter in the component boilerplate. ```diff @@ -288,14 +288,14 @@ This warning is also ignored by default in [workiva_analysis_options](https://gi _Constraints_: -* Props classes must directly subclass UiProps, only inheriting other props +* Props classes must directly subclass UiProps, only inheriting other props via mixins. - * This requires consumers to include every single mixin within their `with` - clause, allowing the builder to mix in the generated code corresponding + * This requires consumers to include every single mixin within their `with` + clause, allowing the builder to mix in the generated code corresponding to those mixins. - -* Props can only be declared in mixins. + +* Props can only be declared in mixins. * This ensures they can be inherited by other props classes (by mixing them in, since you can no longer inherit them via subclassing) and provides consistency around how props are declared. @@ -362,7 +362,7 @@ import 'package:over_react/over_react.dart'; part 'foo.over_react.g.dart'; -UiFactory Foo = +UiFactory Foo = castUiFactory(_$Foo); // ignore: undefined_identifier mixin FooProps on UiProps { @@ -377,17 +377,17 @@ class FooComponent extends UiComponent2 { ##### Props Meta Changes -Props meta will be generated as an overridden getter on the component -as opposed to the current static field, and will allow similar access +Props meta will be generated as an overridden getter on the component +as opposed to the current static field, and will allow similar access of prop keys as before. -This eliminates the current `meta` portion of the boilerplate which has +This eliminates the current `meta` portion of the boilerplate which has to reference more generated code. -Prop meta from all mixins can be accessed, allowing us to default +Prop meta from all mixins can be accessed, allowing us to default `consumedProps` to all props statically accessible from that component. -Consumption: +Consumption: ```dart @Props() @@ -454,7 +454,7 @@ class PropsMetaCollection implements PropsMeta { /// Returns the metadata for only the prop fields declared in [mixinType]. PropsMeta forMixin(Type mixinType) { final meta = _metaByMixin[mixinType]; - assert(meta != null, + assert(meta != null, 'No meta found for $mixinType;' 'it likely isn\'t mixed in by the props class.') return meta ?? const PropsMeta(fields: [], keys: []); @@ -463,11 +463,11 @@ class PropsMetaCollection implements PropsMeta { // PropsMeta overrides @override - List get keys => + List get keys => _metaByMixin.values.expand((meta) => meta.keys).toList(); @override - List get fields => + List get fields => _metaByMixin.values.expand((meta) => meta.fields).toList(); @override @@ -477,12 +477,12 @@ class PropsMetaCollection implements PropsMeta { ##### Props MapViews -Since props mixins can only be consumed by other generated code, the -existing props map view consumption pattern, whereby props mixins are +Since props mixins can only be consumed by other generated code, the +existing props map view consumption pattern, whereby props mixins are consumed in user-authored MapView subclasses, cannot be supported. -Instead, props map views will be declared similarly to a component, -with a factory and props mixin/class, but no component. +Instead, props map views will be declared similarly to a component, +with a factory and props mixin/class, but no component. ```diff import 'package:over_react/over_react.dart'; @@ -522,19 +522,19 @@ class FooProps extends UiProps { class FooComponent extends UiComponent { render() { // { - // FooProps.foo: 1, - // FooProps.bar: 2, + // FooProps.foo: 1, + // FooProps.bar: 2, // data-a-dom-prop: 3, // onClick: 4, // someArbitraryProp: 5, // } print(props); - + // { // data-a-dom-prop: 3, // onClick: 4, // someArbitraryProp: 5, - // } + // } print(copyUnconsumedProps()); } } @@ -552,21 +552,21 @@ class FooProps extends OtherPropsMixin { class FooComponent extends UiComponent { render() { // { - // FooProps.foo: 1, - // FooProps.bar: 2, + // FooProps.foo: 1, + // FooProps.bar: 2, // data-a-dom-prop: 3, // onClick: 4, // someArbitraryProp: 5, - // OtherPropsMixin.other: 6, + // OtherPropsMixin.other: 6, // } print(props); - + // { // data-a-dom-prop: 3, // onClick: 4, // someArbitraryProp: 5, // OtherPropsMixin.other: 6, - // } + // } print(copyUnconsumedProps()); } } @@ -586,7 +586,7 @@ get consumedProps => [ #### Updated default behavior in the mixin-based syntax With the new mixin-based syntax, props cannot be declared directly within props classes, so if we kept using the consumed -props behavior from the legacy syntax, they wouldn't have any consumed props by default. (Unless we picked a mixin or something, +props behavior from the legacy syntax, they wouldn't have any consumed props by default. (Unless we picked a mixin or something, which could get confusing) This would mean that any props class that consumes the props of a single mixin would need to override consumedProps, @@ -621,7 +621,7 @@ class FooComponent ... { } ``` -To help optimize this use-case, as well as to make whether props are consumed or not more consistent across different +To help optimize this use-case, as well as to make whether props are consumed or not more consistent across different forms of the new syntax, we decided to __consume props from all props mixins by default__, if consumedProps is not overridden. So, taking the above example again, the new behavior would be: @@ -636,20 +636,20 @@ class FooProps = UiProps with FooPropsMixin, OtherPropsMixin; class FooComponent extends UiComponent { render() { // { - // FooProps.foo: 1, - // FooProps.bar: 2, + // FooProps.foo: 1, + // FooProps.bar: 2, // data-a-dom-prop: 3, // onClick: 4, // someArbitraryProp: 5, - // OtherPropsMixin.other: 6, + // OtherPropsMixin.other: 6, // } print(props); - + // { // data-a-dom-prop: 3, // onClick: 4, // someArbitraryProp: 5, - // } + // } print(copyUnconsumedProps()); } } @@ -661,16 +661,16 @@ For example: - Consuming all props except for a few mixins: - Before: + Before: ```dart - @Props() - class FooProps extends UiProps with - AProps, - BProps, - CProps, - NoConsumeProps { ... } - - @Component() + @Props() + class FooProps extends UiProps with + AProps, + BProps, + CProps, + NoConsumeProps { ... } + + @Component() class FooComponent extends UiComponent { @override consumedProps => [ @@ -679,36 +679,36 @@ For example: BProps.meta, CProps.meta, ]; - + ... } ``` After: ```dart class FooProps = UiProps with - FooPropsMixin - AProps, - BProps, - CProps, - NoConsumeProps; - + FooPropsMixin + AProps, + BProps, + CProps, + NoConsumeProps; + class FooComponent extends UiComponent { @override - consumedProps => + consumedProps => propsMeta.allExceptForMixins({NoConsumeProps}), - + ... } ``` #### Why didn't we do this earlier? -We couldn't "consume" props from other classes by default, since we didn't have full knowledge of all the +We couldn't "consume" props from other classes by default, since we didn't have full knowledge of all the props classes and mixins inherited by a given props class's superclass (due to not having a resolved AST in our builder, for performance reasons). However, in the new mixin-based syntax, props classes must explicitly mix in all props mixins they inherit from, -so we're able to easily tell at build time what they all are, and thus don't have that same restriction. +so we're able to easily tell at build time what they all are, and thus don't have that same restriction. ### Examples @@ -720,20 +720,20 @@ Most code within over_react has been updated to use this new boilerplate, includ ## Function Component Boilerplate -### Function Component Constraints +### Function Component Constraints -Includes all of [the constraints listed in the Boilerplate Updates section](#design-constraints), +Includes all of [the constraints listed in the Boilerplate Updates section](#design-constraints), ignoring parts about backwards-compatibility. * Should be as visually uncluttered as possible. * Should not wrap excessively for longer component names. -* Should be easy to transition between having and not having default +* Should be easy to transition between having and not having default props, and boilerplate shouldn't change shape drastically when doing so. -* Function calls using generated functions should be avoided since they -don't allow generic type inference of the `props` arg in the function +* Function calls using generated functions should be avoided since they +don't allow generic type inference of the `props` arg in the function closure. ### Syntax @@ -745,25 +745,25 @@ part 'foo.over_react.g.dart'; UiFactory Foo = uiFunction( (props) { - return 'foo: ${props.foo}'; + return 'foo: ${props.foo}'; }, _$FooConfig, // ignore: undefined_identifier -); +); mixin FooProps on UiProps { String foo; } ``` -Here, `uiFunction` gets a generic parameter of `FooProps` inferred +Here, `uiFunction` gets a generic parameter of `FooProps` inferred from the LHS typing, allowing props to be statically typed as `FooProps`. -The generated `$FooConfig` is passed in as an argument, and serves -as the entrypoint to the generated code. +The generated `$FooConfig` is passed in as an argument, and serves +as the entrypoint to the generated code. #### With Default Props -`defaultProps` on function components is +`defaultProps` on function components is [already deprecated](https://github.com/facebook/react/pull/16210). Instead, we use null-aware operators to default null values. This provides almost the @@ -775,123 +775,48 @@ UiFactory Foo = uiFunction( (props) { final foo = props.foo ?? 'default foo value'; - return 'foo: $foo'; + return 'foo: $foo'; }, _$FooConfig, // ignore: undefined_identifier -); -``` - -#### With Prop Forwarding (fka Consumed Props) - -Because functional components have no instance that track consumed props, the syntax for forwarding props -changes within functional components. - -`UiProps` exposes 2 APIs `getPropsToForward` & `addPropsToForward` that can be used to forward props -that have not been used to a child component. - -##### getPropsToForward -`getPropsToForward` will return a `Map` of props removing the props found in the `exclude` argument. -`exclude` is optional and will default to a `Set` with the type that `props` is statically typed as, -this only works with `mixin .. on UiProps` types. If your function component uses a Props `class` then -you must include an `exclude` argument. - -Component with a single props mixin: -```dart -mixin FooPropsMixin on UiProps { - String foo; -} - -UiFactory Foo = uiFunction((props) { - return (Bar() - // Filter out props declared in FooPropsMixin - // (used as the default for `exclude` since that's what `props` is statically typed as) - // when forwarding to Bar. - ..addAll(props.getPropsToForward()) - )(); -}); +); ``` -Component with a more than one props mixin: -```dart -mixin FooPropsMixin on UiProps { - String foo; -} +#### With Consumed Props -class FooProps = UiProps with BarProps, FooPropsMixin; +Because functional components have no instance that track consumed props, the syntax for passing unconsumed +props changes within functional components. -UiFactory Foo = uiFunction((props) { - return (Bar() - // Filter out props declared in FooPropsMixin when forwarding to Bar. - ..addAll(props.getPropsToForward(exclude: {FooPropsMixin})) - )(); -}); -``` +`UiProps` exposes a field `staticMeta` that can be used to generate an iterable containing props meta for specific mixins. +This is similar to accessing `propsMeta` within a class based component. Using the iterable returned from `staticMeta`'s +APIs (such as `forMixins`), we can generate unconsumed props and pass them to a child component. -`domOnly` - to forward DOM props only: +This is done like so: ```dart mixin FooPropsMixin on UiProps { - String foo; + String passedProp; } -UiFactory Foo = uiFunction((props) { - return (Dom.div() - // Forward only DOM based props & Filter out props declared in FooPropsMixin - // (used as the default for `exclude` since that's what `props` is statically typed as) - // when forwarding to Bar. - ..addAll(props.getPropsToForward(domOnly: true)) - )(); -}); -``` - -##### addPropsToForward -`addPropsToForward` has the same function signature as `getPropsToForward` but is meant to be used with the `UiProps` method `modifyProps`. - -Component with a single props mixin: -```dart -mixin FooPropsMixin on UiProps { - String foo; +mixin BarPropsMixin on UiProps { + String nonPassedProp; } -UiFactory Foo = uiFunction((props) { - return (Bar() - // Filter out props declared in FooPropsMixin - // (used as the default for `exclude` since that's what `props` is statically typed as) - // when forwarding to Bar. - ..modifyProps(props.addPropsToForward()) - )(); -}); -``` +class FooBarProps = UiProps with BarPropsMixin, FooPropsMixin; -Component with a more than one props mixin: -```dart -mixin FooPropsMixin on UiProps { - String foo; -} - -class FooProps = UiProps with BarProps, FooPropsMixin; - -UiFactory Foo = uiFunction((props) { - return (Bar() - // Filter out props declared in FooPropsMixin when forwarding to Bar. - ..modifyProps(props.addPropsToForward(exclude: {FooPropsMixin})) - )(); -}); -``` +UiFactory FooBar = uiFunction( + (props) { + final consumedProps = props.staticMeta.forMixins({BarPropsMixin}); -`domOnly` - to forward DOM props only: -```dart -mixin FooPropsMixin on UiProps { - String foo; -} + return (Foo()..addUnconsumedProps(props, consumedProps))(); + }, + _$FooBarConfig, // ignore: undefined_identifier +); -UiFactory Foo = uiFunction((props) { - return (Dom.div() - // Forward only DOM based props & Filter out props declared in FooPropsMixin - // (used as the default for `exclude` since that's what `props` is statically typed as) - // when forwarding to Bar. - ..modifyProps(props.addPropsToForward(domOnly: true)) - )(); -}); +UiFactory Foo = uiFunction( + (props) { + return 'foo: ${props.passedProp}'; + }, + _$FooConfig, // ignore: undefined_identifier +); ``` #### With UiProps @@ -899,21 +824,21 @@ UiFactory Foo = uiFunction((props) { ```dart UiFactory Foo = uiFunction( (props) { - return 'id: ${props.id}'; - }, + return 'id: ${props.id}'; + }, UiFactoryConfig( displayName: 'Foo', ), ); ``` -#### With propTypes (~Coming soon!~ lol no its not) +#### With propTypes (Coming soon!) ```dart UiFactory Foo = uiFunction( (props) { - return 'foo: ${props.foo}'; - }, + return 'foo: ${props.foo}'; + }, _$FooConfig, // ignore: undefined_identifier getPropTypes: (keyFor) => { keyFor((p) => p.foo): (props, info) { @@ -925,7 +850,7 @@ UiFactory Foo = uiFunction( ); ``` -`getPropTypes` provides a way to set up prop validation within the +`getPropTypes` provides a way to set up prop validation within the same variable initializer. #### Local function components using just a props mixin - no top-level Factory necessary (Coming soon!) @@ -939,27 +864,27 @@ mixin FooProps on UiProps { String foo; } -// Example function; this can look like anything and doesn't have to -// be declared in this file. -UiFactory createFooHoc(UiFactory otherFactory) { - Object closureVariable; - // ... +// Example function; this can look like anything and doesn't have to +// be declared in this file. +UiFactory createFooHoc(UiFactory otherFactory) { + Object closureVariable; + // ... UiFactory FooHoc = uiFunction( - (props) { - return otherFactory()( - Dom.div()('closureVariable: ${closureVariable}'), - Dom.div()('prop foo: ${props.foo}'), - ); + (props) { + return otherFactory()( + Dom.div()('closureVariable: ${closureVariable}'), + Dom.div()('prop foo: ${props.foo}'), + ); }, UiFactoryConfig( displayName: 'FooHoc', propsFactory: PropsFactory.fromUiFactory(Foo), ), - ); + ); - return FooHoc; -} + return FooHoc; +} ``` #### With forwardRef @@ -993,14 +918,14 @@ To update your repository to the new boilerplate, there are two steps: If you are already using the mixin based boilerplate, skip to [Upgrade to the New Factory Syntax](#upgrade-to-the-new-factory-syntax). ### Upgrade to the Mixin Based Boilerplate -You can use [over_react_codemod](https://github.com/Workiva/over_react_codemod)'s -`boilerplate_upgrade` executable to make this step easier. This codemod goes -through the repository and updates the boilerplate as necessary. While -the codemod will handle many basic updates, it will still need to be -supplemented with some manual checks and refactoring. - -If you are migrating a Workiva library, before running the codemod, -run `semver_audit` inside your repository and save the report using the +You can use [over_react_codemod](https://github.com/Workiva/over_react_codemod)'s +`boilerplate_upgrade` executable to make this step easier. This codemod goes +through the repository and updates the boilerplate as necessary. While +the codemod will handle many basic updates, it will still need to be +supplemented with some manual checks and refactoring. + +If you are migrating a Workiva library, before running the codemod, +run `semver_audit` inside your repository and save the report using the following commands: 1. `pub global activate semver_audit --hosted-url=https://pub.workiva.org` @@ -1009,16 +934,16 @@ following commands: This will allow the codemod to check whether or not components are public API. -If you are migrating a library outside of the Workiva ecosystem, run the command +If you are migrating a library outside of the Workiva ecosystem, run the command below with the `--treat-all-components-as-private` flag. -Then, run the codemod by following the directions within +Then, run the codemod by following the directions within [the executable](https://github.com/Workiva/over_react_codemod/blob/master/lib/src/executables/boilerplate_upgrade.dart#L32) from the root of your local copy of the repository. #### Flags -When running the command `pub global run over_react_codemod:boilerplate_upgrade` +When running the command `pub global run over_react_codemod:boilerplate_upgrade` to update your components, there are two flags that can be used: * `--treat-all-components-as-private`: assumes that all components are not @@ -1031,11 +956,11 @@ superclasses to be upgraded to the new boilerplate. Without this flag, all class with external superclasses _will not_ be upgraded. ### Upgrade to the new factory syntax -Similar to step number 1, there is a codemod to assist with this. After activating over_react_codemod, within your +Similar to step number 1, there is a codemod to assist with this. After activating over_react_codemod, within your project, run: ```bash pub global run over_react_codemod:dart2_9_upgrade ``` -This upgrade is considered very minor, and while manual intervention may be necessary, we are not +This upgrade is considered very minor, and while manual intervention may be necessary, we are not aware of any edge cases that will be notably difficult. diff --git a/example/builder/src/functional_consumed_props.dart b/example/builder/src/functional_consumed_props.dart index 3c6d164f7..fcb2ac455 100644 --- a/example/builder/src/functional_consumed_props.dart +++ b/example/builder/src/functional_consumed_props.dart @@ -28,12 +28,14 @@ mixin SharedPropsMixin on UiProps { class SomeParentProps = UiProps with ParentOnlyPropsMixin, SharedPropsMixin; UiFactory SomeParent = uiFunction((props) { + final consumedProps = props.staticMeta.forMixins({ParentOnlyPropsMixin}); + return ( Dom.div()( Dom.div()( 'The parent prop is: ${props.aParentProp}', ), - (SomeChild()..addAll(props.getPropsToForward(exclude: {ParentOnlyPropsMixin})))(), + (SomeChild()..addUnconsumedProps(props, consumedProps))(), ) ); }, diff --git a/example/builder/src/new_class_consumed_props.dart b/example/builder/src/new_class_consumed_props.dart index f41ccd5c7..4309b1fd9 100644 --- a/example/builder/src/new_class_consumed_props.dart +++ b/example/builder/src/new_class_consumed_props.dart @@ -32,12 +32,14 @@ class SomeParentProps = UiProps with ParentOnlyPropsMixin, SharedPropsMixin; class SomeClassParentComponent extends UiComponent2 { @override render() { + final meta = props.staticMeta.forMixins({ParentOnlyPropsMixin}); + return ( Dom.div()( Dom.div()( 'The parent prop is: ${props.aParentProp}', ), - (SomeClassChild()..modifyProps(props.addPropsToForward(exclude: {ParentOnlyPropsMixin})))(), + (SomeClassChild()..addUnconsumedProps(props, meta))(), ) ); } @@ -56,4 +58,4 @@ class SomeClassChildComponent extends UiComponent2 { ) ); } -} +} \ No newline at end of file diff --git a/lib/src/component_declaration/builder_helpers.dart b/lib/src/component_declaration/builder_helpers.dart index b14f2f90b..cb35412e7 100644 --- a/lib/src/component_declaration/builder_helpers.dart +++ b/lib/src/component_declaration/builder_helpers.dart @@ -167,10 +167,10 @@ extension PropsToForward on T { /// } /// class FooProps = UiProps with BarProps, FooPropsMixin; /// - /// UiFactory Foo = uiFunction((props) { + /// UiFactory Foo = uiFunction((props) { /// return (Bar() /// // Filter out props declared in FooPropsMixin when forwarding to Bar. - /// ..addAll(props.getPropsToForward(exclude: {FooPropsMixin})) + /// ..addAll(props.getPropsToForward({FooPropsMixin})) /// )(); /// }); /// ``` @@ -185,39 +185,20 @@ extension PropsToForward on T { /// A utility function to be used with `modifyProps` to add props excluding the keys found in [exclude]. /// /// [exclude] should be a `Set` of PropsMixin `Type`s. - /// If [exclude] is not set, it defaults to using the current instance's Type. + /// If [exclude] is not set it defaults to using the current instances Type. /// /// __Example:__ /// - /// Component with a single props mixin: /// ```dart - /// mixin FooPropsMixin on UiProps { - /// String foo; - /// } - /// - /// UiFactory Foo = uiFunction((props) { - /// return (Bar() - /// // Filter out props declared in FooPropsMixin - /// // (used as the default for `exclude` since that's what `props` is statically typed as) - /// // when forwarding to Bar. - /// ..modifyProps(props.addPropsToForward()) - /// )(); - /// }); + /// // within a functional component: `uiFunction` + /// // filter out the current components props when forwarding to Bar. + /// return (Bar()..modifyProps(props.addPropsToForward()))(); /// ``` - /// - /// Component with a more than one props mixin: + /// OR /// ```dart - /// mixin FooPropsMixin on UiProps { - /// String foo; - /// } - /// class FooProps = UiProps with BarProps, FooPropsMixin; - /// - /// UiFactory Foo = uiFunction((props) { - /// return (Bar() - /// // Filter out props declared in FooPropsMixin when forwarding to Bar. - /// ..modifyProps(props.addPropsToForward(exclude: {FooPropsMixin})) - /// )(); - /// }); + /// // within a functional component that has multiple mixins on a Props class: `uiFunction` + /// // filter out the Components props when forwarding to Bar. + /// return (Bar()..modifyProps(props.addPropsToForward(exclude: {FooPropsMixin}))(); /// ``` /// /// To only add DOM props, use the [domOnly] named argument. From 23c11e972e185e4139fd4ad086c8ded086210282 Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Thu, 10 Aug 2023 13:07:33 -0700 Subject: [PATCH 27/32] add other doc updates --- .../src/functional_consumed_props.dart | 4 +- .../builder/src/new_class_consumed_props.dart | 6 +-- .../builder_helpers.dart | 39 ++++++++++++++----- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/example/builder/src/functional_consumed_props.dart b/example/builder/src/functional_consumed_props.dart index fcb2ac455..3c6d164f7 100644 --- a/example/builder/src/functional_consumed_props.dart +++ b/example/builder/src/functional_consumed_props.dart @@ -28,14 +28,12 @@ mixin SharedPropsMixin on UiProps { class SomeParentProps = UiProps with ParentOnlyPropsMixin, SharedPropsMixin; UiFactory SomeParent = uiFunction((props) { - final consumedProps = props.staticMeta.forMixins({ParentOnlyPropsMixin}); - return ( Dom.div()( Dom.div()( 'The parent prop is: ${props.aParentProp}', ), - (SomeChild()..addUnconsumedProps(props, consumedProps))(), + (SomeChild()..addAll(props.getPropsToForward(exclude: {ParentOnlyPropsMixin})))(), ) ); }, diff --git a/example/builder/src/new_class_consumed_props.dart b/example/builder/src/new_class_consumed_props.dart index 4309b1fd9..f41ccd5c7 100644 --- a/example/builder/src/new_class_consumed_props.dart +++ b/example/builder/src/new_class_consumed_props.dart @@ -32,14 +32,12 @@ class SomeParentProps = UiProps with ParentOnlyPropsMixin, SharedPropsMixin; class SomeClassParentComponent extends UiComponent2 { @override render() { - final meta = props.staticMeta.forMixins({ParentOnlyPropsMixin}); - return ( Dom.div()( Dom.div()( 'The parent prop is: ${props.aParentProp}', ), - (SomeClassChild()..addUnconsumedProps(props, meta))(), + (SomeClassChild()..modifyProps(props.addPropsToForward(exclude: {ParentOnlyPropsMixin})))(), ) ); } @@ -58,4 +56,4 @@ class SomeClassChildComponent extends UiComponent2 { ) ); } -} \ No newline at end of file +} diff --git a/lib/src/component_declaration/builder_helpers.dart b/lib/src/component_declaration/builder_helpers.dart index cb35412e7..b14f2f90b 100644 --- a/lib/src/component_declaration/builder_helpers.dart +++ b/lib/src/component_declaration/builder_helpers.dart @@ -167,10 +167,10 @@ extension PropsToForward on T { /// } /// class FooProps = UiProps with BarProps, FooPropsMixin; /// - /// UiFactory Foo = uiFunction((props) { + /// UiFactory Foo = uiFunction((props) { /// return (Bar() /// // Filter out props declared in FooPropsMixin when forwarding to Bar. - /// ..addAll(props.getPropsToForward({FooPropsMixin})) + /// ..addAll(props.getPropsToForward(exclude: {FooPropsMixin})) /// )(); /// }); /// ``` @@ -185,20 +185,39 @@ extension PropsToForward on T { /// A utility function to be used with `modifyProps` to add props excluding the keys found in [exclude]. /// /// [exclude] should be a `Set` of PropsMixin `Type`s. - /// If [exclude] is not set it defaults to using the current instances Type. + /// If [exclude] is not set, it defaults to using the current instance's Type. /// /// __Example:__ /// + /// Component with a single props mixin: /// ```dart - /// // within a functional component: `uiFunction` - /// // filter out the current components props when forwarding to Bar. - /// return (Bar()..modifyProps(props.addPropsToForward()))(); + /// mixin FooPropsMixin on UiProps { + /// String foo; + /// } + /// + /// UiFactory Foo = uiFunction((props) { + /// return (Bar() + /// // Filter out props declared in FooPropsMixin + /// // (used as the default for `exclude` since that's what `props` is statically typed as) + /// // when forwarding to Bar. + /// ..modifyProps(props.addPropsToForward()) + /// )(); + /// }); /// ``` - /// OR + /// + /// Component with a more than one props mixin: /// ```dart - /// // within a functional component that has multiple mixins on a Props class: `uiFunction` - /// // filter out the Components props when forwarding to Bar. - /// return (Bar()..modifyProps(props.addPropsToForward(exclude: {FooPropsMixin}))(); + /// mixin FooPropsMixin on UiProps { + /// String foo; + /// } + /// class FooProps = UiProps with BarProps, FooPropsMixin; + /// + /// UiFactory Foo = uiFunction((props) { + /// return (Bar() + /// // Filter out props declared in FooPropsMixin when forwarding to Bar. + /// ..modifyProps(props.addPropsToForward(exclude: {FooPropsMixin})) + /// )(); + /// }); /// ``` /// /// To only add DOM props, use the [domOnly] named argument. From eef837c333bcb0113d737077cd5c0980dcb62fe8 Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Thu, 10 Aug 2023 13:35:33 -0700 Subject: [PATCH 28/32] update migration guide? --- doc/new_boilerplate_migration.md | 123 +++++++++++++++++++++++++------ 1 file changed, 99 insertions(+), 24 deletions(-) diff --git a/doc/new_boilerplate_migration.md b/doc/new_boilerplate_migration.md index c1face774..51eaf94bc 100644 --- a/doc/new_boilerplate_migration.md +++ b/doc/new_boilerplate_migration.md @@ -781,42 +781,117 @@ UiFactory Foo = uiFunction( ); ``` -#### With Consumed Props +#### With Prop Forwarding (fka Consumed Props) -Because functional components have no instance that track consumed props, the syntax for passing unconsumed -props changes within functional components. +Because functional components have no instance that track consumed props, the syntax for forwarding props +changes within functional components. -`UiProps` exposes a field `staticMeta` that can be used to generate an iterable containing props meta for specific mixins. -This is similar to accessing `propsMeta` within a class based component. Using the iterable returned from `staticMeta`'s -APIs (such as `forMixins`), we can generate unconsumed props and pass them to a child component. +`UiProps` exposes 2 APIs `getPropsToForward` & `addPropsToForward` that can be used to forward props +that have not been used to a child component. -This is done like so: +##### getPropsToForward +`getPropsToForward` will return a `Map` of props removing the props found in the `exclude` argument. +`exclude` is optional and will default to a `Set` with the type that `props` is statically typed as, +this only works with `mixin .. on UiProps` types. If your function component uses a Props `class` then +you must include an `exclude` argument. + +Component with a single props mixin: ```dart mixin FooPropsMixin on UiProps { - String passedProp; + String foo; } -mixin BarPropsMixin on UiProps { - String nonPassedProp; +UiFactory Foo = uiFunction((props) { + return (Bar() + // Filter out props declared in FooPropsMixin + // (used as the default for `exclude` since that's what `props` is statically typed as) + // when forwarding to Bar. + ..addAll(props.getPropsToForward()) + )(); +}); +``` + +Component with a more than one props mixin: +```dart +mixin FooPropsMixin on UiProps { + String foo; } -class FooBarProps = UiProps with BarPropsMixin, FooPropsMixin; +class FooProps = UiProps with BarProps, FooPropsMixin; -UiFactory FooBar = uiFunction( - (props) { - final consumedProps = props.staticMeta.forMixins({BarPropsMixin}); +UiFactory Foo = uiFunction((props) { + return (Bar() + // Filter out props declared in FooPropsMixin when forwarding to Bar. + ..addAll(props.getPropsToForward(exclude: {FooPropsMixin})) + )(); +}); +``` - return (Foo()..addUnconsumedProps(props, consumedProps))(); - }, - _$FooBarConfig, // ignore: undefined_identifier -); +`domOnly` - to forward DOM props only: +```dart +mixin FooPropsMixin on UiProps { + String foo; +} -UiFactory Foo = uiFunction( - (props) { - return 'foo: ${props.passedProp}'; - }, - _$FooConfig, // ignore: undefined_identifier -); +UiFactory Foo = uiFunction((props) { + return (Dom.div() + // Forward only DOM based props & Filter out props declared in FooPropsMixin + // (used as the default for `exclude` since that's what `props` is statically typed as) + // when forwarding to Bar. + ..addAll(props.getPropsToForward(domOnly: true)) + )(); +}); +``` + +##### addPropsToForward +`addPropsToForward` has the same function signature as `getPropsToForward` but is meant to be used with the `UiProps` method `modifyProps`. + +Component with a single props mixin: +```dart +mixin FooPropsMixin on UiProps { + String foo; +} + +UiFactory Foo = uiFunction((props) { + return (Bar() + // Filter out props declared in FooPropsMixin + // (used as the default for `exclude` since that's what `props` is statically typed as) + // when forwarding to Bar. + ..modifyProps(props.addPropsToForward()) + )(); +}); +``` + +Component with a more than one props mixin: +```dart +mixin FooPropsMixin on UiProps { + String foo; +} + +class FooProps = UiProps with BarProps, FooPropsMixin; + +UiFactory Foo = uiFunction((props) { + return (Bar() + // Filter out props declared in FooPropsMixin when forwarding to Bar. + ..modifyProps(props.addPropsToForward(exclude: {FooPropsMixin})) + )(); +}); +``` + +`domOnly` - to forward DOM props only: +```dart +mixin FooPropsMixin on UiProps { + String foo; +} + +UiFactory Foo = uiFunction((props) { + return (Dom.div() + // Forward only DOM based props & Filter out props declared in FooPropsMixin + // (used as the default for `exclude` since that's what `props` is statically typed as) + // when forwarding to Bar. + ..modifyProps(props.addPropsToForward(domOnly: true)) + )(); +}); ``` #### With UiProps From 81010e5ab5ae655a537dbb26d3c7c79f1cb59ee1 Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Thu, 10 Aug 2023 14:04:22 -0700 Subject: [PATCH 29/32] another doc update --- doc/props_mixin_component_composition.md | 25 +++++++++++------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/doc/props_mixin_component_composition.md b/doc/props_mixin_component_composition.md index dbd671674..3cafd056f 100644 --- a/doc/props_mixin_component_composition.md +++ b/doc/props_mixin_component_composition.md @@ -2,9 +2,9 @@ In our core `UiProps` documentation, the pattern of [composing multiple props mixins into a single component props API](../README.md#with-other-mixins) is introduced. -This example builds on that, showing a lightweight example a common use-case for such composition. +This example builds on that, showing a lightweight example a common use-case for such composition. -We'll show three components +We'll show three components 1. A `Foo` component that has its own props API - and default rendering behavior when rendered standalone. 1. A `FooBar` component that has its own props API, in addition to the `Foo` props API. This allows consumers to set props declared in `FooPropsMixin`, which will be forwarded to the `Foo` component it renders. @@ -16,7 +16,7 @@ import 'package:over_react/over_react.dart'; part 'foo.over_react.g.dart'; -UiFactory Foo = +UiFactory Foo = castUiFactory(_$Foo); // ignore: undefined_identifier mixin FooPropsMixin on UiProps { @@ -35,7 +35,7 @@ class FooComponent extends UiComponent2 { ..modifyProps(addUnconsumedDomProps) ..className = (forwardingClassNameBuilder()..add('foo')) )( - 'Qux: ', + 'Qux: ', props.qux.map((n) => n), props.children, ); @@ -58,7 +58,7 @@ import 'foo.dart'; part 'foo_bar.over_react.g.dart'; -UiFactory FooBar = +UiFactory FooBar = castUiFactory(_$FooBar); // ignore: undefined_identifier mixin BarPropsMixin on UiProps { @@ -69,7 +69,7 @@ mixin BarPropsMixin on UiProps { class FooBarProps = UiProps with BarPropsMixin, FooPropsMixin; class FooBarComponent extends UiComponent2 { - // Only consume the props found within BarPropsMixin, so that any prop values + // Only consume the props found within BarPropsMixin, so that any prop values // found in FooPropsMixin get forwarded to the child Foo component via `addUnconsumedProps`. @override get consumedProps => propsMeta.forMixins({BarPropsMixin}); @@ -122,7 +122,7 @@ Produces the following HTML:
Qux: 234
- Bizzles: + Bizzles:
  1. a
  2. b
  3. @@ -150,12 +150,9 @@ class FooBazProps = UiProps with BarPropsMixin, FooPropsMixin; UiFactory FooBaz = uiFunction( (props) { - // Only consume the props found within BarPropsMixin, so that any prop values - // found in FooPropsMixin get forwarded to the child Foo component via `addUnconsumedProps`. - final consumedProps = props.staticMeta.forMixins({BarPropsMixin}); - return (Foo() - ..addUnconsumedProps(props, consumedProps) + // Only forward the props not belonging to BarPropsMixin to the child Foo component. + ..addAll(props.getPropsToForward(exclude: {BarPropsMixin})) ..className = (forwardingClassNameBuilder()..add('foo__baz')) )( (Dom.div()..className = 'foo__baz__bizzles')( @@ -165,7 +162,7 @@ UiFactory FooBaz = uiFunction( ), ), ); - + ReactElement _renderBizzleItem(String bizzle) { return (Dom.li()..key = bizzle)(bizzle); } @@ -201,7 +198,7 @@ Produces the following HTML:
    Qux: 234
    - Bizzles: + Bizzles:
    1. a
    2. b
    3. From 6bfad61da785e79e292cc376cf2a2acef67cf542 Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Fri, 11 Aug 2023 11:13:39 -0700 Subject: [PATCH 30/32] address feedback --- lib/src/component_declaration/builder_helpers.dart | 1 - .../new_boilerplate/function_component_test.dart | 2 -- 2 files changed, 3 deletions(-) diff --git a/lib/src/component_declaration/builder_helpers.dart b/lib/src/component_declaration/builder_helpers.dart index b14f2f90b..82cd5066f 100644 --- a/lib/src/component_declaration/builder_helpers.dart +++ b/lib/src/component_declaration/builder_helpers.dart @@ -234,7 +234,6 @@ extension PropsToForward on T { try { consumedProps = staticMeta.forMixins(exclude ?? {T}).toList(); } catch(_) { - // If [domOnly] is `true`, it is alright for the meta lookup to fail, otherwise throw the error. assert(exclude != null, ArgumentError('Could not find props meta for type $T.' ' If this is not a props mixin, you need to specify its mixins as the second argument. For example:' '\n ..addAll(props.getPropsToForward(exclude: {${T}Mixin})').message); diff --git a/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart index 33ab35ca9..ac2dd4993 100644 --- a/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart +++ b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart @@ -14,7 +14,6 @@ @TestOn('browser') -import 'dart:developer'; import 'dart:html'; import 'package:over_react/over_react.dart'; @@ -411,7 +410,6 @@ testPropsToForward({UiFactory factory, bool modifyProps = false}) { }); test('which throws an error when not providing an exclude argument and the props class is NOT a mixin and `domOnly` is NOT `true`', () { - debugger(); expect(() => _propsToForward(exclude: null, props: initialProps, factory: factory, modifyProps: modifyProps), throwsA(isA())); }, tags: 'ddc'); From cfd533e215b6bbc2708fd39406111f2ae3defacf Mon Sep 17 00:00:00 2001 From: Keal Jones <41018730+kealjones-wk@users.noreply.github.com> Date: Mon, 14 Aug 2023 10:32:02 -0700 Subject: [PATCH 31/32] Update test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart Co-authored-by: Greg Littlefield --- .../new_boilerplate/function_component_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart index ac2dd4993..4ef96807a 100644 --- a/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart +++ b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart @@ -411,7 +411,7 @@ testPropsToForward({UiFactory factory, bool modifyProps = false}) { test('which throws an error when not providing an exclude argument and the props class is NOT a mixin and `domOnly` is NOT `true`', () { expect(() => _propsToForward(exclude: null, props: initialProps, factory: factory, modifyProps: modifyProps), - throwsA(isA())); + throwsA(isA().having('toString value', (e) => e.toString(), contains('If this is not a props mixin, you need to specify its mixins as the second argument'))); }, tags: 'ddc'); }); } From 3a82784c4e8bab5a4870fa2ffe8eb9e7c5785bd3 Mon Sep 17 00:00:00 2001 From: kealjones-wk <41018730+kealjones-wk@users.noreply.github.com> Date: Mon, 14 Aug 2023 10:35:00 -0700 Subject: [PATCH 32/32] Fix improper `having` use from suggestion - gosh darnit greg --- .../new_boilerplate/function_component_test.dart | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart index 4ef96807a..de08bc4c6 100644 --- a/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart +++ b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart @@ -411,7 +411,15 @@ testPropsToForward({UiFactory factory, bool modifyProps = false}) { test('which throws an error when not providing an exclude argument and the props class is NOT a mixin and `domOnly` is NOT `true`', () { expect(() => _propsToForward(exclude: null, props: initialProps, factory: factory, modifyProps: modifyProps), - throwsA(isA().having('toString value', (e) => e.toString(), contains('If this is not a props mixin, you need to specify its mixins as the second argument'))); + throwsA( + isA() + .having( + (e) => e.toString(), + 'toString value', + contains('If this is not a props mixin, you need to specify its mixins as the second argument') + ), + ), + ); }, tags: 'ddc'); }); }