diff --git a/playground/frontend/lib/modules/examples/components/example_list/example_list.dart b/playground/frontend/lib/modules/examples/components/example_list/example_list.dart index ca5f2177602a..72c517270745 100644 --- a/playground/frontend/lib/modules/examples/components/example_list/example_list.dart +++ b/playground/frontend/lib/modules/examples/components/example_list/example_list.dart @@ -18,34 +18,36 @@ import 'package:flutter/material.dart'; import 'package:playground/modules/examples/components/examples_components.dart'; +import 'package:playground/pages/playground/states/example_selector_state.dart'; +import 'package:provider/provider.dart'; class ExampleList extends StatelessWidget { - final List items; final ScrollController controller; const ExampleList({ Key? key, - required this.items, required this.controller, }) : super(key: key); @override Widget build(BuildContext context) { - return Expanded( - child: Container( - color: Theme.of(context).backgroundColor, - child: Scrollbar( - isAlwaysShown: true, - showTrackOnHover: true, - controller: controller, - child: ListView.builder( - itemCount: items.length, - itemBuilder: (context, index) => CategoryExpansionPanel( - categoryName: items[index].name, - examples: items[index].examples, - ), + return Consumer( + builder: (context, state, child) => Expanded( + child: Container( + color: Theme.of(context).backgroundColor, + child: Scrollbar( + isAlwaysShown: true, + showTrackOnHover: true, controller: controller, - shrinkWrap: true, + child: ListView.builder( + itemCount: state.categories.length, + itemBuilder: (context, index) => CategoryExpansionPanel( + categoryName: state.categories[index].name, + examples: state.categories[index].examples, + ), + controller: controller, + shrinkWrap: true, + ), ), ), ), diff --git a/playground/frontend/lib/modules/examples/components/filter/category_bubble.dart b/playground/frontend/lib/modules/examples/components/filter/category_bubble.dart index 6481cdb6f631..14a89829f8a4 100644 --- a/playground/frontend/lib/modules/examples/components/filter/category_bubble.dart +++ b/playground/frontend/lib/modules/examples/components/filter/category_bubble.dart @@ -19,48 +19,55 @@ import 'package:flutter/material.dart'; import 'package:playground/config/theme.dart'; import 'package:playground/constants/sizes.dart'; -import 'package:playground/pages/playground/states/example_dropdown_state.dart'; +import 'package:playground/modules/examples/models/example_model.dart'; +import 'package:playground/pages/playground/states/example_selector_state.dart'; import 'package:provider/provider.dart'; class CategoryBubble extends StatelessWidget { - final String name; + final ExampleType type; - const CategoryBubble({Key? key, required this.name}) : super(key: key); + const CategoryBubble({Key? key, required this.type}) : super(key: key); @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.only(right: kMdSpacing), - child: Consumer( - builder: (context, state, child) => MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: () { - if (name != state.selectedCategory) { - state.setSelectedCategory(name); - } - }, - child: Container( - height: kContainerHeight, - padding: const EdgeInsets.symmetric(horizontal: kXlSpacing), - decoration: BoxDecoration( - color: name == state.selectedCategory - ? ThemeColors.of(context).primary - : ThemeColors.of(context).lightGreyColor, - borderRadius: BorderRadius.circular(kXlBorderRadius), - ), - child: Center( - child: Text( - name, - style: TextStyle( - color: name == state.selectedCategory - ? ThemeColors.of(context).primaryBackgroundTextColor - : ThemeColors.of(context).lightGreyBackgroundTextColor, + return MouseRegion( + cursor: SystemMouseCursors.click, + child: Padding( + padding: const EdgeInsets.only(right: kMdSpacing), + child: Consumer( + builder: (context, state, child) { + final isSelected = type == state.selectedFilterType; + + return GestureDetector( + onTap: () { + if (!isSelected) { + state.setSelectedFilterType(type); + state.sortCategories(); + } + }, + child: Container( + height: kContainerHeight, + padding: const EdgeInsets.symmetric(horizontal: kXlSpacing), + decoration: BoxDecoration( + color: isSelected + ? ThemeColors.of(context).primary + : ThemeColors.of(context).lightGreyColor, + borderRadius: BorderRadius.circular(kXlBorderRadius), + ), + child: Center( + child: Text( + type.name, + style: TextStyle( + color: isSelected + ? ThemeColors.of(context).primaryBackgroundTextColor + : ThemeColors.of(context) + .lightGreyBackgroundTextColor, + ), ), ), ), - ), - ), + ); + }, ), ), ); diff --git a/playground/frontend/lib/modules/examples/components/filter/type_filter.dart b/playground/frontend/lib/modules/examples/components/filter/type_filter.dart index 7e4eb9a9b081..7b1bbed9b685 100644 --- a/playground/frontend/lib/modules/examples/components/filter/type_filter.dart +++ b/playground/frontend/lib/modules/examples/components/filter/type_filter.dart @@ -19,6 +19,7 @@ import 'package:flutter/material.dart'; import 'package:playground/constants/sizes.dart'; import 'package:playground/modules/examples/components/examples_components.dart'; +import 'package:playground/modules/examples/models/example_model.dart'; class TypeFilter extends StatelessWidget { const TypeFilter({Key? key}) : super(key: key); @@ -32,10 +33,10 @@ class TypeFilter extends StatelessWidget { ), child: Row( children: const [ - CategoryBubble(name: 'All'), - CategoryBubble(name: 'Examples'), - CategoryBubble(name: 'Katas'), - CategoryBubble(name: 'Unit tests'), + CategoryBubble(type: ExampleType.all), + CategoryBubble(type: ExampleType.example), + CategoryBubble(type: ExampleType.kata), + CategoryBubble(type: ExampleType.test), ], ), ); diff --git a/playground/frontend/lib/modules/examples/components/search_field/search_field.dart b/playground/frontend/lib/modules/examples/components/search_field/search_field.dart index fe59aa784dd9..1889b53db120 100644 --- a/playground/frontend/lib/modules/examples/components/search_field/search_field.dart +++ b/playground/frontend/lib/modules/examples/components/search_field/search_field.dart @@ -19,6 +19,8 @@ import 'package:flutter/material.dart'; import 'package:playground/config/theme.dart'; import 'package:playground/constants/sizes.dart'; +import 'package:playground/pages/playground/states/example_selector_state.dart'; +import 'package:provider/provider.dart'; const double kContainerWidth = 376.0; const int kMinLines = 1; @@ -37,45 +39,53 @@ class SearchField extends StatelessWidget { borderRadius: BorderRadius.circular(kMdBorderRadius), ); - return Container( - margin: const EdgeInsets.only( - top: kLgSpacing, - right: kLgSpacing, - left: kLgSpacing, - ), - width: kContainerWidth, - height: kContainerHeight, - color: Theme.of(context).backgroundColor, - child: ClipRRect( - borderRadius: BorderRadius.circular(kMdBorderRadius), - child: TextFormField( - controller: controller, - decoration: InputDecoration( - suffixIcon: Padding( - padding: const EdgeInsetsDirectional.only( - start: kZeroSpacing, - end: kZeroSpacing, - ), - child: Icon( - Icons.search, - color: ThemeColors.of(context).lightGreyColor, - size: kIconSizeMd, + return Consumer( + builder: (context, state, child) => Container( + margin: const EdgeInsets.only( + top: kLgSpacing, + right: kLgSpacing, + left: kLgSpacing, + ), + width: kContainerWidth, + height: kContainerHeight, + color: Theme.of(context).backgroundColor, + child: ClipRRect( + borderRadius: BorderRadius.circular(kMdBorderRadius), + child: TextFormField( + controller: controller, + decoration: InputDecoration( + suffixIcon: Padding( + padding: const EdgeInsetsDirectional.only( + start: kZeroSpacing, + end: kZeroSpacing, + ), + child: Icon( + Icons.search, + color: ThemeColors.of(context).lightGreyColor, + size: kIconSizeMd, + ), ), + focusedBorder: border, + enabledBorder: border, + filled: false, + isDense: true, + hintText: kHintText, + contentPadding: const EdgeInsets.only(left: kLgSpacing), ), - focusedBorder: border, - enabledBorder: border, - filled: false, - isDense: true, - hintText: kHintText, - contentPadding: const EdgeInsets.only(left: kLgSpacing), + cursorColor: ThemeColors.of(context).lightGreyColor, + cursorWidth: kCursorSize, + textAlignVertical: TextAlignVertical.center, + onFieldSubmitted: (String filterText) { + state.setFilterText(filterText); + state.sortCategories(); + }, + onChanged: (String filterText) { + state.setFilterText(filterText); + state.sortCategories(); + }, + maxLines: kMinLines, + minLines: kMaxLines, ), - cursorColor: ThemeColors.of(context).lightGreyColor, - cursorWidth: kCursorSize, - textAlignVertical: TextAlignVertical.center, - onFieldSubmitted: (String txt) {}, - onChanged: (String txt) {}, - maxLines: kMinLines, - minLines: kMaxLines, ), ), ); diff --git a/playground/frontend/lib/modules/examples/example_selector.dart b/playground/frontend/lib/modules/examples/example_selector.dart index cd6e883bbfaf..a9de0b7b6b2a 100644 --- a/playground/frontend/lib/modules/examples/example_selector.dart +++ b/playground/frontend/lib/modules/examples/example_selector.dart @@ -17,63 +17,172 @@ */ import 'package:flutter/material.dart'; -import 'package:playground/components/dropdown_button/dropdown_button.dart'; +import 'package:playground/config/theme.dart'; +import 'package:playground/constants/sizes.dart'; import 'package:playground/modules/examples/components/examples_components.dart'; -import 'package:playground/modules/examples/components/search_field/search_field.dart'; import 'package:playground/modules/examples/models/category_model.dart'; -import 'package:playground/pages/playground/states/example_dropdown_state.dart'; +import 'package:playground/modules/examples/models/selector_size_model.dart'; +import 'package:playground/pages/playground/states/example_selector_state.dart'; +import 'package:playground/pages/playground/states/examples_state.dart'; import 'package:playground/pages/playground/states/playground_state.dart'; import 'package:provider/provider.dart'; +const int kAnimationDurationInMilliseconds = 80; +const Offset kAnimationBeginOffset = Offset(0.0, -0.02); +const Offset kAnimationEndOffset = Offset(0.0, 0.0); +const double kAdditionalDyAlignment = 50.0; const double kLgContainerHeight = 444.0; const double kLgContainerWidth = 400.0; class ExampleSelector extends StatefulWidget { + final Function changeSelectorVisibility; + final bool isSelectorOpened; final List categories; - const ExampleSelector({Key? key, required this.categories}) : super(key: key); + const ExampleSelector({ + Key? key, + required this.changeSelectorVisibility, + required this.isSelectorOpened, + required this.categories, + }) : super(key: key); @override State createState() => _ExampleSelectorState(); } -class _ExampleSelectorState extends State { +class _ExampleSelectorState extends State + with TickerProviderStateMixin { + final GlobalKey selectorKey = LabeledGlobalKey('ExampleSelector'); + late OverlayEntry? examplesDropdown; + late AnimationController animationController; + late Animation offsetAnimation; + final TextEditingController textController = TextEditingController(); final ScrollController scrollController = ScrollController(); @override void initState() { super.initState(); + animationController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: kAnimationDurationInMilliseconds), + ); + offsetAnimation = Tween( + begin: kAnimationBeginOffset, + end: kAnimationEndOffset, + ).animate(animationController); } @override void dispose() { - scrollController.dispose(); + animationController.dispose(); textController.dispose(); + scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { - return AppDropdownButton( - width: kLgContainerWidth, - height: kLgContainerHeight, - buttonText: Consumer( - builder: (context, state, child) => Text(state.examplesTitle), + return Container( + height: kContainerHeight, + decoration: BoxDecoration( + color: ThemeColors.of(context).greyColor, + borderRadius: BorderRadius.circular(kSmBorderRadius), ), - createDropdown: (_) => ChangeNotifierProvider( - create: (context) => ExampleDropdownState(), - builder: (context, _) => Column( - children: [ - SearchField(controller: textController), - const TypeFilter(), - ExampleList( - controller: scrollController, - items: widget.categories, - ), - ], + child: Consumer( + builder: (context, state, child) => TextButton( + key: selectorKey, + onPressed: () { + if (widget.isSelectorOpened) { + animationController.reverse(); + examplesDropdown?.remove(); + } else { + animationController.forward(); + examplesDropdown = createExamplesDropdown(); + Overlay.of(context)?.insert(examplesDropdown!); + } + widget.changeSelectorVisibility(); + }, + child: Wrap( + alignment: WrapAlignment.center, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + Consumer( + builder: (context, state, child) => Text(state.examplesTitle), + ), + const Icon(Icons.keyboard_arrow_down), + ], + ), ), ), ); } + + OverlayEntry createExamplesDropdown() { + SelectorPositionModel posModel = findSelectorPositionData(); + + return OverlayEntry( + builder: (context) { + return Consumer( + builder: (context, state, child) => Stack( + children: [ + GestureDetector( + onTap: () { + animationController.reverse(); + examplesDropdown?.remove(); + state.changeSelectorVisibility(); + }, + child: Container( + color: Colors.transparent, + height: double.infinity, + width: double.infinity, + ), + ), + ChangeNotifierProvider( + create: (context) => ExampleSelectorState( + state, + state.categories!, + ), + builder: (context, _) => Positioned( + left: posModel.xAlignment, + top: posModel.yAlignment + kAdditionalDyAlignment, + child: SlideTransition( + position: offsetAnimation, + child: Material( + elevation: kElevation.toDouble(), + child: Container( + height: kLgContainerHeight, + width: kLgContainerWidth, + decoration: BoxDecoration( + color: Theme.of(context).backgroundColor, + borderRadius: BorderRadius.circular(kMdBorderRadius), + ), + child: Column( + children: [ + SearchField(controller: textController), + const TypeFilter(), + ExampleList(controller: scrollController), + ], + ), + ), + ), + ), + ), + ), + ], + ), + ); + }, + ); + } + + SelectorPositionModel findSelectorPositionData() { + RenderBox? rBox = + selectorKey.currentContext?.findRenderObject() as RenderBox; + SelectorPositionModel positionModel = SelectorPositionModel( + xAlignment: rBox.localToGlobal(Offset.zero).dx, + yAlignment: rBox.localToGlobal(Offset.zero).dy, + ); + return positionModel; + } } diff --git a/playground/frontend/lib/modules/examples/models/category_model.dart b/playground/frontend/lib/modules/examples/models/category_model.dart index 4b852a05fa25..2c4b0f1a3401 100644 --- a/playground/frontend/lib/modules/examples/models/category_model.dart +++ b/playground/frontend/lib/modules/examples/models/category_model.dart @@ -22,5 +22,5 @@ class CategoryModel { final String name; final List examples; - CategoryModel(this.name, this.examples); + const CategoryModel(this.name, this.examples); } diff --git a/playground/frontend/lib/modules/examples/models/example_model.dart b/playground/frontend/lib/modules/examples/models/example_model.dart index fb7bbb4ccb91..17e6f584982c 100644 --- a/playground/frontend/lib/modules/examples/models/example_model.dart +++ b/playground/frontend/lib/modules/examples/models/example_model.dart @@ -19,11 +19,27 @@ import 'package:playground/modules/sdk/models/sdk.dart'; enum ExampleType { + all, example, kata, test, } +extension ExampleTypeToString on ExampleType { + String get name { + switch (this) { + case ExampleType.example: + return 'Examples'; + case ExampleType.kata: + return 'Katas'; + case ExampleType.test: + return 'Unit tests'; + case ExampleType.all: + return 'All'; + } + } +} + class ExampleModel { final Map sources; final ExampleType type; diff --git a/playground/frontend/lib/modules/examples/repositories/example_repository.dart b/playground/frontend/lib/modules/examples/repositories/example_repository.dart index 80d22e94b329..7fdc641d885d 100644 --- a/playground/frontend/lib/modules/examples/repositories/example_repository.dart +++ b/playground/frontend/lib/modules/examples/repositories/example_repository.dart @@ -46,9 +46,9 @@ object Hello { }'''; class ExampleRepository { - List getCategories() { - return [ - CategoryModel('Side Inputs', const [ + List? getCategories() { + return const [ + CategoryModel('Side Inputs', [ ExampleModel( { SDK.java: javaHelloWorld, @@ -66,11 +66,11 @@ class ExampleRepository { SDK.python: 'PYTHON Source code 1', SDK.scio: 'SCIO Source code 1', }, - 'Source code 1', + 'KATA Source code 1', ExampleType.kata, ), ]), - CategoryModel('Side Outputs', const [ + CategoryModel('Side Outputs', [ ExampleModel( { SDK.java: 'JAVA Source code 2', @@ -78,7 +78,7 @@ class ExampleRepository { SDK.python: 'PYTHON Source code 2', SDK.scio: 'SCIO Source code 2', }, - 'Source code 2', + 'UNIT TEST Source code 2', ExampleType.test, ), ExampleModel( @@ -88,11 +88,11 @@ class ExampleRepository { SDK.python: 'PYTHON Source code 3', SDK.scio: 'SCIO Source code 3', }, - 'Source code 3', + 'EXAMPLE Source code 3', ExampleType.example, ), ]), - CategoryModel('I/O', const [ + CategoryModel('I/O', [ ExampleModel( { SDK.java: 'JAVA Source code 4', @@ -100,7 +100,7 @@ class ExampleRepository { SDK.python: 'PYTHON Source code 4', SDK.scio: 'SCIO Source code 4', }, - 'Source code 4', + 'KATA Source code 4', ExampleType.kata, ), ExampleModel( @@ -110,7 +110,7 @@ class ExampleRepository { SDK.python: 'PYTHON Source code 5', SDK.scio: 'SCIO Source code 5', }, - 'Source code 5', + 'UNIT TEST Source code 5', ExampleType.test, ), ]), diff --git a/playground/frontend/lib/pages/playground/playground_page.dart b/playground/frontend/lib/pages/playground/playground_page.dart index f9853723e917..ab20a0f0f796 100644 --- a/playground/frontend/lib/pages/playground/playground_page.dart +++ b/playground/frontend/lib/pages/playground/playground_page.dart @@ -51,7 +51,11 @@ class PlaygroundPage extends StatelessWidget { const Logo(), Consumer( builder: (context, state, child) { - return ExampleSelector(categories: state.categories!); + return ExampleSelector( + changeSelectorVisibility: state.changeSelectorVisibility, + isSelectorOpened: state.isSelectorOpened, + categories: state.categories!, + ); }, ), SDKSelector(sdk: state.sdk, setSdk: state.setSdk), diff --git a/playground/frontend/lib/pages/playground/states/example_dropdown_state.dart b/playground/frontend/lib/pages/playground/states/example_dropdown_state.dart deleted file mode 100644 index 38a966561fb1..000000000000 --- a/playground/frontend/lib/pages/playground/states/example_dropdown_state.dart +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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:flutter/material.dart'; - -class ExampleDropdownState with ChangeNotifier { - String _selectedCategory; - - ExampleDropdownState([this._selectedCategory = 'All']); - - String get selectedCategory => _selectedCategory; - - setSelectedCategory(String name) async { - _selectedCategory = name; - notifyListeners(); - } -} diff --git a/playground/frontend/lib/pages/playground/states/example_selector_state.dart b/playground/frontend/lib/pages/playground/states/example_selector_state.dart new file mode 100644 index 000000000000..c4718e33215d --- /dev/null +++ b/playground/frontend/lib/pages/playground/states/example_selector_state.dart @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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:flutter/material.dart'; +import 'package:playground/modules/examples/models/category_model.dart'; +import 'package:playground/modules/examples/models/example_model.dart'; + +import 'examples_state.dart'; + +class ExampleSelectorState with ChangeNotifier { + final ExampleState _exampleState; + ExampleType _selectedFilterType; + String _filterText; + List categories; + + ExampleSelectorState( + this._exampleState, + this.categories, [ + this._selectedFilterType = ExampleType.all, + this._filterText = '', + ]); + + ExampleType get selectedFilterType => _selectedFilterType; + + String get filterText => _filterText; + + setSelectedFilterType(ExampleType type) { + _selectedFilterType = type; + notifyListeners(); + } + + setFilterText(String text) { + _filterText = text; + notifyListeners(); + } + + setCategories(List? categories) { + this.categories = categories ?? []; + notifyListeners(); + } + + sortCategories() { + final categories = _exampleState.categories!; + final sortedCategories = categories + .map((category) => CategoryModel( + category.name, _sortCategoryExamples(category.examples))) + .where((category) => category.examples.isNotEmpty) + .toList(); + setCategories(sortedCategories); + } + + List _sortCategoryExamples(List examples) { + final isAllFilterType = selectedFilterType == ExampleType.all; + final isFilterTextEmpty = filterText.isEmpty; + if (isAllFilterType && isFilterTextEmpty) { + return examples; + } + if (!isAllFilterType && isFilterTextEmpty) { + return sortExamplesByType( + examples, + selectedFilterType, + ); + } + if (isAllFilterType && !isFilterTextEmpty) { + return sortExamplesByName(examples, filterText); + } + final sorted = sortExamplesByType( + examples, + selectedFilterType, + ); + return sortExamplesByName(sorted, filterText); + } + + List sortExamplesByType( + List examples, + ExampleType type, + ) { + return examples.where((element) => element.type == type).toList(); + } + + List sortExamplesByName( + List examples, + String name, + ) { + return examples + .where((example) => + example.name.toLowerCase().contains(name.toLowerCase())) + .toList(); + } +} diff --git a/playground/frontend/lib/pages/playground/states/examples_state.dart b/playground/frontend/lib/pages/playground/states/examples_state.dart index 43a69ee8e27e..4e3641c1d3ab 100644 --- a/playground/frontend/lib/pages/playground/states/examples_state.dart +++ b/playground/frontend/lib/pages/playground/states/examples_state.dart @@ -23,13 +23,19 @@ import 'package:playground/modules/examples/repositories/example_repository.dart class ExampleState with ChangeNotifier { final ExampleRepository _exampleRepository; List? categories; + bool isSelectorOpened = false; ExampleState(this._exampleRepository) { _loadCategories(); } _loadCategories() { - categories = _exampleRepository.getCategories(); + categories = _exampleRepository.getCategories() ?? []; + notifyListeners(); + } + + changeSelectorVisibility() { + isSelectorOpened = !isSelectorOpened; notifyListeners(); } } diff --git a/playground/frontend/test/pages/playground/states/example_selector_state_test.dart b/playground/frontend/test/pages/playground/states/example_selector_state_test.dart new file mode 100644 index 000000000000..4cf7302e111b --- /dev/null +++ b/playground/frontend/test/pages/playground/states/example_selector_state_test.dart @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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:flutter_test/flutter_test.dart'; +import 'package:playground/modules/examples/models/example_model.dart'; +import 'package:playground/modules/examples/repositories/example_repository.dart'; +import 'package:playground/pages/playground/states/example_selector_state.dart'; +import 'package:playground/pages/playground/states/examples_state.dart'; + +import 'mocks/categories_mock.dart'; + +void main() { + test( + 'ExampleSelector state should notify all listeners about filter type change', + () { + final exampleState = ExampleState(ExampleRepository()); + final state = ExampleSelectorState(exampleState, []); + state.addListener(() { + expect(state.selectedFilterType, ExampleType.example); + }); + state.setSelectedFilterType(ExampleType.example); + }); + + test( + 'ExampleSelector state should notify all listeners about categories change', + () { + final exampleState = ExampleState(ExampleRepository()); + final state = ExampleSelectorState(exampleState, []); + state.addListener(() { + expect(state.categories, []); + }); + state.setCategories([]); + }); + + test( + 'ExampleSelector state sortCategories should:' + '- update categories and notify all listeners,' + 'but should NOT:' + '- wait for full name of example,' + '- be sensitive for register,' + '- affect Example state categories', () { + final exampleState = ExampleState(ExampleRepository()); + final state = ExampleSelectorState( + exampleState, + exampleState.categories!, + ExampleType.example, + 'hLo' + ); + state.addListener(() { + expect(state.categories, []); + expect(exampleState.categories, exampleState.categories); + }); + state.sortCategories(); + }); + + test( + 'ExampleSelector state sortExamplesByType should:' + '- update categories,' + '- notify all listeners,' + 'but should NOT:' + '- affect Example state categories', () { + final exampleState = ExampleState(ExampleRepository()); + final state = ExampleSelectorState(exampleState, exampleState.categories!); + state.addListener(() { + expect(state.categories, examplesSortedByTypeMock); + expect(exampleState.categories, exampleState.categories); + }); + state.sortExamplesByType(unsortedExamples, ExampleType.kata); + }); + + test( + 'ExampleSelector state sortExamplesByName should:' + '- update categories' + '- notify all listeners,' + 'but should NOT:' + '- wait for full name of example,' + '- be sensitive for register,' + '- affect Example state categories', () { + final exampleState = ExampleState(ExampleRepository()); + final state = ExampleSelectorState(exampleState, exampleState.categories!); + state.addListener(() { + expect(state.categories, examplesSortedByNameMock); + expect(exampleState.categories, exampleState.categories); + }); + state.sortExamplesByName(unsortedExamples, 'eLLoWoRl'); + }); +} diff --git a/playground/frontend/test/pages/playground/states/mocks/categories_mock.dart b/playground/frontend/test/pages/playground/states/mocks/categories_mock.dart new file mode 100644 index 000000000000..f3a5b3456cd6 --- /dev/null +++ b/playground/frontend/test/pages/playground/states/mocks/categories_mock.dart @@ -0,0 +1,160 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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:playground/modules/examples/models/category_model.dart'; +import 'package:playground/modules/examples/models/example_model.dart'; +import 'package:playground/modules/sdk/models/sdk.dart'; + +const javaHelloWorld = '''class HelloWorld { + public static void main(String[] args) { + System.out.println("Hello World!"); + } +}'''; + +const pythonHelloWorld = 'print(‘Hello World’)'; + +const goHelloWorld = '''package main + +import "fmt" + +// this is a comment + +func main() { + fmt.Println("Hello World") +}'''; + +const scioHelloWorld = ''' +object Hello { + def main(args: Array[String]) = { + println("Hello, world") + } +}'''; + +const List sortedCategories = [ + CategoryModel('Side Inputs', [ + ExampleModel( + { + SDK.java: javaHelloWorld, + SDK.go: goHelloWorld, + SDK.python: pythonHelloWorld, + SDK.scio: scioHelloWorld, + }, + 'HelloWorld', + ExampleType.example, + ), + ]) +]; + +const List unsortedExamples = [ + ExampleModel( + { + SDK.java: javaHelloWorld, + SDK.go: goHelloWorld, + SDK.python: pythonHelloWorld, + SDK.scio: scioHelloWorld, + }, + 'HelloWorld', + ExampleType.example, + ), + ExampleModel( + { + SDK.java: 'JAVA Source code 1', + SDK.go: 'GO Source code 1', + SDK.python: 'PYTHON Source code 1', + SDK.scio: 'SCIO Source code 1', + }, + 'KATA Source code 1', + ExampleType.kata, + ), + ExampleModel( + { + SDK.java: 'JAVA Source code 2', + SDK.go: 'GO Source code 2', + SDK.python: 'PYTHON Source code 2', + SDK.scio: 'SCIO Source code 2', + }, + 'UNIT TEST Source code 2', + ExampleType.test, + ), + ExampleModel( + { + SDK.java: 'JAVA Source code 3', + SDK.go: 'GO Source code 3', + SDK.python: 'PYTHON Source code 3', + SDK.scio: 'SCIO Source code 3', + }, + 'EXAMPLE Source code 3', + ExampleType.example, + ), + ExampleModel( + { + SDK.java: 'JAVA Source code 4', + SDK.go: 'GO Source code 4', + SDK.python: 'PYTHON Source code 4', + SDK.scio: 'SCIO Source code 4', + }, + 'KATA Source code 4', + ExampleType.kata, + ), + ExampleModel( + { + SDK.java: 'JAVA Source code 5', + SDK.go: 'GO Source code 5', + SDK.python: 'PYTHON Source code 5', + SDK.scio: 'SCIO Source code 5', + }, + 'UNIT TEST Source code 5', + ExampleType.test, + ), +]; + +const List examplesSortedByTypeMock = [ + ExampleModel( + { + SDK.java: 'JAVA Source code 1', + SDK.go: 'GO Source code 1', + SDK.python: 'PYTHON Source code 1', + SDK.scio: 'SCIO Source code 1', + }, + 'KATA Source code 1', + ExampleType.kata, + ), + ExampleModel( + { + SDK.java: 'JAVA Source code 4', + SDK.go: 'GO Source code 4', + SDK.python: 'PYTHON Source code 4', + SDK.scio: 'SCIO Source code 4', + }, + 'KATA Source code 4', + ExampleType.kata, + ), +]; + +const List examplesSortedByNameMock = [ + ExampleModel( + { + SDK.java: javaHelloWorld, + SDK.go: goHelloWorld, + SDK.python: pythonHelloWorld, + SDK.scio: scioHelloWorld, + }, + 'HelloWorld', + ExampleType.example, + ), +];