diff --git a/.travis.yml b/.travis.yml index af3e3868f..e8b2bf62f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ script: - git diff --exit-code - pub run dart_dev analyze # TODO: Remove ignore on analyzer once the wdesk ecosystem is all on a Dart 2 compat major version and we no longer need a "pinned" dep - - pub run dependency_validator --no-fatal-pins -i coverage,build_web_compilers,analyzer,build_runner,built_value_generator + - pub run dependency_validator --no-fatal-pins -i analyzer,build_runner,build_web_compilers,built_value_generator # run tests on ddc output - pub run build_runner test --delete-conflicting-outputs -- -P dartdevc # run tests on dart2js output diff --git a/CHANGELOG.md b/CHANGELOG.md index 0935eed83..2c90a554a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # OverReact Changelog +## 2.4.4 + +> Complete `2.4.4` Changsets: +> +> - [Dart 2](https://github.com/Workiva/over_react/compare/2.4.3+dart2...2.4.4+dart2) +> - Dart 1 (no changes) + +* [#310] Upgrade to dart 2.4, analyzer 0.36.x, and build_web_compilers 2.x + ## 2.4.3 > Complete `2.4.3` Changsets: diff --git a/Dockerfile b/Dockerfile index d597d673f..a61ab10e5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM google/dart:2.1.0 +FROM google/dart:2.4 # Expose env vars for git ssh access ARG GIT_SSH_KEY diff --git a/lib/over_react.dart b/lib/over_react.dart index f8e6b04ad..7dadd36a6 100644 --- a/lib/over_react.dart +++ b/lib/over_react.dart @@ -40,6 +40,7 @@ export 'src/component/callback_typedefs.dart'; export 'src/component/error_boundary.dart'; export 'src/component/error_boundary_mixins.dart'; export 'src/component/dom_components.dart'; +export 'src/component/fragment_component.dart'; export 'src/component/dummy_component.dart'; export 'src/component/prop_mixins.dart'; export 'src/component/prop_typedefs.dart'; diff --git a/lib/src/component/fragment_component.dart b/lib/src/component/fragment_component.dart new file mode 100644 index 000000000..c1f374649 --- /dev/null +++ b/lib/src/component/fragment_component.dart @@ -0,0 +1,44 @@ +// Copyright 2019 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/src/component_declaration/component_base.dart' + as component_base; +import 'package:over_react/src/component_declaration/builder_helpers.dart' + as builder_helpers; +import 'package:react/react_client.dart'; +import 'package:react/src/react_client/js_backed_map.dart'; +import 'package:react/react.dart' as react; + +class FragmentProps extends component_base.UiProps + with builder_helpers.GeneratedClass + implements builder_helpers.UiProps { + // Initialize to a JsBackedMap so that copying can be optimized + // when converting props during ReactElement creation. + FragmentProps([Map props]) : this.props = props ?? new JsBackedMap(); + + @override + ReactJsComponentFactoryProxy componentFactory = react.Fragment; + + @override + final Map props; + + @override + String get propKeyNamespace => ''; +} + +/// Fragment component that allows the wrapping of children without the necessity of using +/// an element that adds an additional layer to the DOM (div, span, etc). +/// +/// See: +FragmentProps Fragment() => new FragmentProps(); diff --git a/pubspec.yaml b/pubspec.yaml index 60476b4f0..1f7bf0dca 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: over_react -version: 2.4.3+dart2 +version: 2.4.4+dart2 description: A library for building statically-typed React UI components using Dart. homepage: https://github.com/Workiva/over_react/ authors: @@ -9,9 +9,7 @@ environment: sdk: '>=2.1.0 <3.0.0' dependencies: - # analyzer >= 0.33.0 breaks the builder, and analyzer < 0.32.4 is Dart 2 incompatible. Wdesk currently resolves to - # 0.30.x, so we need the lower bound - analyzer: ^0.35.0 + analyzer: '>=0.35.0 <0.37.0' build: ^1.0.0 built_redux: ^7.4.2 built_value: '>=5.4.4 <7.0.0' @@ -28,12 +26,11 @@ dependencies: platform_detect: ^1.3.4 quiver: ">=0.25.0 <1.0.0" dev_dependencies: - build_resolvers: ^1.0.3 - build_runner: ^1.3.1 - build_test: ^0.10.6 - build_web_compilers: ^1.2.0 + build_resolvers: ^1.0.5 + build_runner: ^1.5.2 + build_test: ^0.10.8 + build_web_compilers: ^2.1.1 built_value_generator: ^6.0.0 - coverage: ">=0.10.0 <0.12.4" dart2_constant: ^1.0.0 dart_dev: ^2.0.0 dependency_validator: ^1.2.2 @@ -41,7 +38,6 @@ dev_dependencies: over_react_test: ^2.5.0 test: ^1.0.0 - dependency_overrides: react: git: diff --git a/test/over_react/component/fixtures/dummy_component.dart b/test/over_react/component/fixtures/dummy_component.dart new file mode 100644 index 000000000..7c87d7315 --- /dev/null +++ b/test/over_react/component/fixtures/dummy_component.dart @@ -0,0 +1,47 @@ +// Copyright 2019 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'; + +// ignore: uri_has_not_been_generated +part 'dummy_component.over_react.g.dart'; + +@Factory() +// ignore: undefined_identifier +UiFactory Dummy = _$Dummy; + +@Props() +class _$DummyProps extends UiProps { + Function onComponentDidMount; +} + +// AF-3369 This will be removed once the transition to Dart 2 is complete. +// ignore: mixin_of_non_class, undefined_class +class DummyProps extends _$DummyProps with _$DummyPropsAccessorsMixin { + // ignore: undefined_identifier, undefined_class, const_initialized_with_non_constant_value + static const PropsMeta meta = _$metaForDummyProps; +} + +@Component2() +class DummyComponent extends UiComponent2 { + @override + void componentDidMount() { + props.onComponentDidMount(); + } + + @override + render() { + return (Dom.button()..addTestId('DummyButton'))('oh hai'); + } +} diff --git a/test/over_react/component/fixtures/dummy_component.over_react.g.dart b/test/over_react/component/fixtures/dummy_component.over_react.g.dart new file mode 100644 index 000000000..c99c8ded2 --- /dev/null +++ b/test/over_react/component/fixtures/dummy_component.over_react.g.dart @@ -0,0 +1,151 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'dummy_component.dart'; + +// ************************************************************************** +// OverReactBuilder (package:over_react/src/builder.dart) +// ************************************************************************** + +// React component factory implementation. +// +// Registers component implementation and links type meta to builder factory. +final $DummyComponentFactory = registerComponent2( + () => new _$DummyComponent(), + builderFactory: Dummy, + componentClass: DummyComponent, + isWrapper: false, + parentType: null, + displayName: 'Dummy', +); + +abstract class _$DummyPropsAccessorsMixin implements _$DummyProps { + @override + Map get props; + + /// + @override + Function get onComponentDidMount => + props[_$key__onComponentDidMount___$DummyProps] ?? + null; // Add ` ?? null` to workaround DDC bug: ; + /// + @override + set onComponentDidMount(Function value) => + props[_$key__onComponentDidMount___$DummyProps] = value; + /* GENERATED CONSTANTS */ + static const PropDescriptor _$prop__onComponentDidMount___$DummyProps = + const PropDescriptor(_$key__onComponentDidMount___$DummyProps); + static const String _$key__onComponentDidMount___$DummyProps = + 'DummyProps.onComponentDidMount'; + + static const List $props = const [ + _$prop__onComponentDidMount___$DummyProps + ]; + static const List $propKeys = const [ + _$key__onComponentDidMount___$DummyProps + ]; +} + +const PropsMeta _$metaForDummyProps = const PropsMeta( + fields: _$DummyPropsAccessorsMixin.$props, + keys: _$DummyPropsAccessorsMixin.$propKeys, +); + +_$$DummyProps _$Dummy([Map backingProps]) => backingProps == null + ? new _$$DummyProps$JsMap(new JsBackedMap()) + : new _$$DummyProps(backingProps); + +// Concrete props implementation. +// +// Implements constructor and backing map, and links up to generated component factory. +abstract class _$$DummyProps extends _$DummyProps + with _$DummyPropsAccessorsMixin + implements DummyProps { + _$$DummyProps._(); + + factory _$$DummyProps(Map backingMap) { + if (backingMap is JsBackedMap) { + return new _$$DummyProps$JsMap(backingMap); + } else { + return new _$$DummyProps$PlainMap(backingMap); + } + } + + /// Let [UiProps] internals know that this class has been generated. + @override + bool get $isClassGenerated => true; + + /// The [ReactComponentFactory] associated with the component built by this class. + @override + ReactComponentFactoryProxy get componentFactory => $DummyComponentFactory; + + /// The default namespace for the prop getters/setters generated for this class. + @override + String get propKeyNamespace => 'DummyProps.'; +} + +// Concrete props implementation that can be backed by any [Map]. +class _$$DummyProps$PlainMap extends _$$DummyProps { + // This initializer of `_props` to an empty map, as well as the reassignment + // of `_props` in the constructor body is necessary to work around a DDC bug: https://github.com/dart-lang/sdk/issues/36217 + _$$DummyProps$PlainMap(Map backingMap) + : this._props = {}, + super._() { + this._props = backingMap ?? {}; + } + + /// The backing props map proxied by this class. + @override + Map get props => _props; + Map _props; +} + +// Concrete props implementation that can only be backed by [JsMap], +// allowing dart2js to compile more optimal code for key-value pair reads/writes. +class _$$DummyProps$JsMap extends _$$DummyProps { + // This initializer of `_props` to an empty map, as well as the reassignment + // of `_props` in the constructor body is necessary to work around a DDC bug: https://github.com/dart-lang/sdk/issues/36217 + _$$DummyProps$JsMap(JsBackedMap backingMap) + : this._props = new JsBackedMap(), + super._() { + this._props = backingMap ?? new JsBackedMap(); + } + + /// The backing props map proxied by this class. + @override + JsBackedMap get props => _props; + JsBackedMap _props; +} + +// Concrete component implementation mixin. +// +// Implements typed props/state factories, defaults `consumedPropKeys` to the keys +// generated for the associated props class. +class _$DummyComponent extends DummyComponent { + _$$DummyProps$JsMap _cachedTypedProps; + + @override + _$$DummyProps$JsMap get props => _cachedTypedProps; + + @override + set props(Map value) { + super.props = value; + _cachedTypedProps = typedPropsFactoryJs(value); + } + + @override + _$$DummyProps$JsMap typedPropsFactoryJs(JsBackedMap backingMap) => + new _$$DummyProps$JsMap(backingMap); + + @override + _$$DummyProps typedPropsFactory(Map backingMap) => + new _$$DummyProps(backingMap); + + /// Let [UiComponent] internals know that this class has been generated. + @override + bool get $isClassGenerated => true; + + /// The default consumed props, taken from _$DummyProps. + /// Used in [UiProps.consumedProps] if [consumedProps] is not overridden. + @override + final List $defaultConsumedProps = const [_$metaForDummyProps]; +} diff --git a/test/over_react/component/fragment_component_test.dart b/test/over_react/component/fragment_component_test.dart new file mode 100644 index 000000000..aea698d30 --- /dev/null +++ b/test/over_react/component/fragment_component_test.dart @@ -0,0 +1,70 @@ +// Copyright 2019 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. + +library fragment_component_test; + +import 'package:over_react/over_react.dart'; +import 'package:over_react_test/over_react_test.dart'; +import 'package:test/test.dart'; + +import 'fixtures/dummy_component.dart'; + +void main() { + group('Fragment', () { + test('renders only its children', () { + var wrappingDivRef; + + renderAttachedToDocument( + (Dom.div() + ..ref = (ref) { + wrappingDivRef = ref; + })(Fragment()( + Dom.div()(), + Dom.div()(), + Dom.div()(), + Dom.div()(), + )), + ); + + expect(wrappingDivRef.children, hasLength(4)); + }); + + test('passes the key properly onto the fragment', () { + var callCount = 0; + var jacket = mount( + (Fragment()..key = 1)( + (Dummy() + ..onComponentDidMount = () { + callCount++; + })(), + ), + ); + + expect(callCount, 1); + + jacket.rerender( + (Fragment()..key = 2)( + (Dummy() + ..onComponentDidMount = () { + callCount++; + })(), + ), + ); + + expect(callCount, 2, + reason: + 'Dummy should have been remounted as a result of Fragment key changing'); + }); + }); +} diff --git a/test/over_react_component_test.dart b/test/over_react_component_test.dart index c3d2f4d5f..3997b67f5 100644 --- a/test/over_react_component_test.dart +++ b/test/over_react_component_test.dart @@ -29,6 +29,7 @@ import 'over_react/component/error_boundary_mixin_test.dart' as error_boundary_m import 'over_react/component/error_boundary_test.dart' as error_boundary_test; import 'over_react/component/prop_mixins_test.dart' as prop_mixins_test; import 'over_react/component/resize_sensor_test.dart' as resize_sensor_test; +import 'over_react/component/fragment_component_test.dart' as fragment_component_test; void main() { @@ -42,4 +43,5 @@ void main() { dom_components_test.main(); prop_mixins_test.main(); resize_sensor_test.main(); + fragment_component_test.main(); } diff --git a/tool/generate_coverage.sh b/tool/generate_coverage.sh deleted file mode 100755 index 0cf7e4411..000000000 --- a/tool/generate_coverage.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash - -# DART_FLAGS has to be set to `--checked` because dart_dev does not run tests in checked mode. -DART_FLAGS=--checked pub run dart_dev coverage --no-html --no-open diff --git a/web/component2/src/demos/progress/progress-basic.dart b/web/component2/src/demos/progress/progress-basic.dart index 62d4d81d1..63bfe7525 100644 --- a/web/component2/src/demos/progress/progress-basic.dart +++ b/web/component2/src/demos/progress/progress-basic.dart @@ -2,7 +2,7 @@ import 'package:over_react/over_react.dart'; import '../../demo_components.dart'; -ReactElement progressBasicDemo() => Dom.div()( +ReactElement progressBasicDemo() => Fragment()( (Progress() ..showCaption = true ..captionProps = (domProps()..className = 'text-xs-center') diff --git a/web/component2/src/demos/tag/tag-basic.dart b/web/component2/src/demos/tag/tag-basic.dart index f39e8986a..70c8289de 100644 --- a/web/component2/src/demos/tag/tag-basic.dart +++ b/web/component2/src/demos/tag/tag-basic.dart @@ -2,7 +2,7 @@ import 'package:over_react/over_react.dart'; import '../../demo_components.dart'; -ReactElement tagBasicDemo() => Dom.div()( +ReactElement tagBasicDemo() => Fragment()( Dom.h1()('Example heading ', Tag()('New')), Dom.h2()('Example heading ', Tag()('New')), Dom.h3()('Example heading ', Tag()('New')),