diff --git a/lib/src/component_declaration/component_base.dart b/lib/src/component_declaration/component_base.dart index 2784af621..df12671ef 100644 --- a/lib/src/component_declaration/component_base.dart +++ b/lib/src/component_declaration/component_base.dart @@ -91,7 +91,7 @@ typedef TProps BuilderOnlyUiFactory(); /// /// Extends [react.Component]. /// -/// Related: [UiStatefulComponent] +/// For strongly-typed state, mix in [UiStatefulMixin] or extend from [UiStatefulComponent]. abstract class UiComponent extends react.Component { /// The props for the non-forwarding props defined in this component. Iterable get consumedProps => null; @@ -193,16 +193,37 @@ abstract class UiComponent extends react.Component { // END Typed props helpers // ---------------------------------------------------------------------- // ---------------------------------------------------------------------- + + + // ---------------------------------------------------------------------- + // ---------------------------------------------------------------------- + // BEGIN Typed state helpers + // + + /// The state Map that will be used to create the typed [state] object. + /// + /// Defined in [UiComponent] and not [UiStatefulMixin] to work around dart2js restriction + /// on super calls in mixins . + Map get unwrappedState => super.state; + set unwrappedState(Map value) => super.state = value; + + // + // END Typed state helpers + // ---------------------------------------------------------------------- + // ---------------------------------------------------------------------- } -/// The basis for a stateful over_react component. +/// A base class for a stateful over_react component. /// /// Includes support for strongly-typed props and state and utilities for prop and CSS classname forwarding. -/// -/// Extends [react.Component]. -/// -/// Related: [UiComponent] -abstract class UiStatefulComponent extends UiComponent { +abstract class UiStatefulComponent + extends UiComponent with UiStatefulMixin {} + +/// A mixin that adds support for strongly-typed state to a [UiComponent]. +abstract class UiStatefulMixin + // Implement react.Component instead of UiComponent so we don't run into https://github.com/dart-lang/sdk/issues/14729 + // and have to pass through UiComponent's generic parameter. + implements react.Component { // ---------------------------------------------------------------------- // ---------------------------------------------------------------------- // BEGIN Typed state helpers @@ -225,11 +246,11 @@ abstract class UiStatefulComponent super.state = value; + set state(Map value) => unwrappedState = value; /// The state Map that will be used to create the typed [state] object. - Map get unwrappedState => super.state; - set unwrappedState(Map value) => super.state = value; + Map get unwrappedState; + set unwrappedState(Map value); /// Returns a typed state object backed by the specified [stateMap]. /// diff --git a/lib/src/component_declaration/flux_component.dart b/lib/src/component_declaration/flux_component.dart index 0589379b0..18c2b97c4 100644 --- a/lib/src/component_declaration/flux_component.dart +++ b/lib/src/component_declaration/flux_component.dart @@ -49,32 +49,13 @@ abstract class FluxUiProps extends UiProps { /// the resulting component. /// /// Use with the over_react transformer via the `@Component()` ([annotations.Component]) annotation. -abstract class FluxUiComponent extends UiComponent - with _FluxComponentMixin, BatchedRedraws {} - -/// Builds on top of [UiStatefulComponent], adding `w_flux` integration, much like the [FluxComponent] in w_flux. -/// -/// * Flux components are responsible for rendering application views and turning -/// user interactions and events into [Action]s. -/// * Flux components can use data from one or many [Store] instances to define -/// the resulting component. -/// -/// Use with the over_react transformer via the `@Component()` ([annotations.Component]) annotation. -abstract class FluxUiStatefulComponent - extends UiStatefulComponent - with _FluxComponentMixin, BatchedRedraws {} - -/// Helper mixin to keep [FluxUiComponent] and [FluxUiStatefulComponent] clean/DRY. -/// -/// Private so it will only get used in this file, since having lifecycle methods in a mixin is risky. -abstract class _FluxComponentMixin implements BatchedRedraws { - TProps get props; - +abstract class FluxUiComponent extends UiComponent with BatchedRedraws { /// List of store subscriptions created when the component mounts. /// /// These subscriptions are canceled when the component is unmounted. List _subscriptions = []; + @override void componentWillMount() { /// Subscribe to all applicable stores. /// @@ -92,6 +73,7 @@ abstract class _FluxComponentMixin implements Batche }); } + @override void componentWillUnmount() { // Ensure that unmounted components don't batch render shouldBatchRedraw = false; @@ -147,3 +129,15 @@ abstract class _FluxComponentMixin implements Batche _subscriptions.add(subscription); } } + +/// A [FluxUiComponent] subclass with typed state added via [UiStatefulMixin], for convenience. +/// +/// * Flux components are responsible for rendering application views and turning +/// user interactions and events into [Action]s. +/// * Flux components can use data from one or many [Store] instances to define +/// the resulting component. +/// +/// Use with the over_react transformer via the `@Component()` ([annotations.Component]) annotation. +abstract class FluxUiStatefulComponent + extends FluxUiComponent with UiStatefulMixin + implements UiStatefulComponent {} diff --git a/lib/src/component_declaration/transformer_helpers.dart b/lib/src/component_declaration/transformer_helpers.dart index 2973c62ad..5c01709a5 100644 --- a/lib/src/component_declaration/transformer_helpers.dart +++ b/lib/src/component_declaration/transformer_helpers.dart @@ -89,6 +89,8 @@ class GeneratedClass { /// See: [component_base.UiComponent] /// /// Use with the over_react transformer via the `@Component()` ([annotations.Component]) annotation. +/// +/// For strongly-typed state, mix in [component_base.UiStatefulMixin] or extend from [UiStatefulComponent]. abstract class UiComponent extends component_base.UiComponent with GeneratedClass { /// This class should not be instantiated directly, and throws an error to indicate this. UiComponent() { @@ -118,34 +120,19 @@ abstract class UiComponent extends component_base.UiComp /// /// Use with the over_react transformer via the `@Component()` ([annotations.Component]) annotation. abstract class UiStatefulComponent - extends component_base.UiStatefulComponent with GeneratedClass { + extends UiComponent with component_base.UiStatefulMixin + implements component_base.UiStatefulComponent { /// This class should not be instantiated directly, and throws an error to indicate this. UiStatefulComponent() { _throwIfNotGenerated(); } - /// The default consumed prop keys, taken from the keys generated in the associated @[annotations.Props] class. - @toBeGenerated - Iterable get $defaultConsumedProps => throw new UngeneratedError(member: #$defaultConsumedProps); - - /// The keys for the non-forwarding props defined in this component. - /// - /// For generated components, this defaults to the keys generated in the associated @[annotations.Props] class - /// if this getter is not overridden. - @override - Iterable get consumedProps => $defaultConsumedProps; - - /// Returns a typed props object backed by the specified [propsMap]. - /// - /// Required to properly instantiate the generic [TProps] class. - @override - @toBeGenerated - TProps typedPropsFactory(Map propsMap) => throw new UngeneratedError(member: #typedPropsFactory); - /// Returns a typed state object backed by the specified [stateMap]. /// /// Required to properly instantiate the generic [TState] class. - @override @toBeGenerated TState typedStateFactory(Map stateMap) => throw new UngeneratedError(member: #typedStateFactory); + @override + @toBeGenerated + TState typedStateFactory(Map stateMap) => throw new UngeneratedError(member: #typedStateFactory); } /// A [dart.collection.MapView]-like class with strongly-typed getters/setters for React props that diff --git a/smithy.yaml b/smithy.yaml index 3b5f060bf..ac9b6981e 100644 --- a/smithy.yaml +++ b/smithy.yaml @@ -6,6 +6,7 @@ runner_image: drydock-prod.workiva.org/workiva/smithy-runner-dart:74173 script: - pub get + - ./tool/smithy_dart2js_tests.sh artifacts: build: diff --git a/test/over_react/component/dom_components_test.dart b/test/over_react/component/dom_components_test.dart index 2061cb09e..03793f301 100644 --- a/test/over_react/component/dom_components_test.dart +++ b/test/over_react/component/dom_components_test.dart @@ -17,7 +17,7 @@ library dom_components_test; // Tell dart2js that this library only needs to reflect the specified types. // This speeds up compilation and makes JS output much smaller. @MirrorsUsed(targets: const [ - 'over_react.Dom' + 'over_react.dom_components.Dom' ]) import 'dart:mirrors'; diff --git a/tool/dev.dart b/tool/dev.dart index e3f9d88f0..83740a084 100644 --- a/tool/dev.dart +++ b/tool/dev.dart @@ -30,7 +30,11 @@ main(List args) async { ..pubServe = true ..platforms = [ 'vm', - 'content-shell' + 'content-shell', + // Can't run tests in dart2js on Travis since the suite takes too long to load and times out. + // Run on Smithy instead. + // See https://github.com/Workiva/over_react/issues/36 + // 'chrome', ] // Prevent test load timeouts on Smithy. ..concurrency = 1 diff --git a/tool/smithy_dart2js_tests.sh b/tool/smithy_dart2js_tests.sh new file mode 100755 index 000000000..3b613e109 --- /dev/null +++ b/tool/smithy_dart2js_tests.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +# Can't run tests in dart2js on Travis since the suite takes too long to load and times out. +# Run on Smithy instead. +# See https://github.com/Workiva/over_react/issues/36 + +set -e + +# Trick the test package into using Chromium instead of Chrome +TMP_BIN=$(mktemp -d) +ln -s "$(which chromium-browser)" "$TMP_BIN/google-chrome" +export PATH="$PATH:$TMP_BIN" + +# Run the tests +DART_FLAGS=--checked xvfb-run -s '-screen 0 1024x768x24' pub run dart_dev test -p chrome + +# Be sneaky and clean up our tricks +rm -rf "$TMP_BIN"