From 933bb24a2f58cd6cfa1e35e8b41a9baa4c13815f Mon Sep 17 00:00:00 2001 From: Alexey Inkin Date: Tue, 20 Dec 2022 17:56:42 +0400 Subject: [PATCH 01/52] Integration test to load the default example of the default SDK and change the example (#24730) (#24729) --- .gitignore | 1 + playground/frontend/README.md | 31 +++ playground/frontend/build.gradle | 131 +++++------ .../frontend/integration_test/common.dart | 37 ++++ .../standalone_change_example_test.dart | 56 +++++ .../frontend/playground_components/LICENSE | 205 ------------------ .../frontend/playground_components/README.md | 28 +-- .../analysis_options.yaml | 3 - .../playground_components_dev/LICENSE | 202 +++++++++++++++++ .../README.md} | 5 +- .../analysis_options.yaml | 18 ++ .../lib/playground_components_dev.dart | 22 ++ .../lib/src/code.dart | 42 ++++ .../lib/src/example_names.dart | 21 ++ .../lib/src/example_paths.dart | 24 ++ .../lib/src/examples.dart | 34 +++ .../lib/src/widget_tester.dart | 29 +++ .../playground_components_dev/pubspec.yaml | 33 +++ playground/frontend/pubspec.lock | 56 ++++- playground/frontend/pubspec.yaml | 3 + .../test_driver/integration_test.dart | 21 ++ 21 files changed, 685 insertions(+), 317 deletions(-) create mode 100644 playground/frontend/integration_test/common.dart create mode 100644 playground/frontend/integration_test/standalone_change_example_test.dart create mode 100644 playground/frontend/playground_components_dev/LICENSE rename playground/frontend/{playground_components/CHANGELOG.md => playground_components_dev/README.md} (83%) create mode 100644 playground/frontend/playground_components_dev/analysis_options.yaml create mode 100644 playground/frontend/playground_components_dev/lib/playground_components_dev.dart create mode 100644 playground/frontend/playground_components_dev/lib/src/code.dart create mode 100644 playground/frontend/playground_components_dev/lib/src/example_names.dart create mode 100644 playground/frontend/playground_components_dev/lib/src/example_paths.dart create mode 100644 playground/frontend/playground_components_dev/lib/src/examples.dart create mode 100644 playground/frontend/playground_components_dev/lib/src/widget_tester.dart create mode 100644 playground/frontend/playground_components_dev/pubspec.yaml create mode 100644 playground/frontend/test_driver/integration_test.dart diff --git a/.gitignore b/.gitignore index 73c9e05b4eec..b00398d7de58 100644 --- a/.gitignore +++ b/.gitignore @@ -127,6 +127,7 @@ website/www/yarn-error.log **/.packages **/generated_plugin_registrant.dart playground/frontend/playground_components/pubspec.lock +playground/frontend/playground_components_dev/pubspec.lock # Ignore Beam Playground Terraform **/.terraform diff --git a/playground/frontend/README.md b/playground/frontend/README.md index 862fcdacc050..0e7915d1cced 100644 --- a/playground/frontend/README.md +++ b/playground/frontend/README.md @@ -141,6 +141,37 @@ Code can be automatically reformatted using: flutter format ./lib ``` +### Unit Tests + +To delete all generated files and re-generate them again and then run tests: + +```bash +./gradlew :playground:frontend:playground_components_test +./gradlew :playground:frontend:test +``` + +To run tests without re-generating files: + +```bash +cd playground/frontend/playground_components +flutter test +cd .. +flutter test +``` + +# Integration Tests + +Integration tests currently can be run only on a local development machine. +Server testing has not been verified yet. + +1. Install and run Chrome Driver: https://chromedriver.chromium.org/downloads +2. Run it on port 4444: `chromedriver --port=4444` +3. Run: + +```bash +./gradlew :playground:frontend:integrationTest +``` + ## Localization The project is in the process of migrating from diff --git a/playground/frontend/build.gradle b/playground/frontend/build.gradle index 27ca2c6ce064..c4de9c5726dd 100644 --- a/playground/frontend/build.gradle +++ b/playground/frontend/build.gradle @@ -17,18 +17,11 @@ */ -apply plugin: 'org.apache.beam.module' -apply plugin: 'base' +apply(plugin: "org.apache.beam.module") +apply(plugin: "base") applyDockerNature() -def playgroundBackendUrl = project.playgroundBackendUrl -def analyticsUA = project.analyticsUA -def playgroundBackendJavaRouteUrl = project.playgroundBackendJavaRouteUrl -def playgroundBackendGoRouteUrl = project.playgroundBackendGoRouteUrl -def playgroundBackendPythonRouteUrl = project.playgroundBackendPythonRouteUrl -def playgroundBackendScioRouteUrl = project.playgroundBackendScioRouteUrl - -def playgroundJobServerProject = "${project.path.replace('-container', '')}" +def playgroundJobServerProject = "${project.path.replace("-container", "")}" description = project(playgroundJobServerProject).description + " :: Container" @@ -37,10 +30,10 @@ configurations { } dependencies { - dockerDependency project(path: playgroundJobServerProject, configuration: "shadow") + dockerDependency(project(path: playgroundJobServerProject, configuration: "shadow")) } -task generate { +tasks.register("generate") { dependsOn("playground_components:generate") dependsOn("generateCode") @@ -49,7 +42,7 @@ task generate { description = "Generates all generated files." } -task printPath { +tasks.register("printPath") { doLast { exec { executable("printenv") @@ -58,7 +51,7 @@ task printPath { } } -task analyze { +tasks.register("analyze") { dependsOn("playground_components:generateCode") dependsOn("generateCode") @@ -74,7 +67,7 @@ task analyze { } } -task pubGet { +tasks.register("pubGet") { group = "build" description = "Get packages for the playground frontend project" doLast { @@ -85,7 +78,7 @@ task pubGet { } } -task format { +tasks.register("format") { group = "build" description = "Idiomatically format Dart source code" doLast { @@ -97,9 +90,10 @@ task format { } } -task run { +tasks.register("run") { group = "application" description = "Run application on Google Chrome" + doLast { exec { executable("flutter") @@ -108,7 +102,7 @@ task run { } } -task test { +tasks.register("test") { dependsOn("playground_components:generateCode") dependsOn("generateCode") @@ -123,14 +117,14 @@ task test { } } -task precommit { +tasks.register("precommit") { dependsOn("playground_components:precommit") dependsOn("analyze") dependsOn("test") } -task generateCode { +tasks.register("generateCode") { dependsOn("playground_components:generateCode") dependsOn("cleanFlutter") @@ -147,7 +141,7 @@ task generateCode { } } -task cleanFlutter { +tasks.register("cleanFlutter") { group = "build" description = "Remove build artifacts" @@ -159,7 +153,7 @@ task cleanFlutter { } } -task cleanGenerated { +tasks.register("cleanGenerated") { dependsOn("playground_components:cleanGenerated") group = "build" @@ -188,75 +182,54 @@ ext.deleteFilesByRegExp = { re -> } } +tasks.register("integrationTest") { + dependsOn("integrationTest_standalone_change_example") +} + +tasks.register("integrationTest_standalone_change_example") { + runIntegrationTest("standalone_change_example", "/") +} + +void runIntegrationTest(String path, String url) { + exec { + executable("flutter") + args( + "drive", + "--driver=test_driver/integration_test.dart", + "--target=integration_test/${path}_test.dart", + "--web-launch-url='$url'", + "--device-id=chrome", + ) + } +} + task copyDockerfileDependencies(type: Copy) { group = "build" description = "Copy files that required to build docker container" copy { - from '.' - into 'build/' - exclude 'build' - exclude 'Dockerfile' + from(".") + into("build/") + exclude("build") + exclude("Dockerfile") } copy { - from '../playground' - into 'build/playground' + from("../playground") + into("build/playground") } } docker { group = "build" description = "Build container for playground frontend application" - name containerImageName( - name: project.docker_image_default_repo_prefix + "playground-frontend", - root: project.rootProject.hasProperty(["docker-repository-root"]) ? - project.rootProject["docker-repository-root"] : - project.docker_image_default_repo_root) - files "./build/" - tags containerImageTags() - buildArgs(['FLUTTER_VERSION': project.rootProject.hasProperty(["flutter-version"]) ? - project.rootProject["flutter-version"] : - "3.3.2" ]) + name = containerImageName( + name: project.docker_image_default_repo_prefix + "playground-frontend", + root: project.rootProject.hasProperty(["docker-repository-root"]) + ? project.rootProject["docker-repository-root"] + : project.docker_image_default_repo_root + ) + files("./build/") + tags(containerImageTags()) } // Ensure that we build the required resources and copy and file dependencies from related projects -dockerPrepare.dependsOn copyDockerfileDependencies - -task("createConfig") { - group = "build" - description = "Generate config for the playground frontend project" - doLast { - def configFileName = "config.g.dart" - def modulePath = project(":playground:frontend").projectDir.absolutePath - def file = new File(modulePath + "/lib", configFileName) - file.write("""/* - * 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. - */ - -const String kApiClientURL = - '${playgroundBackendUrl}'; -const String kAnalyticsUA = '${analyticsUA}'; -const String kApiJavaClientURL = - '${playgroundBackendJavaRouteUrl}'; -const String kApiGoClientURL = - '${playgroundBackendGoRouteUrl}'; -const String kApiPythonClientURL = - '${playgroundBackendPythonRouteUrl}'; -const String kApiScioClientURL = - '${playgroundBackendScioRouteUrl}'; -""") - } -} +dockerPrepare.dependsOn(copyDockerfileDependencies) diff --git a/playground/frontend/integration_test/common.dart b/playground/frontend/integration_test/common.dart new file mode 100644 index 000000000000..92162fbede7c --- /dev/null +++ b/playground/frontend/integration_test/common.dart @@ -0,0 +1,37 @@ +/* + * 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/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:playground/main.dart' as app; +import 'package:playground/modules/examples/example_selector.dart'; + +Future init(WidgetTester wt) async { + app.main(); + await wt.pumpAndSettle(); +} + +extension CommonFindersExtension on CommonFinders { + Finder exampleSelector() { + return byType(ExampleSelector); + } + + Finder exampleItemInDropdown(String name) { + return widgetWithText(GestureDetector, name); + } +} diff --git a/playground/frontend/integration_test/standalone_change_example_test.dart b/playground/frontend/integration_test/standalone_change_example_test.dart new file mode 100644 index 000000000000..71da58418fe7 --- /dev/null +++ b/playground/frontend/integration_test/standalone_change_example_test.dart @@ -0,0 +1,56 @@ +/* + * 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:integration_test/integration_test.dart'; +import 'package:playground_components_dev/playground_components_dev.dart'; + +import 'common.dart'; + +const _fiveSec = Duration(seconds: 5); + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('Integration.', () { + testWidgets('Change example', (WidgetTester wt) async { + await init(wt); + + expect( + wt.findOneCodeController().lastTextSpan!.toPlainText(), + await Examples.getJavaVisibleText(ExamplePaths.javaMinimalWordCount), + ); + + await wt.tap(find.exampleSelector()); + await wt.pumpAndSettle(); + + await wt.tap(find.exampleItemInDropdown(ExampleNames.aggregationMax)); + + // There is no animation on changing examples, so the above did not wait + // for the example to load. + // TODO(alexeyinkin): Add animation, https://github.com/apache/beam/issues/24724 + await Future.delayed(_fiveSec); + await wt.pumpAndSettle(); + + expect( + wt.findOneCodeController().lastTextSpan!.toPlainText(), + await Examples.getJavaVisibleText(ExamplePaths.javaAggregationMax), + ); + }); + }); +} diff --git a/playground/frontend/playground_components/LICENSE b/playground/frontend/playground_components/LICENSE index 8c048c96fb52..d64569567334 100644 --- a/playground/frontend/playground_components/LICENSE +++ b/playground/frontend/playground_components/LICENSE @@ -200,208 +200,3 @@ 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. - - A part of several convenience binary distributions of this software is licensed as follows: - - Google Protobuf: - Copyright 2008 Google Inc. All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer - in the documentation and/or other materials provided with the - distribution. - * Neither the name of Google Inc. nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Code generated by the Protocol Buffer compiler is owned by the owner - of the input file used when generating it. This code is not - standalone and requires a support library to be linked with it. This - support library is itself covered by the above license. - - jsr-305: - Copyright (c) 2007-2009, JSR305 expert group - All rights reserved. - - https://opensource.org/licenses/BSD-3-Clause - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of the JSR305 expert group nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. - - janino-compiler: - Janino - An embedded Java[TM] compiler - - Copyright (c) 2001-2016, Arno Unkrig - Copyright (c) 2015-2016 TIBCO Software Inc. - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials - provided with the distribution. - 3. Neither the name of JANINO nor the names of its contributors - may be used to endorse or promote products derived from this - software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER - IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN - IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - jline: - Copyright (c) 2002-2016, the original author or authors. - All rights reserved. - - http://www.opensource.org/licenses/bsd-license.php - - Redistribution and use in source and binary forms, with or - without modification, are permitted provided that the following - conditions are met: - - Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer - in the documentation and/or other materials provided with - the distribution. - - Neither the name of JLine nor the names of its contributors - may be used to endorse or promote products derived from this - software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, - BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING - IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - OF THE POSSIBILITY OF SUCH DAMAGE. - - sqlline: - SQLLine - Shell for issuing SQL to relational databases via JDBC - - Copyright (c) 2002,2003,2004,2005,2006,2007 Marc Prud'hommeaux - Copyright (c) 2004-2010 The Eigenbase Project - Copyright (c) 2013-2017 Julian Hyde - All rights reserved. - - =============================================================================== - - Licensed under the Modified BSD License (the "License"); you may not - use this file except in compliance with the License. You may obtain a - copy of the License at: - - http://opensource.org/licenses/BSD-3-Clause - - Redistribution and use in source and binary forms, - with or without modification, are permitted provided - that the following conditions are met: - - (1) Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - (2) Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the - distribution. - - (3) The name of the author may not be used to endorse or promote - products derived from this software without specific prior written - permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - slf4j: - Copyright (c) 2004-2017 QOS.ch - All rights reserved. - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE - -See the adjacent LICENSE.python file, if present, for additional licenses that -apply to parts of Apache Beam Python. diff --git a/playground/frontend/playground_components/README.md b/playground/frontend/playground_components/README.md index 9c4ef73d25d0..6a99b6b645ce 100644 --- a/playground/frontend/playground_components/README.md +++ b/playground/frontend/playground_components/README.md @@ -17,29 +17,7 @@ under the License. --> -TODO: Put a short description of the package here that helps potential users -know whether this package might be useful for them. +# playground_components -## Features - -TODO: List what your package can do. Maybe include images, gifs, or videos. - -## Getting started - -TODO: List prerequisites and provide or point to information on how to -start using the package. - -## Usage - -TODO: Include short and useful examples for package users. Add longer examples -to `/example` folder. - -```dart -const like = 'sample'; -``` - -## Additional information - -TODO: Tell users more about the package: where to find more information, how to -contribute to the package, how to file issues, what response they can expect -from the package authors, and more. +This is a non-pub.dev Flutter package that contains common components +for both Beam Playground app and Tour of Beam app. diff --git a/playground/frontend/playground_components/analysis_options.yaml b/playground/frontend/playground_components/analysis_options.yaml index 318f01bfa2fd..fe2e0e8eb952 100644 --- a/playground/frontend/playground_components/analysis_options.yaml +++ b/playground/frontend/playground_components/analysis_options.yaml @@ -16,6 +16,3 @@ # under the License. include: package:total_lints/app.yaml - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options diff --git a/playground/frontend/playground_components_dev/LICENSE b/playground/frontend/playground_components_dev/LICENSE new file mode 100644 index 000000000000..d64569567334 --- /dev/null +++ b/playground/frontend/playground_components_dev/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/playground/frontend/playground_components/CHANGELOG.md b/playground/frontend/playground_components_dev/README.md similarity index 83% rename from playground/frontend/playground_components/CHANGELOG.md rename to playground/frontend/playground_components_dev/README.md index 504fa05fe23c..cf1f2678a28d 100644 --- a/playground/frontend/playground_components/CHANGELOG.md +++ b/playground/frontend/playground_components_dev/README.md @@ -17,6 +17,7 @@ under the License. --> -## 0.0.1 +# playground_components_dev -* TODO: Describe initial release. +This is a non-pub.dev Flutter package that contains +helpers for testing [playground_components](../playground_components) package. diff --git a/playground/frontend/playground_components_dev/analysis_options.yaml b/playground/frontend/playground_components_dev/analysis_options.yaml new file mode 100644 index 000000000000..fe2e0e8eb952 --- /dev/null +++ b/playground/frontend/playground_components_dev/analysis_options.yaml @@ -0,0 +1,18 @@ +# 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. + +include: package:total_lints/app.yaml diff --git a/playground/frontend/playground_components_dev/lib/playground_components_dev.dart b/playground/frontend/playground_components_dev/lib/playground_components_dev.dart new file mode 100644 index 000000000000..88b05483a40b --- /dev/null +++ b/playground/frontend/playground_components_dev/lib/playground_components_dev.dart @@ -0,0 +1,22 @@ +/* + * 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. + */ + +export 'src/example_names.dart'; +export 'src/example_paths.dart'; +export 'src/examples.dart'; +export 'src/widget_tester.dart'; diff --git a/playground/frontend/playground_components_dev/lib/src/code.dart b/playground/frontend/playground_components_dev/lib/src/code.dart new file mode 100644 index 000000000000..e0029b73ebb2 --- /dev/null +++ b/playground/frontend/playground_components_dev/lib/src/code.dart @@ -0,0 +1,42 @@ +/* + * 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. + */ + +extension StringCodeExtension on String { + /// Returns the visible text mimicking the flutter_code_editor's folding of + /// license. + String foldJavaLicense() { + return replaceFirstMapped( + RegExp(r'^(/\*)(.*?\*/)(.*)$', dotAll: true), + (m) => '${m[1]}${m[3]}', + ); + } + + /// Returns the visible text mimicking the flutter_code_editor's folding of + /// sequential imports. + String foldJavaImports() { + final packageRegExp = RegExp('^package .*?\n', multiLine: true); + final cutStart = indexOf(packageRegExp) + + (packageRegExp.firstMatch(this)?[0]?.length ?? 0); + + final importRegExp = RegExp('^import .*?\n', multiLine: true); + final lastImportStart = lastIndexOf(importRegExp); + + return substring(0, cutStart) + + substring(lastImportStart).replaceFirst(importRegExp, ''); + } +} diff --git a/playground/frontend/playground_components_dev/lib/src/example_names.dart b/playground/frontend/playground_components_dev/lib/src/example_names.dart new file mode 100644 index 000000000000..632044005926 --- /dev/null +++ b/playground/frontend/playground_components_dev/lib/src/example_names.dart @@ -0,0 +1,21 @@ +/* + * 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. + */ + +class ExampleNames { + static const aggregationMax = 'AggregationMax'; +} diff --git a/playground/frontend/playground_components_dev/lib/src/example_paths.dart b/playground/frontend/playground_components_dev/lib/src/example_paths.dart new file mode 100644 index 000000000000..e7e78af5efc9 --- /dev/null +++ b/playground/frontend/playground_components_dev/lib/src/example_paths.dart @@ -0,0 +1,24 @@ +/* + * 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. + */ + +class ExamplePaths { + static const javaAggregationMax = + '/learning/katas/java/Common Transforms/Aggregation/Max/src/org/apache/beam/learning/katas/commontransforms/aggregation/max/Task.java'; + static const javaMinimalWordCount = + '/examples/java/src/main/java/org/apache/beam/examples/MinimalWordCount.java'; +} diff --git a/playground/frontend/playground_components_dev/lib/src/examples.dart b/playground/frontend/playground_components_dev/lib/src/examples.dart new file mode 100644 index 000000000000..1a4147f9369e --- /dev/null +++ b/playground/frontend/playground_components_dev/lib/src/examples.dart @@ -0,0 +1,34 @@ +/* + * 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:http/http.dart' as http; + +import 'code.dart'; + +class Examples { + static const repoAndBranch = 'apache/beam/master'; + + static Future getJavaVisibleText(String path) async { + final uri = + Uri.parse('https://raw.githubusercontent.com/$repoAndBranch$path'); + final response = await http.get(uri); + final content = response.body; + + return content.foldJavaLicense().foldJavaImports(); + } +} diff --git a/playground/frontend/playground_components_dev/lib/src/widget_tester.dart b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart new file mode 100644 index 000000000000..16a42e41d728 --- /dev/null +++ b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart @@ -0,0 +1,29 @@ +/* + * 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_code_editor/flutter_code_editor.dart'; +import 'package:flutter_test/flutter_test.dart'; + +extension WidgetTesterExtension on WidgetTester { + CodeController findOneCodeController() { + final codeField = find.byType(CodeField); + expect(codeField, findsOneWidget); + + return widget(codeField).controller; + } +} diff --git a/playground/frontend/playground_components_dev/pubspec.yaml b/playground/frontend/playground_components_dev/pubspec.yaml new file mode 100644 index 000000000000..0c659c41742a --- /dev/null +++ b/playground/frontend/playground_components_dev/pubspec.yaml @@ -0,0 +1,33 @@ +# 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. + +name: playground_components_dev +description: Helpers for testing playground_components package +version: 0.0.1 +publish_to: none + +environment: + sdk: '>=2.18.1 <4.0.0' + flutter: '>=3.3.2' + +dependencies: + flutter: { sdk: flutter } + flutter_code_editor: ^0.2.2 + flutter_test: { sdk: flutter } + http: ^0.13.5 + playground_components: { path: ../playground_components } + total_lints: ^2.18.0 diff --git a/playground/frontend/pubspec.lock b/playground/frontend/pubspec.lock index af95acc0757f..bbe8b5e21a68 100644 --- a/playground/frontend/pubspec.lock +++ b/playground/frontend/pubspec.lock @@ -42,7 +42,7 @@ packages: name: archive url: "https://pub.dartlang.org" source: hosted - version: "3.3.1" + version: "3.3.0" args: dependency: transitive description: @@ -287,12 +287,17 @@ packages: source: sdk version: "0.0.0" flutter_code_editor: - dependency: transitive + dependency: "direct dev" description: name: flutter_code_editor url: "https://pub.dartlang.org" source: hosted - version: "0.2.1" + version: "0.2.2" + flutter_driver: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" flutter_highlight: dependency: transitive description: @@ -357,6 +362,11 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.3" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" get_it: dependency: "direct main" description: @@ -448,6 +458,11 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.0.1" + integration_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" intl: dependency: "direct main" description: @@ -651,6 +666,13 @@ packages: relative: true source: path version: "0.0.1" + playground_components_dev: + dependency: "direct dev" + description: + path: playground_components_dev + relative: true + source: path + version: "0.0.1" plugin_platform_interface: dependency: transitive description: @@ -831,6 +853,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.1" + sync_http: + dependency: transitive + description: + name: sync_http + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.1" term_glyph: dependency: transitive description: @@ -852,6 +881,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" + total_lints: + dependency: transitive + description: + name: total_lints + url: "https://pub.dartlang.org" + source: hosted + version: "2.18.0" tuple: dependency: transitive description: @@ -957,6 +993,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.2" + vm_service: + dependency: transitive + description: + name: vm_service + url: "https://pub.dartlang.org" + source: hosted + version: "9.0.0" watcher: dependency: transitive description: @@ -978,6 +1021,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.2.0" + webdriver: + dependency: transitive + description: + name: webdriver + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" win32: dependency: transitive description: diff --git a/playground/frontend/pubspec.yaml b/playground/frontend/pubspec.yaml index 655948c14426..f5b35dca8fbb 100644 --- a/playground/frontend/pubspec.yaml +++ b/playground/frontend/pubspec.yaml @@ -53,9 +53,12 @@ dependencies: dev_dependencies: build_runner: ^2.1.4 fake_async: ^1.3.0 + flutter_code_editor: ^0.2.2 flutter_lints: ^2.0.1 flutter_test: { sdk: flutter } + integration_test: { sdk: flutter } mockito: ^5.0.16 + playground_components_dev: { path: playground_components_dev } flutter: assets: diff --git a/playground/frontend/test_driver/integration_test.dart b/playground/frontend/test_driver/integration_test.dart new file mode 100644 index 000000000000..6b59b37dd129 --- /dev/null +++ b/playground/frontend/test_driver/integration_test.dart @@ -0,0 +1,21 @@ +/* + * 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:integration_test/integration_test_driver.dart'; + +Future main() => integrationDriver(); From acd76ef6b9165c913d887df66ebe437404a4c071 Mon Sep 17 00:00:00 2001 From: Alexey Inkin Date: Tue, 20 Dec 2022 18:22:28 +0400 Subject: [PATCH 02/52] Fix formatting and README (#24730) --- playground/frontend/README.md | 2 +- .../integration_test/standalone_change_example_test.dart | 2 +- .../frontend/playground_components_dev/lib/src/examples.dart | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/playground/frontend/README.md b/playground/frontend/README.md index 0e7915d1cced..120d9da7e2ba 100644 --- a/playground/frontend/README.md +++ b/playground/frontend/README.md @@ -159,7 +159,7 @@ cd .. flutter test ``` -# Integration Tests +### Integration Tests Integration tests currently can be run only on a local development machine. Server testing has not been verified yet. diff --git a/playground/frontend/integration_test/standalone_change_example_test.dart b/playground/frontend/integration_test/standalone_change_example_test.dart index 71da58418fe7..71d4bdef6cf6 100644 --- a/playground/frontend/integration_test/standalone_change_example_test.dart +++ b/playground/frontend/integration_test/standalone_change_example_test.dart @@ -41,7 +41,7 @@ void main() { await wt.tap(find.exampleItemInDropdown(ExampleNames.aggregationMax)); - // There is no animation on changing examples, so the above did not wait + // There is no animation on changing examples, so we cannot wait // for the example to load. // TODO(alexeyinkin): Add animation, https://github.com/apache/beam/issues/24724 await Future.delayed(_fiveSec); diff --git a/playground/frontend/playground_components_dev/lib/src/examples.dart b/playground/frontend/playground_components_dev/lib/src/examples.dart index 1a4147f9369e..8b624bc80e54 100644 --- a/playground/frontend/playground_components_dev/lib/src/examples.dart +++ b/playground/frontend/playground_components_dev/lib/src/examples.dart @@ -21,11 +21,11 @@ import 'package:http/http.dart' as http; import 'code.dart'; class Examples { - static const repoAndBranch = 'apache/beam/master'; + static const _repoAndBranch = 'apache/beam/master'; static Future getJavaVisibleText(String path) async { final uri = - Uri.parse('https://raw.githubusercontent.com/$repoAndBranch$path'); + Uri.parse('https://raw.githubusercontent.com/$_repoAndBranch$path'); final response = await http.get(uri); final content = response.body; From 07e78c6fff346a5aabc76e2255cb7998f87f3fec Mon Sep 17 00:00:00 2001 From: Alexey Inkin Date: Wed, 21 Dec 2022 11:11:20 +0400 Subject: [PATCH 03/52] Support collection v1.17.0 (#24730) --- learning/tour-of-beam/frontend/pubspec.yaml | 2 +- playground/frontend/pubspec.lock | 2 +- playground/frontend/pubspec.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/learning/tour-of-beam/frontend/pubspec.yaml b/learning/tour-of-beam/frontend/pubspec.yaml index 22fa61ebbacf..fa059b5153b8 100644 --- a/learning/tour-of-beam/frontend/pubspec.yaml +++ b/learning/tour-of-beam/frontend/pubspec.yaml @@ -27,7 +27,7 @@ environment: flutter: '>=3.3.2' dependencies: - app_state: ^0.8.1 + app_state: ^0.8.4 collection: ^1.16.0 easy_localization: ^3.0.1 easy_localization_ext: ^0.1.0 diff --git a/playground/frontend/pubspec.lock b/playground/frontend/pubspec.lock index bbe8b5e21a68..604a2a415989 100644 --- a/playground/frontend/pubspec.lock +++ b/playground/frontend/pubspec.lock @@ -35,7 +35,7 @@ packages: name: app_state url: "https://pub.dartlang.org" source: hosted - version: "0.8.3" + version: "0.8.4" archive: dependency: transitive description: diff --git a/playground/frontend/pubspec.yaml b/playground/frontend/pubspec.yaml index f5b35dca8fbb..8dc7765db698 100644 --- a/playground/frontend/pubspec.yaml +++ b/playground/frontend/pubspec.yaml @@ -27,7 +27,7 @@ environment: dependencies: akvelon_flutter_issue_106664_workaround: ^0.1.2 aligned_dialog: ^0.0.6 - app_state: ^0.8.3 + app_state: ^0.8.4 collection: ^1.15.0 easy_localization: ^3.0.1 easy_localization_ext: ^0.1.1 From 5ffd808412b1a6e723e8881ef4717e4740403c64 Mon Sep 17 00:00:00 2001 From: Alexey Inkin Date: Wed, 21 Dec 2022 19:13:27 +0400 Subject: [PATCH 04/52] LoadingIndicator on chaning examples, remove duplicating licenses (#24730) --- .../standalone_change_example_test.dart | 7 - playground/frontend/lib/main.dart | 2 +- .../example_list/expansion_panel_item.dart | 16 +- .../lib/pages/embedded_playground/screen.dart | 28 ++- .../widgets/embedded_editor.dart | 2 +- .../pages/standalone_playground/screen.dart | 18 +- .../widgets/editor_textarea_wrapper.dart | 110 +++++----- .../widgets/playground_page_body.dart | 27 ++- .../frontend/playground_components/LICENSE | 202 ------------------ .../lib/playground_components.dart | 2 + .../standard_example_loader.dart | 21 +- .../controllers/playground_controller.dart | 38 ++++ .../snippet_editing_controller.dart | 26 +++ .../lib/src/router/router_delegate.dart | 25 +++ .../src/widgets/toasts/toast_listener.dart | 1 + .../playground_components/pubspec.yaml | 3 +- .../playground_components_dev/LICENSE | 202 ------------------ .../playground_components_dev/pubspec.yaml | 2 +- playground/frontend/pubspec.lock | 2 +- playground/frontend/pubspec.yaml | 2 +- 20 files changed, 202 insertions(+), 534 deletions(-) delete mode 100644 playground/frontend/playground_components/LICENSE create mode 100644 playground/frontend/playground_components/lib/src/router/router_delegate.dart delete mode 100644 playground/frontend/playground_components_dev/LICENSE diff --git a/playground/frontend/integration_test/standalone_change_example_test.dart b/playground/frontend/integration_test/standalone_change_example_test.dart index 71d4bdef6cf6..3ae2244ab1f4 100644 --- a/playground/frontend/integration_test/standalone_change_example_test.dart +++ b/playground/frontend/integration_test/standalone_change_example_test.dart @@ -22,8 +22,6 @@ import 'package:playground_components_dev/playground_components_dev.dart'; import 'common.dart'; -const _fiveSec = Duration(seconds: 5); - void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -40,11 +38,6 @@ void main() { await wt.pumpAndSettle(); await wt.tap(find.exampleItemInDropdown(ExampleNames.aggregationMax)); - - // There is no animation on changing examples, so we cannot wait - // for the example to load. - // TODO(alexeyinkin): Add animation, https://github.com/apache/beam/issues/24724 - await Future.delayed(_fiveSec); await wt.pumpAndSettle(); expect( diff --git a/playground/frontend/lib/main.dart b/playground/frontend/lib/main.dart index ead9321f5a12..f65bf8aa916f 100644 --- a/playground/frontend/lib/main.dart +++ b/playground/frontend/lib/main.dart @@ -42,7 +42,7 @@ void main() async { // Router API specific initialization. final pageStack = GetIt.instance.get(); - final routerDelegate = PageStackRouterDelegate(pageStack); + final routerDelegate = BeamRouterDelegate(pageStack); final routeInformationParser = PlaygroundRouteInformationParser(); final backButtonDispatcher = PageStackBackButtonDispatcher(pageStack); diff --git a/playground/frontend/lib/modules/examples/components/example_list/expansion_panel_item.dart b/playground/frontend/lib/modules/examples/components/example_list/expansion_panel_item.dart index 053968ff09a4..87e792169be1 100644 --- a/playground/frontend/lib/modules/examples/components/example_list/expansion_panel_item.dart +++ b/playground/frontend/lib/modules/examples/components/example_list/expansion_panel_item.dart @@ -47,21 +47,7 @@ class ExpansionPanelItem extends StatelessWidget { if (controller.selectedExample != example) { _closeDropdown(controller.exampleCache); AnalyticsService.get(context).trackSelectExample(example); - final exampleWithInfo = - await controller.exampleCache.loadExampleInfo(example); - // TODO: setCurrentSdk = false when we do - // per-SDK output and run status. - // Now using true to reset the output and run status. - // https://github.com/apache/beam/issues/23248 - final descriptor = StandardExampleLoadingDescriptor( - sdk: exampleWithInfo.sdk, - path: exampleWithInfo.path, - ); - controller.setExample( - exampleWithInfo, - descriptor: descriptor, - setCurrentSdk: true, - ); + controller.setExampleBase(example); } }, child: Container( diff --git a/playground/frontend/lib/pages/embedded_playground/screen.dart b/playground/frontend/lib/pages/embedded_playground/screen.dart index 0e85c7678aa5..05d39ce3e33f 100644 --- a/playground/frontend/lib/pages/embedded_playground/screen.dart +++ b/playground/frontend/lib/pages/embedded_playground/screen.dart @@ -41,21 +41,19 @@ class EmbeddedPlaygroundScreen extends StatelessWidget { playgroundController: notifier.playgroundController, child: PlaygroundShortcutsManager( playgroundController: notifier.playgroundController, - child: ToastListenerWidget( - child: Scaffold( - appBar: AppBar( - automaticallyImplyLeading: false, - title: const EmbeddedAppBarTitle(), - actions: const [EmbeddedActions()], - ), - body: EmbeddedSplitView( - first: EmbeddedEditor(isEditable: notifier.isEditable), - second: Container( - color: Theme.of(context).backgroundColor, - child: OutputWidget( - playgroundController: notifier.playgroundController, - graphDirection: Axis.horizontal, - ), + child: Scaffold( + appBar: AppBar( + automaticallyImplyLeading: false, + title: const EmbeddedAppBarTitle(), + actions: const [EmbeddedActions()], + ), + body: EmbeddedSplitView( + first: EmbeddedEditor(isEditable: notifier.isEditable), + second: Container( + color: Theme.of(context).backgroundColor, + child: OutputWidget( + playgroundController: notifier.playgroundController, + graphDirection: Axis.horizontal, ), ), ), diff --git a/playground/frontend/lib/pages/embedded_playground/widgets/embedded_editor.dart b/playground/frontend/lib/pages/embedded_playground/widgets/embedded_editor.dart index 94000034f299..dfecdb09de67 100644 --- a/playground/frontend/lib/pages/embedded_playground/widgets/embedded_editor.dart +++ b/playground/frontend/lib/pages/embedded_playground/widgets/embedded_editor.dart @@ -30,7 +30,7 @@ class EmbeddedEditor extends StatelessWidget { final controller = Provider.of(context); final snippetController = controller.snippetEditingController; - if (snippetController == null) { + if (snippetController == null || snippetController.isLoading) { return const LoadingIndicator(); } diff --git a/playground/frontend/lib/pages/standalone_playground/screen.dart b/playground/frontend/lib/pages/standalone_playground/screen.dart index d19960e32c18..1aace1bfb940 100644 --- a/playground/frontend/lib/pages/standalone_playground/screen.dart +++ b/playground/frontend/lib/pages/standalone_playground/screen.dart @@ -95,16 +95,14 @@ class StandalonePlaygroundScreen extends StatelessWidget { ), ], ), - body: ToastListenerWidget( - child: Column( - children: [ - const Expanded(child: PlaygroundPageBody()), - Semantics( - container: true, - child: const PlaygroundPageFooter(), - ), - ], - ), + body: Column( + children: [ + const Expanded(child: PlaygroundPageBody()), + Semantics( + container: true, + child: const PlaygroundPageFooter(), + ), + ], ), ); }, diff --git a/playground/frontend/lib/pages/standalone_playground/widgets/editor_textarea_wrapper.dart b/playground/frontend/lib/pages/standalone_playground/widgets/editor_textarea_wrapper.dart index 65c6e4177c60..475b3c3fb400 100644 --- a/playground/frontend/lib/pages/standalone_playground/widgets/editor_textarea_wrapper.dart +++ b/playground/frontend/lib/pages/standalone_playground/widgets/editor_textarea_wrapper.dart @@ -19,7 +19,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:playground_components/playground_components.dart'; -import 'package:provider/provider.dart'; import '../../../components/playground_run_or_cancel_button.dart'; import '../../../constants/sizes.dart'; @@ -29,81 +28,82 @@ import '../../../modules/examples/components/multifile_popover/multifile_popover /// A code editor with controls stacked above it. class CodeTextAreaWrapper extends StatelessWidget { - const CodeTextAreaWrapper({Key? key}) : super(key: key); + final PlaygroundController controller; + + const CodeTextAreaWrapper({ + required this.controller, + }); @override Widget build(BuildContext context) { - return Consumer( - builder: (context, controller, child) { - if (controller.result?.errorMessage?.isNotEmpty ?? false) { - WidgetsBinding.instance.addPostFrameCallback((_) { - _handleError(context, controller); - }); - } + if (controller.result?.errorMessage?.isNotEmpty ?? false) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _handleError(context, controller); + }); + } - final snippetController = controller.snippetEditingController; + final snippetController = controller.snippetEditingController; - if (snippetController == null) { - return const LoadingIndicator(); - } + if (snippetController == null) { + return const LoadingIndicator(); + } - return Column( - children: [ - Expanded( - child: Stack( - children: [ - Positioned.fill( - child: SnippetEditor( - controller: snippetController, - isEditable: true, - goToContextLine: true, - ), + return Column( + children: [ + Expanded( + child: Stack( + children: [ + Positioned.fill( + child: SnippetEditor( + controller: snippetController, + isEditable: true, + goToContextLine: true, ), - Positioned( - right: kXlSpacing, - top: kXlSpacing, - height: kButtonHeight, - child: Row( - children: [ - if (controller.selectedExample != null) ...[ - if (controller.selectedExample?.isMultiFile ?? false) - Semantics( - container: true, - child: MultifilePopoverButton( - example: controller.selectedExample!, - followerAnchor: Alignment.topRight, - targetAnchor: Alignment.bottomRight, - ), - ), + ), + Positioned( + right: kXlSpacing, + top: kXlSpacing, + height: kButtonHeight, + child: Row( + children: [ + if (controller.selectedExample != null) ...[ + if (controller.selectedExample?.isMultiFile ?? false) Semantics( container: true, - child: DescriptionPopoverButton( + child: MultifilePopoverButton( example: controller.selectedExample!, followerAnchor: Alignment.topRight, targetAnchor: Alignment.bottomRight, ), ), - ], Semantics( container: true, - child: ShareButton( - playgroundController: controller, + child: DescriptionPopoverButton( + example: controller.selectedExample!, + followerAnchor: Alignment.topRight, + targetAnchor: Alignment.bottomRight, ), ), - const SizedBox(width: kLgSpacing), - Semantics( - container: true, - child: const PlaygroundRunOrCancelButton(), - ), ], - ), + Semantics( + container: true, + child: ShareButton( + playgroundController: controller, + ), + ), + const SizedBox(width: kLgSpacing), + Semantics( + container: true, + child: const PlaygroundRunOrCancelButton(), + ), + ], ), - ], - ), + ), + ], ), - ], - ); - }); + ), + ], + ); } void _handleError(BuildContext context, PlaygroundController controller) { diff --git a/playground/frontend/lib/pages/standalone_playground/widgets/playground_page_body.dart b/playground/frontend/lib/pages/standalone_playground/widgets/playground_page_body.dart index 83357abb0cd4..2ce17d79c35e 100644 --- a/playground/frontend/lib/pages/standalone_playground/widgets/playground_page_body.dart +++ b/playground/frontend/lib/pages/standalone_playground/widgets/playground_page_body.dart @@ -20,7 +20,6 @@ import 'package:flutter/material.dart'; import 'package:playground_components/playground_components.dart'; import 'package:provider/provider.dart'; -import '../../../constants/sizes.dart'; import '../../../modules/output/components/output_header/output_placements.dart'; import '../../../modules/output/models/output_placement.dart'; import '../../../modules/output/models/output_placement_state.dart'; @@ -32,13 +31,23 @@ class PlaygroundPageBody extends StatelessWidget { @override Widget build(BuildContext context) { return Consumer2( - builder: (context, outputState, playgroundState, child) { + builder: (context, outputState, controller, child) { + final snippetController = controller.snippetEditingController; + + if (snippetController == null || snippetController.isLoading) { + return const LoadingIndicator(); + } + final output = OutputWidget( graphDirection: outputState.placement.graphDirection, - playgroundController: playgroundState, + playgroundController: controller, trailing: const OutputPlacements(), ); + final codeTextArea = CodeTextAreaWrapper( + controller: controller, + ); + switch (outputState.placement) { case OutputPlacement.bottom: return SplitView( @@ -63,16 +72,4 @@ class PlaygroundPageBody extends StatelessWidget { } }); } - - Widget get codeTextArea => const CodeTextAreaWrapper(); - - Widget getVerticalSeparator(BuildContext context) => Container( - width: kMdSpacing, - color: Theme.of(context).dividerColor, - ); - - Widget getHorizontalSeparator(BuildContext context) => Container( - height: kMdSpacing, - color: Theme.of(context).dividerColor, - ); } diff --git a/playground/frontend/playground_components/LICENSE b/playground/frontend/playground_components/LICENSE deleted file mode 100644 index d64569567334..000000000000 --- a/playground/frontend/playground_components/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. diff --git a/playground/frontend/playground_components/lib/playground_components.dart b/playground/frontend/playground_components/lib/playground_components.dart index 9e08eaff733f..a7903a9c1367 100644 --- a/playground/frontend/playground_components/lib/playground_components.dart +++ b/playground/frontend/playground_components/lib/playground_components.dart @@ -54,6 +54,8 @@ export 'src/repositories/code_repository.dart'; export 'src/repositories/example_client/grpc_example_client.dart'; export 'src/repositories/example_repository.dart'; +export 'src/router/router_delegate.dart'; + export 'src/services/symbols/loaders/yaml.dart'; export 'src/theme/switch_notifier.dart'; diff --git a/playground/frontend/playground_components/lib/src/controllers/example_loaders/standard_example_loader.dart b/playground/frontend/playground_components/lib/src/controllers/example_loaders/standard_example_loader.dart index 7a64b8aa818f..5f180589ea3c 100644 --- a/playground/frontend/playground_components/lib/src/controllers/example_loaders/standard_example_loader.dart +++ b/playground/frontend/playground_components/lib/src/controllers/example_loaders/standard_example_loader.dart @@ -51,16 +51,23 @@ class StandardExampleLoader extends ExampleLoader { } Future _load() async { - final example = await _loadExampleBase(); + try { + final example = await _loadExampleBase(); - if (example == null) { - _completer.completeError('Example not found: $descriptor'); + if (example == null) { + _completer.completeError('Example not found: $descriptor'); + return; + } + + _completer.complete( + exampleCache.loadExampleInfo(example), + ); + + // ignore: avoid_catches_without_on_clauses + } catch (ex, trace) { + _completer.completeError(ex, trace); return; } - - _completer.complete( - exampleCache.loadExampleInfo(example), - ); } Future _loadExampleBase() async { diff --git a/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart index decbd366f93a..6ef86908c161 100644 --- a/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart +++ b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart @@ -30,6 +30,7 @@ import '../models/example_base.dart'; import '../models/example_loading_descriptors/empty_example_loading_descriptor.dart'; import '../models/example_loading_descriptors/example_loading_descriptor.dart'; import '../models/example_loading_descriptors/examples_loading_descriptor.dart'; +import '../models/example_loading_descriptors/standard_example_loading_descriptor.dart'; import '../models/example_loading_descriptors/user_shared_example_loading_descriptor.dart'; import '../models/intents.dart'; import '../models/outputs.dart'; @@ -173,6 +174,43 @@ class PlaygroundController with ChangeNotifier { ); } + Future setExampleBase(ExampleBase exampleBase) async { + final snippetEditingController = _getOrCreateSnippetEditingController( + exampleBase.sdk, + loadDefaultIfNot: false, + ); + + if (!snippetEditingController.lockExampleLoading()) { + return; + } + + notifyListeners(); + + try { + final example = await exampleCache.loadExampleInfo(exampleBase); + // TODO(alexeyinkin): setCurrentSdk = false when we do + // per-SDK output and run status. + // Now using true to reset the output and run status. + // https://github.com/apache/beam/issues/23248 + final descriptor = StandardExampleLoadingDescriptor( + sdk: example.sdk, + path: example.path, + ); + + setExample( + example, + descriptor: descriptor, + setCurrentSdk: true, + ); + + // ignore: avoid_catches_without_on_clauses + } catch (ex) { + snippetEditingController.releaseExampleLoading(); + notifyListeners(); + rethrow; + } + } + void setExample( Example example, { required ExampleLoadingDescriptor descriptor, diff --git a/playground/frontend/playground_components/lib/src/controllers/snippet_editing_controller.dart b/playground/frontend/playground_components/lib/src/controllers/snippet_editing_controller.dart index 195c0bd1e105..451cab943a80 100644 --- a/playground/frontend/playground_components/lib/src/controllers/snippet_editing_controller.dart +++ b/playground/frontend/playground_components/lib/src/controllers/snippet_editing_controller.dart @@ -25,6 +25,7 @@ import '../models/example_loading_descriptors/content_example_loading_descriptor import '../models/example_loading_descriptors/empty_example_loading_descriptor.dart'; import '../models/example_loading_descriptors/example_loading_descriptor.dart'; import '../models/example_view_options.dart'; +import '../models/loading_status.dart'; import '../models/sdk.dart'; import '../services/symbols/symbols_notifier.dart'; @@ -37,6 +38,7 @@ class SnippetEditingController extends ChangeNotifier { ExampleLoadingDescriptor? _descriptor; String _pipelineOptions = ''; bool _isChanged = false; + LoadingStatus _exampleLoadingStatus = LoadingStatus.done; SnippetEditingController({ required this.sdk, @@ -63,6 +65,29 @@ class SnippetEditingController extends ChangeNotifier { } } + /// Attempts to acquire a lock for asynchronous example loading. + /// + /// This prevents race condition for quick example switching + /// and allows to show a loading indicator. + /// + /// Returns whether the lock was acquired. + bool lockExampleLoading() { + switch (_exampleLoadingStatus) { + case LoadingStatus.loading: + return false; + case LoadingStatus.done: + case LoadingStatus.error: + _exampleLoadingStatus = LoadingStatus.loading; + return true; + } + } + + void releaseExampleLoading() { + _exampleLoadingStatus = LoadingStatus.done; + } + + bool get isLoading => _exampleLoadingStatus == LoadingStatus.loading; + void setExample( Example example, { ExampleLoadingDescriptor? descriptor, @@ -71,6 +96,7 @@ class SnippetEditingController extends ChangeNotifier { _selectedExample = example; _pipelineOptions = example.pipelineOptions; _isChanged = false; + releaseExampleLoading(); final viewOptions = example.viewOptions; diff --git a/playground/frontend/playground_components/lib/src/router/router_delegate.dart b/playground/frontend/playground_components/lib/src/router/router_delegate.dart new file mode 100644 index 000000000000..6d7312ee7ba7 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/router/router_delegate.dart @@ -0,0 +1,25 @@ +import 'package:app_state/app_state.dart'; +import 'package:flutter/material.dart'; + +import '../widgets/toasts/toast_listener.dart'; + +/// Wraps [pageStack] in widgets that must be above [Navigator] and can be +/// below [MaterialApp]. +class BeamRouterDelegate extends PageStackRouterDelegate { + BeamRouterDelegate(super.pageStack); + + @override + Widget build(BuildContext context) { + // Overlay: to float toasts. + // ToastListenerWidget: turns notification events into floating toasts. + return Overlay( + initialEntries: [ + OverlayEntry( + builder: (context) => ToastListenerWidget( + child: super.build(context), + ), + ), + ], + ); + } +} diff --git a/playground/frontend/playground_components/lib/src/widgets/toasts/toast_listener.dart b/playground/frontend/playground_components/lib/src/widgets/toasts/toast_listener.dart index 38b76d06008c..9a83f0938976 100644 --- a/playground/frontend/playground_components/lib/src/widgets/toasts/toast_listener.dart +++ b/playground/frontend/playground_components/lib/src/widgets/toasts/toast_listener.dart @@ -27,6 +27,7 @@ import '../../models/toast.dart'; import '../../services/toast_notifier.dart'; import 'toast.dart'; +/// Turns events from [ToastNotifier] into floating [ToastWidget]s. class ToastListenerWidget extends StatefulWidget { final Widget child; diff --git a/playground/frontend/playground_components/pubspec.yaml b/playground/frontend/playground_components/pubspec.yaml index 419b8ceec863..cbb064cba301 100644 --- a/playground/frontend/playground_components/pubspec.yaml +++ b/playground/frontend/playground_components/pubspec.yaml @@ -26,6 +26,7 @@ environment: dependencies: aligned_dialog: ^0.0.6 + app_state: ^0.8.4 collection: ^1.16.0 easy_localization: ^3.0.1 easy_localization_ext: ^0.1.1 @@ -33,7 +34,7 @@ dependencies: enum_map: ^0.2.1 equatable: ^2.0.5 flutter: { sdk: flutter } - flutter_code_editor: ^0.2.1 + flutter_code_editor: ^0.2.3 flutter_markdown: ^0.6.12 flutter_svg: ^1.0.3 fluttertoast: ^8.1.1 diff --git a/playground/frontend/playground_components_dev/LICENSE b/playground/frontend/playground_components_dev/LICENSE deleted file mode 100644 index d64569567334..000000000000 --- a/playground/frontend/playground_components_dev/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. diff --git a/playground/frontend/playground_components_dev/pubspec.yaml b/playground/frontend/playground_components_dev/pubspec.yaml index 0c659c41742a..ba3cc1e234a1 100644 --- a/playground/frontend/playground_components_dev/pubspec.yaml +++ b/playground/frontend/playground_components_dev/pubspec.yaml @@ -26,7 +26,7 @@ environment: dependencies: flutter: { sdk: flutter } - flutter_code_editor: ^0.2.2 + flutter_code_editor: ^0.2.3 flutter_test: { sdk: flutter } http: ^0.13.5 playground_components: { path: ../playground_components } diff --git a/playground/frontend/pubspec.lock b/playground/frontend/pubspec.lock index 604a2a415989..302b01bf1258 100644 --- a/playground/frontend/pubspec.lock +++ b/playground/frontend/pubspec.lock @@ -292,7 +292,7 @@ packages: name: flutter_code_editor url: "https://pub.dartlang.org" source: hosted - version: "0.2.2" + version: "0.2.3" flutter_driver: dependency: transitive description: flutter diff --git a/playground/frontend/pubspec.yaml b/playground/frontend/pubspec.yaml index 8dc7765db698..b2588b0c8ea7 100644 --- a/playground/frontend/pubspec.yaml +++ b/playground/frontend/pubspec.yaml @@ -53,7 +53,7 @@ dependencies: dev_dependencies: build_runner: ^2.1.4 fake_async: ^1.3.0 - flutter_code_editor: ^0.2.2 + flutter_code_editor: ^0.2.3 flutter_lints: ^2.0.1 flutter_test: { sdk: flutter } integration_test: { sdk: flutter } From 9bb026867a0a2d4268b8596e3c9b982c9425e5fc Mon Sep 17 00:00:00 2001 From: Alexey Inkin Date: Wed, 21 Dec 2022 19:25:32 +0400 Subject: [PATCH 05/52] Add a missing license header (#24730) --- .../lib/src/router/router_delegate.dart | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/playground/frontend/playground_components/lib/src/router/router_delegate.dart b/playground/frontend/playground_components/lib/src/router/router_delegate.dart index 6d7312ee7ba7..41857979d9e1 100644 --- a/playground/frontend/playground_components/lib/src/router/router_delegate.dart +++ b/playground/frontend/playground_components/lib/src/router/router_delegate.dart @@ -1,3 +1,21 @@ +/* + * 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:app_state/app_state.dart'; import 'package:flutter/material.dart'; From e888e270b8d6c1f9a20358091d39d54474ef82e1 Mon Sep 17 00:00:00 2001 From: alexeyinkin Date: Wed, 28 Dec 2022 16:00:24 +0400 Subject: [PATCH 06/52] Integration test for changing SDK and running code (#24779) (#382) * Integration test for changing SDK and running code (#24779) * Rename an integration test (#24779) * Use enum to switch SDK in integration test (#24779) * Find SDK in a dropdown by key (#24779) * Add a TODO (#24779) * Fix exports (#24779) --- playground/frontend/build.gradle | 6 +- .../integration_test/{ => common}/common.dart | 12 -- .../common_finders.dart} | 42 ++--- ...tandalone_change_example_sdk_run_test.dart | 174 ++++++++++++++++++ .../dropdown_button/dropdown_button.dart | 4 +- .../modules/sdk/components/sdk_selector.dart | 21 +-- .../sdk/components/sdk_selector_row.dart | 5 +- .../lib/playground_components.dart | 3 + .../playground_components/pubspec.yaml | 2 +- .../lib/playground_components_dev.dart | 5 + .../lib/src/code.dart | 30 +-- .../lib/src/common_finders.dart | 54 ++++++ .../lib/src/example_names.dart | 1 + .../lib/src/example_outputs.dart | 26 +++ .../lib/src/example_paths.dart | 5 + .../lib/src/examples.dart | 5 +- .../lib/src/expect.dart | 36 ++++ .../lib/src/finder.dart | 41 +++++ .../lib/src/string.dart | 33 ++++ .../lib/src/widget_tester.dart | 20 +- .../playground_components_dev/pubspec.yaml | 3 +- playground/frontend/pubspec.lock | 2 +- playground/frontend/pubspec.yaml | 2 +- 23 files changed, 449 insertions(+), 83 deletions(-) rename playground/frontend/integration_test/{ => common}/common.dart (74%) rename playground/frontend/integration_test/{standalone_change_example_test.dart => common/common_finders.dart} (54%) create mode 100644 playground/frontend/integration_test/standalone_change_example_sdk_run_test.dart create mode 100644 playground/frontend/playground_components_dev/lib/src/common_finders.dart create mode 100644 playground/frontend/playground_components_dev/lib/src/example_outputs.dart create mode 100644 playground/frontend/playground_components_dev/lib/src/expect.dart create mode 100644 playground/frontend/playground_components_dev/lib/src/finder.dart create mode 100644 playground/frontend/playground_components_dev/lib/src/string.dart diff --git a/playground/frontend/build.gradle b/playground/frontend/build.gradle index c4de9c5726dd..6b4c89d99a9b 100644 --- a/playground/frontend/build.gradle +++ b/playground/frontend/build.gradle @@ -183,11 +183,11 @@ ext.deleteFilesByRegExp = { re -> } tasks.register("integrationTest") { - dependsOn("integrationTest_standalone_change_example") + dependsOn("integrationTest_standalone_change_example_sdk_run") } -tasks.register("integrationTest_standalone_change_example") { - runIntegrationTest("standalone_change_example", "/") +tasks.register("integrationTest_standalone_change_example_sdk_run") { + runIntegrationTest("standalone_change_example_sdk_run", "/") } void runIntegrationTest(String path, String url) { diff --git a/playground/frontend/integration_test/common.dart b/playground/frontend/integration_test/common/common.dart similarity index 74% rename from playground/frontend/integration_test/common.dart rename to playground/frontend/integration_test/common/common.dart index 92162fbede7c..0e90e5325996 100644 --- a/playground/frontend/integration_test/common.dart +++ b/playground/frontend/integration_test/common/common.dart @@ -16,22 +16,10 @@ * limitations under the License. */ -import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:playground/main.dart' as app; -import 'package:playground/modules/examples/example_selector.dart'; Future init(WidgetTester wt) async { app.main(); await wt.pumpAndSettle(); } - -extension CommonFindersExtension on CommonFinders { - Finder exampleSelector() { - return byType(ExampleSelector); - } - - Finder exampleItemInDropdown(String name) { - return widgetWithText(GestureDetector, name); - } -} diff --git a/playground/frontend/integration_test/standalone_change_example_test.dart b/playground/frontend/integration_test/common/common_finders.dart similarity index 54% rename from playground/frontend/integration_test/standalone_change_example_test.dart rename to playground/frontend/integration_test/common/common_finders.dart index 3ae2244ab1f4..1491486fe5ea 100644 --- a/playground/frontend/integration_test/standalone_change_example_test.dart +++ b/playground/frontend/integration_test/common/common_finders.dart @@ -16,34 +16,28 @@ * limitations under the License. */ +import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:integration_test/integration_test.dart'; +import 'package:playground/modules/examples/example_selector.dart'; +import 'package:playground/modules/sdk/components/sdk_selector.dart'; +import 'package:playground/modules/sdk/components/sdk_selector_row.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:playground_components_dev/playground_components_dev.dart'; -import 'common.dart'; +extension CommonFindersExtension on CommonFinders { + Finder exampleItemInDropdown(String name) { + return widgetWithText(GestureDetector, name); + } -void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + Finder exampleSelector() { + return byType(ExampleSelector); + } - group('Integration.', () { - testWidgets('Change example', (WidgetTester wt) async { - await init(wt); + Finder sdkItemInDropdown(Sdk sdk) { + return find.byType(SdkSelectorRow).and(find.byKey(ValueKey(sdk))); + } - expect( - wt.findOneCodeController().lastTextSpan!.toPlainText(), - await Examples.getJavaVisibleText(ExamplePaths.javaMinimalWordCount), - ); - - await wt.tap(find.exampleSelector()); - await wt.pumpAndSettle(); - - await wt.tap(find.exampleItemInDropdown(ExampleNames.aggregationMax)); - await wt.pumpAndSettle(); - - expect( - wt.findOneCodeController().lastTextSpan!.toPlainText(), - await Examples.getJavaVisibleText(ExamplePaths.javaAggregationMax), - ); - }); - }); + Finder sdkSelector() { + return byType(SDKSelector); + } } diff --git a/playground/frontend/integration_test/standalone_change_example_sdk_run_test.dart b/playground/frontend/integration_test/standalone_change_example_sdk_run_test.dart new file mode 100644 index 000000000000..f7b601c877ea --- /dev/null +++ b/playground/frontend/integration_test/standalone_change_example_sdk_run_test.dart @@ -0,0 +1,174 @@ +/* + * 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:highlight/languages/java.dart'; +import 'package:highlight/languages/python.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:playground_components/playground_components.dart'; +import 'package:playground_components_dev/playground_components_dev.dart'; + +import 'common/common.dart'; +import 'common/common_finders.dart'; + +const _outputPrefix = 'The processing has started\n'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + /// Runs and expects that the execution is as fast as it should be for cache. + Future runExpectCached(WidgetTester wt) async { + final dateTimeStart = DateTime.now(); + + await wt.tap(find.runOrCancelButton()); + await wt.pumpAndSettle(); + + expect( + DateTime.now().difference(dateTimeStart), + lessThan(const Duration(milliseconds: 2000)), + ); + } + + Future expectJavaMinimalWordCount(WidgetTester wt) async { + expect( + wt.findOneCodeController().lastTextSpan!.toPlainText().isAsIfCutFrom( + await Examples.getVisibleTextByPath( + ExamplePaths.javaMinimalWordCount, + java, + ), + ), + true, + ); + + expect(find.graphTab(), findsOneWidget); + expect(find.resultTab(), findsOneWidget); + expect(wt.findOutputTabController().index, 0); + } + + Future changeToJavaAggregationMax(WidgetTester wt) async { + await wt.tap(find.exampleSelector()); + await wt.pumpAndSettle(); + + await wt.tap(find.exampleItemInDropdown(ExampleNames.aggregationMax)); + await wt.pumpAndSettle(); + + expect( + wt.findOneCodeController().lastTextSpan!.toPlainText().isAsIfCutFrom( + await Examples.getVisibleTextByPath( + ExamplePaths.javaAggregationMax, + java, + ), + ), + true, + ); + } + + Future runExpectJavaAggregationMax(WidgetTester wt) async { + await runExpectCached(wt); + expectOutputEndsWith(ExampleOutputs.javaAggregationMaxTail, wt); + } + + Future runCustomJava(WidgetTester wt) async { + const text = 'OK'; + const code = ''' +public class MyClass { + public static void main(String[] args) { + System.out.print("$text"); + } +} +'''; + + await wt.enterText(find.codeField(), code); + await wt.pumpAndSettle(); + + await wt.tap(find.runOrCancelButton()); + await wt.pumpAndSettle(); + + expectOutput('$_outputPrefix$text', wt); + } + + Future switchToPython(WidgetTester wt) async { + await wt.tap(find.sdkSelector()); + await wt.pumpAndSettle(); + + await wt.tap(find.sdkItemInDropdown(Sdk.python)); + await wt.pumpAndSettle(); + + expect( + wt.findOneCodeController().lastTextSpan!.toPlainText().isAsIfCutFrom( + await Examples.getVisibleTextByPath( + ExamplePaths.pythonMinimalWordCountWithMetrics, + python, + ), + ), + true, + ); + } + + Future changeToPythonAggregationMean(WidgetTester wt) async { + await wt.tap(find.exampleSelector()); + await wt.pumpAndSettle(); + + await wt.tap(find.exampleItemInDropdown(ExampleNames.aggregationMean)); + await wt.pumpAndSettle(); + + // Cannot test this because the DB examples differ from GitHub now. + // TODO(alexeyinkin): Uncomment when DB is up-to-date. + // expect( + // wt.findOneCodeController().lastTextSpan!.toPlainText().isAsIfCutFrom( + // await Examples.getVisibleTextByPath( + // ExamplePaths.pythonAggregationMean, + // python, + // ), + // ), + // true, + // ); + } + + Future runExpectPythonAggregationMean(WidgetTester wt) async { + await runExpectCached(wt); + expectOutputContains(ExampleOutputs.pythonAggregationMeanContains, wt); + } + + Future runCustomPython(WidgetTester wt) async { + const text = 'OK'; + const code = 'print("$text", end="")'; + + await wt.enterText(find.codeField(), code); + await wt.pumpAndSettle(); + + await wt.tap(find.runOrCancelButton()); + await wt.pumpAndSettle(); + + expectOutput('$_outputPrefix$text', wt); + } + + testWidgets('Change example, change SDK, run', (WidgetTester wt) async { + await init(wt); + + await expectJavaMinimalWordCount(wt); + await changeToJavaAggregationMax(wt); + await runExpectJavaAggregationMax(wt); + await runCustomJava(wt); + + await switchToPython(wt); + await changeToPythonAggregationMean(wt); + await runExpectPythonAggregationMean(wt); + await runCustomPython(wt); + }); +} diff --git a/playground/frontend/lib/components/dropdown_button/dropdown_button.dart b/playground/frontend/lib/components/dropdown_button/dropdown_button.dart index c2aff6f2f950..17a0d692f92d 100644 --- a/playground/frontend/lib/components/dropdown_button/dropdown_button.dart +++ b/playground/frontend/lib/components/dropdown_button/dropdown_button.dart @@ -37,7 +37,7 @@ enum DropdownAlignment { class AppDropdownButton extends StatefulWidget { final Widget buttonText; final Widget Function(void Function()) createDropdown; - final double height; + final double? height; final double width; final Widget? leading; final bool showArrow; @@ -47,8 +47,8 @@ class AppDropdownButton extends StatefulWidget { super.key, required this.buttonText, required this.createDropdown, - required this.height, required this.width, + this.height, this.leading, this.showArrow = true, this.dropdownAlign = DropdownAlignment.left, diff --git a/playground/frontend/lib/modules/sdk/components/sdk_selector.dart b/playground/frontend/lib/modules/sdk/components/sdk_selector.dart index c4c1656a1748..8bba9ed7e6aa 100644 --- a/playground/frontend/lib/modules/sdk/components/sdk_selector.dart +++ b/playground/frontend/lib/modules/sdk/components/sdk_selector.dart @@ -18,26 +18,23 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:playground/components/dropdown_button/dropdown_button.dart'; -import 'package:playground/constants/sizes.dart'; -import 'package:playground/modules/sdk/components/sdk_selector_row.dart'; import 'package:playground_components/playground_components.dart'; import 'package:provider/provider.dart'; -const kEmptyExampleName = 'Catalog'; +import '../../../components/dropdown_button/dropdown_button.dart'; +import '../../../constants/sizes.dart'; +import 'sdk_selector_row.dart'; -const double kWidth = 150; -const double kHeight = 172; +const double _width = 150; class SDKSelector extends StatelessWidget { - final Sdk? value; final ValueChanged onChanged; + final Sdk? value; const SDKSelector({ - Key? key, - required this.value, required this.onChanged, - }) : super(key: key); + required this.value, + }); @override Widget build(BuildContext context) { @@ -68,10 +65,10 @@ class SDKSelector extends StatelessWidget { ), ); }), + const SizedBox(height: kMdSpacing), ], ), - width: kWidth, - height: kHeight, + width: _width, ), ), ); diff --git a/playground/frontend/lib/modules/sdk/components/sdk_selector_row.dart b/playground/frontend/lib/modules/sdk/components/sdk_selector_row.dart index 7993723bf25d..1039078b15e6 100644 --- a/playground/frontend/lib/modules/sdk/components/sdk_selector_row.dart +++ b/playground/frontend/lib/modules/sdk/components/sdk_selector_row.dart @@ -25,11 +25,10 @@ class SdkSelectorRow extends StatelessWidget { final Sdk sdk; final VoidCallback onSelect; - const SdkSelectorRow({ - Key? key, + SdkSelectorRow({ required this.sdk, required this.onSelect, - }) : super(key: key); + }) : super(key: ValueKey(sdk)); @override Widget build(BuildContext context) { diff --git a/playground/frontend/playground_components/lib/playground_components.dart b/playground/frontend/playground_components/lib/playground_components.dart index a7903a9c1367..007a3ec29b64 100644 --- a/playground/frontend/playground_components/lib/playground_components.dart +++ b/playground/frontend/playground_components/lib/playground_components.dart @@ -73,6 +73,9 @@ export 'src/widgets/loading_error.dart'; export 'src/widgets/loading_indicator.dart'; export 'src/widgets/logo.dart'; export 'src/widgets/output/output.dart'; +export 'src/widgets/output/output_area.dart'; +export 'src/widgets/output/output_tab.dart'; +export 'src/widgets/output/output_tabs.dart'; export 'src/widgets/reset_button.dart'; export 'src/widgets/run_or_cancel_button.dart'; export 'src/widgets/shortcut_tooltip.dart'; diff --git a/playground/frontend/playground_components/pubspec.yaml b/playground/frontend/playground_components/pubspec.yaml index cbb064cba301..d3591304b61b 100644 --- a/playground/frontend/playground_components/pubspec.yaml +++ b/playground/frontend/playground_components/pubspec.yaml @@ -34,7 +34,7 @@ dependencies: enum_map: ^0.2.1 equatable: ^2.0.5 flutter: { sdk: flutter } - flutter_code_editor: ^0.2.3 + flutter_code_editor: ^0.2.4 flutter_markdown: ^0.6.12 flutter_svg: ^1.0.3 fluttertoast: ^8.1.1 diff --git a/playground/frontend/playground_components_dev/lib/playground_components_dev.dart b/playground/frontend/playground_components_dev/lib/playground_components_dev.dart index 88b05483a40b..19a653be0add 100644 --- a/playground/frontend/playground_components_dev/lib/playground_components_dev.dart +++ b/playground/frontend/playground_components_dev/lib/playground_components_dev.dart @@ -16,7 +16,12 @@ * limitations under the License. */ +export 'src/common_finders.dart'; export 'src/example_names.dart'; +export 'src/example_outputs.dart'; export 'src/example_paths.dart'; export 'src/examples.dart'; +export 'src/expect.dart'; +export 'src/finder.dart'; +export 'src/string.dart'; export 'src/widget_tester.dart'; diff --git a/playground/frontend/playground_components_dev/lib/src/code.dart b/playground/frontend/playground_components_dev/lib/src/code.dart index e0029b73ebb2..2f85ac9f029d 100644 --- a/playground/frontend/playground_components_dev/lib/src/code.dart +++ b/playground/frontend/playground_components_dev/lib/src/code.dart @@ -16,27 +16,17 @@ * limitations under the License. */ -extension StringCodeExtension on String { - /// Returns the visible text mimicking the flutter_code_editor's folding of - /// license. - String foldJavaLicense() { - return replaceFirstMapped( - RegExp(r'^(/\*)(.*?\*/)(.*)$', dotAll: true), - (m) => '${m[1]}${m[3]}', - ); - } +import 'package:flutter_code_editor/flutter_code_editor.dart'; +import 'package:highlight/highlight_core.dart'; - /// Returns the visible text mimicking the flutter_code_editor's folding of - /// sequential imports. - String foldJavaImports() { - final packageRegExp = RegExp('^package .*?\n', multiLine: true); - final cutStart = indexOf(packageRegExp) + - (packageRegExp.firstMatch(this)?[0]?.length ?? 0); +String foldLicenseAndImports(String text, Mode language) { + final controller = CodeController( + text: text, + language: language, + ); - final importRegExp = RegExp('^import .*?\n', multiLine: true); - final lastImportStart = lastIndexOf(importRegExp); + controller.foldCommentAtLineZero(); + controller.foldImports(); - return substring(0, cutStart) + - substring(lastImportStart).replaceFirst(importRegExp, ''); - } + return controller.text; } diff --git a/playground/frontend/playground_components_dev/lib/src/common_finders.dart b/playground/frontend/playground_components_dev/lib/src/common_finders.dart new file mode 100644 index 000000000000..7c01f7f29075 --- /dev/null +++ b/playground/frontend/playground_components_dev/lib/src/common_finders.dart @@ -0,0 +1,54 @@ +/* + * 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:flutter_code_editor/flutter_code_editor.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:playground_components/playground_components.dart'; + +extension CommonFindersExtension on CommonFinders { + Finder codeField() { + return byType(CodeField); + } + + Finder graphTab() { + // TODO(alexeyinkin): Use keys when output tabs get to use enum, https://github.com/apache/beam/issues/22663 + return widgetWithText(OutputTab, 'Graph'); + } + + Finder outputArea() { + return byType(OutputArea); + } + + Finder outputSelectableText() { + final outputArea = find.outputArea(); + return find.descendant( + of: outputArea, + matching: find.byType(SelectableText), + ); + } + + Finder resultTab() { + // TODO(alexeyinkin): Use keys when output tabs get to use enum, https://github.com/apache/beam/issues/22663 + return widgetWithText(OutputTab, 'Result'); + } + + Finder runOrCancelButton() { + return byType(RunOrCancelButton); + } +} diff --git a/playground/frontend/playground_components_dev/lib/src/example_names.dart b/playground/frontend/playground_components_dev/lib/src/example_names.dart index 632044005926..204a6578ecd5 100644 --- a/playground/frontend/playground_components_dev/lib/src/example_names.dart +++ b/playground/frontend/playground_components_dev/lib/src/example_names.dart @@ -18,4 +18,5 @@ class ExampleNames { static const aggregationMax = 'AggregationMax'; + static const aggregationMean = 'AggregationMean'; } diff --git a/playground/frontend/playground_components_dev/lib/src/example_outputs.dart b/playground/frontend/playground_components_dev/lib/src/example_outputs.dart new file mode 100644 index 000000000000..5a548be683c8 --- /dev/null +++ b/playground/frontend/playground_components_dev/lib/src/example_outputs.dart @@ -0,0 +1,26 @@ +/* + * 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. + */ + +class ExampleOutputs { + static const javaAggregationMaxTail = 'INFO: 10\n'; + + static const pythonAggregationMeanContains = + '16 [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]'; + + static const pythonWordCountWithMetricsTail = 'average word length: 4\n'; +} diff --git a/playground/frontend/playground_components_dev/lib/src/example_paths.dart b/playground/frontend/playground_components_dev/lib/src/example_paths.dart index e7e78af5efc9..f0f554c79226 100644 --- a/playground/frontend/playground_components_dev/lib/src/example_paths.dart +++ b/playground/frontend/playground_components_dev/lib/src/example_paths.dart @@ -21,4 +21,9 @@ class ExamplePaths { '/learning/katas/java/Common Transforms/Aggregation/Max/src/org/apache/beam/learning/katas/commontransforms/aggregation/max/Task.java'; static const javaMinimalWordCount = '/examples/java/src/main/java/org/apache/beam/examples/MinimalWordCount.java'; + + static const pythonAggregationMean = + '/learning/katas/python/Common Transforms/Aggregation/Mean/task.py'; + static const pythonMinimalWordCountWithMetrics = + '/sdks/python/apache_beam/examples/wordcount_with_metrics.py'; } diff --git a/playground/frontend/playground_components_dev/lib/src/examples.dart b/playground/frontend/playground_components_dev/lib/src/examples.dart index 8b624bc80e54..c558133d742e 100644 --- a/playground/frontend/playground_components_dev/lib/src/examples.dart +++ b/playground/frontend/playground_components_dev/lib/src/examples.dart @@ -16,6 +16,7 @@ * limitations under the License. */ +import 'package:highlight/highlight_core.dart'; import 'package:http/http.dart' as http; import 'code.dart'; @@ -23,12 +24,12 @@ import 'code.dart'; class Examples { static const _repoAndBranch = 'apache/beam/master'; - static Future getJavaVisibleText(String path) async { + static Future getVisibleTextByPath(String path, Mode language) async { final uri = Uri.parse('https://raw.githubusercontent.com/$_repoAndBranch$path'); final response = await http.get(uri); final content = response.body; - return content.foldJavaLicense().foldJavaImports(); + return foldLicenseAndImports(content, language); } } diff --git a/playground/frontend/playground_components_dev/lib/src/expect.dart b/playground/frontend/playground_components_dev/lib/src/expect.dart new file mode 100644 index 000000000000..34e338dbec7b --- /dev/null +++ b/playground/frontend/playground_components_dev/lib/src/expect.dart @@ -0,0 +1,36 @@ +/* + * 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 'widget_tester.dart'; + +void expectOutput(String text, WidgetTester wt) { + final actualText = wt.findOutputText(); + expect(actualText, text); +} + +void expectOutputContains(String text, WidgetTester wt) { + final actualText = wt.findOutputText(); + expect(actualText, contains(text)); +} + +void expectOutputEndsWith(String text, WidgetTester wt) { + final actualText = wt.findOutputText(); + expect(actualText, endsWith(text)); +} diff --git a/playground/frontend/playground_components_dev/lib/src/finder.dart b/playground/frontend/playground_components_dev/lib/src/finder.dart new file mode 100644 index 000000000000..72d2dd86a389 --- /dev/null +++ b/playground/frontend/playground_components_dev/lib/src/finder.dart @@ -0,0 +1,41 @@ +/* + * 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/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +extension FinderExtension on Finder { + // TODO(alexeyinkin): Push to Flutter or wait for them to make their own, https://github.com/flutter/flutter/issues/117675 + Finder and(Finder another) { + return _AndFinder(this, another); + } +} + +class _AndFinder extends ChainedFinder { + _AndFinder(super.parent, this.another); + + final Finder another; + + @override + String get description => '${parent.description} AND ${another.description}'; + + @override + Iterable filter(Iterable parentCandidates) { + return another.apply(parentCandidates); + } +} diff --git a/playground/frontend/playground_components_dev/lib/src/string.dart b/playground/frontend/playground_components_dev/lib/src/string.dart new file mode 100644 index 000000000000..7f300aeeb980 --- /dev/null +++ b/playground/frontend/playground_components_dev/lib/src/string.dart @@ -0,0 +1,33 @@ +/* + * 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/widgets.dart'; +import 'package:flutter_code_editor/flutter_code_editor.dart'; + +extension StringExtension on String { + /// Whether this is different from [another] only by cutting a single range + /// of zero or more characters. + bool isAsIfCutFrom(String another) { + final range = getChangedRange( + another, + attributeChangeTo: TextAffinity.downstream, + ); + + return range.isCollapsed; + } +} diff --git a/playground/frontend/playground_components_dev/lib/src/widget_tester.dart b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart index 16a42e41d728..9aed823511ed 100644 --- a/playground/frontend/playground_components_dev/lib/src/widget_tester.dart +++ b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart @@ -16,14 +16,32 @@ * limitations under the License. */ +import 'package:flutter/material.dart'; import 'package:flutter_code_editor/flutter_code_editor.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:playground_components/playground_components.dart'; + +import 'common_finders.dart'; extension WidgetTesterExtension on WidgetTester { CodeController findOneCodeController() { - final codeField = find.byType(CodeField); + final codeField = find.codeField(); expect(codeField, findsOneWidget); return widget(codeField).controller; } + + TabController findOutputTabController() { + final outputTabs = find.byType(OutputTabs); + expect(outputTabs, findsOneWidget); + + return widget(outputTabs).tabController; + } + + String? findOutputText() { + final selectableText = find.outputSelectableText(); + expect(selectableText, findsOneWidget); + + return widget(selectableText).data; + } } diff --git a/playground/frontend/playground_components_dev/pubspec.yaml b/playground/frontend/playground_components_dev/pubspec.yaml index ba3cc1e234a1..66878bdaafa7 100644 --- a/playground/frontend/playground_components_dev/pubspec.yaml +++ b/playground/frontend/playground_components_dev/pubspec.yaml @@ -26,8 +26,9 @@ environment: dependencies: flutter: { sdk: flutter } - flutter_code_editor: ^0.2.3 + flutter_code_editor: ^0.2.4 flutter_test: { sdk: flutter } + highlight: ^0.7.0 http: ^0.13.5 playground_components: { path: ../playground_components } total_lints: ^2.18.0 diff --git a/playground/frontend/pubspec.lock b/playground/frontend/pubspec.lock index 302b01bf1258..db8ae40f1114 100644 --- a/playground/frontend/pubspec.lock +++ b/playground/frontend/pubspec.lock @@ -292,7 +292,7 @@ packages: name: flutter_code_editor url: "https://pub.dartlang.org" source: hosted - version: "0.2.3" + version: "0.2.4" flutter_driver: dependency: transitive description: flutter diff --git a/playground/frontend/pubspec.yaml b/playground/frontend/pubspec.yaml index b2588b0c8ea7..71891c778a4a 100644 --- a/playground/frontend/pubspec.yaml +++ b/playground/frontend/pubspec.yaml @@ -53,7 +53,7 @@ dependencies: dev_dependencies: build_runner: ^2.1.4 fake_async: ^1.3.0 - flutter_code_editor: ^0.2.3 + flutter_code_editor: ^0.2.4 flutter_lints: ^2.0.1 flutter_test: { sdk: flutter } integration_test: { sdk: flutter } From 9c16c66119d10c1b86dbdf0d660e3442ff522f3a Mon Sep 17 00:00:00 2001 From: alexeyinkin Date: Wed, 4 Jan 2023 13:09:36 +0400 Subject: [PATCH 07/52] Issue24779 integration changing sdk from 24370 (#387) * Integration test for changing SDK and running code (#24779) * Rename an integration test (#24779) * Use enum to switch SDK in integration test (#24779) * Find SDK in a dropdown by key (#24779) * Add a TODO (#24779) * Fix exports (#24779) * Integration tests miscellaneous UI (#383) * miscellaneous ui integration tests * reverted pubspec.lock * gradle tasks ordered alhpabetically * integration tests refactoring * clean code * integration tests miscellaneous ui fix pr * rename method * added layout adaptivity * A minor cleanup (#24779) Co-authored-by: Dmitry Repin --- playground/frontend/build.gradle | 5 + .../integration_test/common/common.dart | 14 +++ .../common/common_finders.dart | 56 ++++++++++ .../miscellaneous_ui/description_test.dart | 47 ++++++++ .../enjoy_playground_test.dart | 102 +++++++++++++++++ .../output_placement_test.dart | 45 ++++++++ .../miscellaneous_ui/resize_output_test.dart | 103 ++++++++++++++++++ .../shortcuts_modal_test.dart | 48 ++++++++ .../toggle_brightness_mode_test.dart | 41 +++++++ .../standalone_miscellaneous_ui_test.dart | 45 ++++++++ .../modules/analytics/analytics_event.dart | 44 ++++++++ .../modules/analytics/analytics_service.dart | 3 + .../analytics/google_analytics_service.dart | 12 ++ .../output_header/output_placements.dart | 1 + .../shortcuts/components/shortcut_row.dart | 20 ++-- .../shortcuts/components/shortcuts_modal.dart | 1 + .../feedback/feedback_dropdown_content.dart | 10 +- .../widgets/feedback/playground_feedback.dart | 8 +- .../lib/src/theme/theme.dart | 12 +- .../lib/src/widgets/output/output.dart | 10 +- .../lib/src/widgets/split_view.dart | 20 ++-- .../lib/src/common_finders.dart | 12 ++ .../lib/src/widget_tester.dart | 6 + .../playground_components_dev/pubspec.yaml | 1 + 24 files changed, 634 insertions(+), 32 deletions(-) create mode 100644 playground/frontend/integration_test/miscellaneous_ui/description_test.dart create mode 100644 playground/frontend/integration_test/miscellaneous_ui/enjoy_playground_test.dart create mode 100644 playground/frontend/integration_test/miscellaneous_ui/output_placement_test.dart create mode 100644 playground/frontend/integration_test/miscellaneous_ui/resize_output_test.dart create mode 100644 playground/frontend/integration_test/miscellaneous_ui/shortcuts_modal_test.dart create mode 100644 playground/frontend/integration_test/miscellaneous_ui/toggle_brightness_mode_test.dart create mode 100644 playground/frontend/integration_test/standalone_miscellaneous_ui_test.dart create mode 100644 playground/frontend/lib/modules/analytics/analytics_event.dart diff --git a/playground/frontend/build.gradle b/playground/frontend/build.gradle index 6b4c89d99a9b..4de0f867b363 100644 --- a/playground/frontend/build.gradle +++ b/playground/frontend/build.gradle @@ -184,12 +184,17 @@ ext.deleteFilesByRegExp = { re -> tasks.register("integrationTest") { dependsOn("integrationTest_standalone_change_example_sdk_run") + dependsOn("integrationTest_standalone_miscellaneous_ui") } tasks.register("integrationTest_standalone_change_example_sdk_run") { runIntegrationTest("standalone_change_example_sdk_run", "/") } +tasks.register("integrationTest_standalone_miscellaneous_ui") { + runIntegrationTest("standalone_miscellaneous_ui", "/") +} + void runIntegrationTest(String path, String url) { exec { executable("flutter") diff --git a/playground/frontend/integration_test/common/common.dart b/playground/frontend/integration_test/common/common.dart index 0e90e5325996..83575a99cb6c 100644 --- a/playground/frontend/integration_test/common/common.dart +++ b/playground/frontend/integration_test/common/common.dart @@ -23,3 +23,17 @@ Future init(WidgetTester wt) async { app.main(); await wt.pumpAndSettle(); } + +void expectHasDescendant(Finder ancestor, Finder descendant) { + expect( + find.descendant(of: ancestor, matching: descendant), + findsOneWidget, + ); +} + +void expectSimilar(double a, double b) { + Matcher closeToFraction(num value, double fraction) => + closeTo(value, value * fraction); + Matcher onePerCentTolerance(num value) => closeToFraction(value, 0.01); + expect(a, onePerCentTolerance(b)); +} diff --git a/playground/frontend/integration_test/common/common_finders.dart b/playground/frontend/integration_test/common/common_finders.dart index 1491486fe5ea..7d906ee055ad 100644 --- a/playground/frontend/integration_test/common/common_finders.dart +++ b/playground/frontend/integration_test/common/common_finders.dart @@ -18,13 +18,37 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:playground/modules/examples/components/description_popover/description_popover.dart'; +import 'package:playground/modules/examples/components/description_popover/description_popover_button.dart'; import 'package:playground/modules/examples/example_selector.dart'; import 'package:playground/modules/sdk/components/sdk_selector.dart'; import 'package:playground/modules/sdk/components/sdk_selector_row.dart'; +import 'package:playground/modules/shortcuts/components/shortcuts_modal.dart'; +import 'package:playground/pages/standalone_playground/widgets/editor_textarea_wrapper.dart'; +import 'package:playground/pages/standalone_playground/widgets/feedback/feedback_dropdown_content.dart'; +import 'package:playground/pages/standalone_playground/widgets/feedback/playground_feedback.dart'; +import 'package:playground/pages/standalone_playground/widgets/more_actions.dart'; import 'package:playground_components/playground_components.dart'; +import 'package:playground_components/src/widgets/drag_handle.dart'; import 'package:playground_components_dev/playground_components_dev.dart'; extension CommonFindersExtension on CommonFinders { + Finder codeTextAreaWrapper() { + return byType(CodeTextAreaWrapper); + } + + Finder descriptionPopoverButton() { + return byType(DescriptionPopoverButton); + } + + Finder descriptionPopover() { + return byType(DescriptionPopover); + } + + Finder dragHandle() { + return byType(DragHandle); + } + Finder exampleItemInDropdown(String name) { return widgetWithText(GestureDetector, name); } @@ -33,6 +57,34 @@ extension CommonFindersExtension on CommonFinders { return byType(ExampleSelector); } + Finder feedbackDropdownCancelButton() { + return find.byKey(FeedbackDropdownContent.cancelButtonKey); + } + + Finder feedbackDropdownContent() { + return byType(FeedbackDropdownContent); + } + + Finder feedbackDropdownSendButton() { + return find.byKey(FeedbackDropdownContent.sendButtonKey); + } + + Finder feedbackDropdownTextField() { + return find.byKey(FeedbackDropdownContent.textFieldKey); + } + + Finder feedbackThumbDown() { + return find.byKey(PlaygroundFeedback.thumbDownKey); + } + + Finder feedbackThumbUp() { + return find.byKey(PlaygroundFeedback.thumbUpKey); + } + + Finder moreActions() { + return byType(MoreActions); + } + Finder sdkItemInDropdown(Sdk sdk) { return find.byType(SdkSelectorRow).and(find.byKey(ValueKey(sdk))); } @@ -40,4 +92,8 @@ extension CommonFindersExtension on CommonFinders { Finder sdkSelector() { return byType(SDKSelector); } + + Finder shortcutsModal() { + return byType(ShortcutsModal); + } } diff --git a/playground/frontend/integration_test/miscellaneous_ui/description_test.dart b/playground/frontend/integration_test/miscellaneous_ui/description_test.dart new file mode 100644 index 000000000000..17c9e29df337 --- /dev/null +++ b/playground/frontend/integration_test/miscellaneous_ui/description_test.dart @@ -0,0 +1,47 @@ +/* + * 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/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:playground_components_dev/playground_components_dev.dart'; + +import '../common/common.dart'; +import '../common/common_finders.dart'; + +Future checkDescription(WidgetTester wt) async { + await wt.tap(find.descriptionPopoverButton()); + await wt.pumpAndSettle(); + + expect(find.descriptionPopover(), findsOneWidget); + + final example = wt.findPlaygroundController().selectedExample!; + + expectHasDescendant(find.descriptionPopover(), find.text(example.name)); + expectHasDescendant( + find.descriptionPopover(), + find.text(example.description), + ); + + // //TODO Check contains github and colab links, + // //when https://github.com/apache/beam/pull/24820 will be merged + + await wt.sendKeyEvent(LogicalKeyboardKey.escape); + await wt.pumpAndSettle(); + + expect(find.descriptionPopover(), findsNothing); +} diff --git a/playground/frontend/integration_test/miscellaneous_ui/enjoy_playground_test.dart b/playground/frontend/integration_test/miscellaneous_ui/enjoy_playground_test.dart new file mode 100644 index 000000000000..a69d9eac115f --- /dev/null +++ b/playground/frontend/integration_test/miscellaneous_ui/enjoy_playground_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/analytics/analytics_event.dart'; +import 'package:playground/modules/analytics/analytics_events.dart'; +import 'package:playground/modules/analytics/analytics_service.dart'; + +import '../common/common_finders.dart'; + +Future checkEnjoyPlayground(WidgetTester wt) async { + await _checkEnjoyingAndSendFeedback(wt); + await _checkNotEnjoyingAndSendFeedback(wt); + await _checkNotEnjoyingAndClose(wt); +} + +Future _checkNotEnjoyingAndClose(WidgetTester wt) async { + await wt.tap(find.feedbackThumbDown()); + await wt.pumpAndSettle(); + + expect(find.feedbackDropdownContent(), findsOneWidget); + + await wt.tap(find.feedbackDropdownCancelButton()); + await wt.pumpAndSettle(); + + expect(find.feedbackDropdownContent(), findsNothing); +} + +Future _checkEnjoyingAndSendFeedback(WidgetTester wt) async { + expect(find.feedbackDropdownContent(), findsNothing); + + await wt.tap(find.feedbackThumbUp()); + await wt.pumpAndSettle(); + + expect(find.feedbackDropdownContent(), findsOneWidget); + + const text = 'This is enjoying text'; + await wt.enterText(find.feedbackDropdownTextField(), text); + await wt.pumpAndSettle(); + + expect(find.text(text), findsOneWidget); + + await wt.tap(find.feedbackDropdownSendButton()); + await wt.pumpAndSettle(); + + final context = wt.element(find.feedbackThumbUp()); + final lastSentEvent = AnalyticsService.get(context).lastSentEvent; + expect( + lastSentEvent, + AnalyticsEvent( + category: kFeedbackCategory, + action: kClickSendFeedbackEvent, + label: text, + ), + ); + + expect(find.feedbackDropdownContent(), findsNothing); +} + +Future _checkNotEnjoyingAndSendFeedback(WidgetTester wt) async { + await wt.tap(find.feedbackThumbDown()); + await wt.pumpAndSettle(); + + expect(find.feedbackDropdownContent(), findsOneWidget); + + const text = 'This is not enjoying text'; + await wt.enterText(find.feedbackDropdownTextField(), text); + await wt.pumpAndSettle(); + + expect(find.text(text), findsOneWidget); + + await wt.tap(find.feedbackDropdownSendButton()); + await wt.pumpAndSettle(); + + final context = wt.element(find.feedbackThumbDown()); + final lastSentEvent = AnalyticsService.get(context).lastSentEvent; + expect( + lastSentEvent, + AnalyticsEvent( + category: kFeedbackCategory, + action: kClickSendFeedbackEvent, + label: text, + ), + ); + + expect(find.feedbackDropdownContent(), findsNothing); +} diff --git a/playground/frontend/integration_test/miscellaneous_ui/output_placement_test.dart b/playground/frontend/integration_test/miscellaneous_ui/output_placement_test.dart new file mode 100644 index 000000000000..d0c639d65be5 --- /dev/null +++ b/playground/frontend/integration_test/miscellaneous_ui/output_placement_test.dart @@ -0,0 +1,45 @@ +/* + * 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/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:playground/modules/output/models/output_placement.dart'; +import 'package:playground_components_dev/playground_components_dev.dart'; + +import '../common/common.dart'; +import '../common/common_finders.dart'; + +Future checkOutputPlacement(WidgetTester wt) async { + Offset getCodeAreaCenter() => wt.getCenter(find.codeTextAreaWrapper()); + Offset getOutputCenter() => wt.getCenter(find.outputWidget()); + + await wt.tap(find.byKey(const ValueKey(OutputPlacement.left))); + await wt.pumpAndSettle(); + expect(getCodeAreaCenter().dx > getOutputCenter().dx, true); + expectSimilar(getCodeAreaCenter().dy, getOutputCenter().dy); + + await wt.tap(find.byKey(const ValueKey(OutputPlacement.right))); + await wt.pumpAndSettle(); + expect(getCodeAreaCenter().dx < getOutputCenter().dx, true); + expectSimilar(getCodeAreaCenter().dy, getOutputCenter().dy); + + await wt.tap(find.byKey(const ValueKey(OutputPlacement.bottom))); + await wt.pumpAndSettle(); + expect(getCodeAreaCenter().dy < getOutputCenter().dy, true); + expectSimilar(getCodeAreaCenter().dx, getOutputCenter().dx); +} diff --git a/playground/frontend/integration_test/miscellaneous_ui/resize_output_test.dart b/playground/frontend/integration_test/miscellaneous_ui/resize_output_test.dart new file mode 100644 index 000000000000..ae2daaa54224 --- /dev/null +++ b/playground/frontend/integration_test/miscellaneous_ui/resize_output_test.dart @@ -0,0 +1,103 @@ +/* + * 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/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:playground/modules/output/models/output_placement.dart'; +import 'package:playground_components/playground_components.dart'; +import 'package:playground_components_dev/playground_components_dev.dart'; + +import '../common/common.dart'; +import '../common/common_finders.dart'; + +Future checkResizeOutput(WidgetTester wt) async { + final dragHandleStartPosition = wt.getCenter(find.dragHandle()); + Future resetSplitViewRatio() async { + final currentPosition = wt.getCenter(find.dragHandle()); + final offset = dragHandleStartPosition - currentPosition; + await wt.drag(find.dragHandle(), offset); + await wt.pumpAndSettle(); + } + + await _checkDragVertically(wt); + await resetSplitViewRatio(); + + await _checkExcessivelyDragVertically(wt); + await resetSplitViewRatio(); + + await wt.tap(find.byKey(const ValueKey(OutputPlacement.left))); + await wt.pumpAndSettle(); + + await _checkDragHorizontally(wt); + await resetSplitViewRatio(); + + await _checkExcessivelyDragHorizontally(wt); + await resetSplitViewRatio(); +} + +Future _checkDragVertically(WidgetTester wt) async { + final height = wt.getSize(find.splitView()).height; + var dragHandlePosition = wt.getCenter(find.dragHandle()); + + await wt.drag(find.dragHandle(), Offset(0, height * 0.1)); + await wt.pumpAndSettle(); + + var newPosition = wt.getCenter(find.dragHandle()); + expectSimilar(newPosition.dy, dragHandlePosition.dy + height * 0.1); +} + +Future _checkExcessivelyDragVertically(WidgetTester wt) async { + final height = wt.getSize(find.splitView()).height; + final dragHandlePosition = wt.getCenter(find.dragHandle()); + + await wt.drag(find.dragHandle(), Offset(0, height * 0.9)); + await wt.pumpAndSettle(); + + final newPosition = wt.getCenter(find.dragHandle()); + final maxDy = height * (maxRatio - defaultRatio); + expectSimilar( + newPosition.dy, + dragHandlePosition.dy + maxDy, + ); +} + +Future _checkDragHorizontally(WidgetTester wt) async { + final width = wt.getSize(find.splitView()).width; + final dragHandlePosition = wt.getCenter(find.dragHandle()); + + await wt.drag(find.dragHandle(), Offset(width * 0.1, 0)); + await wt.pumpAndSettle(); + + final newPosition = wt.getCenter(find.dragHandle()); + expectSimilar(newPosition.dx, dragHandlePosition.dx + width * 0.1); +} + +Future _checkExcessivelyDragHorizontally(WidgetTester wt) async { + final width = wt.getSize(find.splitView()).width; + final dragHandlePosition = wt.getCenter(find.dragHandle()); + + await wt.drag(find.dragHandle(), Offset(width * 0.9, 0)); + await wt.pumpAndSettle(); + + final newPosition = wt.getCenter(find.dragHandle()); + final maxDx = width * (maxRatio - defaultRatio); + expectSimilar( + newPosition.dx, + dragHandlePosition.dx + maxDx, + ); +} diff --git a/playground/frontend/integration_test/miscellaneous_ui/shortcuts_modal_test.dart b/playground/frontend/integration_test/miscellaneous_ui/shortcuts_modal_test.dart new file mode 100644 index 000000000000..e12752abef3c --- /dev/null +++ b/playground/frontend/integration_test/miscellaneous_ui/shortcuts_modal_test.dart @@ -0,0 +1,48 @@ +/* + * 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/services.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../common/common_finders.dart'; + +Future checkShortcutsModal(WidgetTester wt) async { + expect(find.shortcutsModal(), findsNothing); + + AppLocalizations appLocale = + AppLocalizations.of(wt.element(find.moreActions()))!; + + await wt.tap(find.moreActions()); + await wt.pumpAndSettle(); + + expect(find.text(appLocale.shortcuts), findsOneWidget); + + await wt.tap(find.text(appLocale.shortcuts)); + await wt.pumpAndSettle(); + + expect(find.shortcutsModal(), findsOneWidget); + + await wt.tap(find.text(appLocale.close)); + await wt.pumpAndSettle(); + + expect(find.shortcutsModal(), findsNothing); + + await wt.sendKeyEvent(LogicalKeyboardKey.escape); + await wt.pumpAndSettle(); +} diff --git a/playground/frontend/integration_test/miscellaneous_ui/toggle_brightness_mode_test.dart b/playground/frontend/integration_test/miscellaneous_ui/toggle_brightness_mode_test.dart new file mode 100644 index 000000000000..16bb9a3cc33d --- /dev/null +++ b/playground/frontend/integration_test/miscellaneous_ui/toggle_brightness_mode_test.dart @@ -0,0 +1,41 @@ +/* + * 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:flutter_test/flutter_test.dart'; +import 'package:playground_components_dev/playground_components_dev.dart'; + +Future checkToggleBrightnessMode(WidgetTester wt) async { + Brightness getBrightness() { + return Theme.of(wt.element(find.toggleThemeButton())).brightness; + } + + Future toggleTheme() async { + await wt.tap(find.toggleThemeButton()); + await wt.pumpAndSettle(); + } + + final startBrightness = getBrightness(); + final invertedBrightness = + startBrightness == Brightness.light ? Brightness.dark : Brightness.light; + + await toggleTheme(); + expect(getBrightness(), invertedBrightness); + await toggleTheme(); + expect(getBrightness(), startBrightness); +} diff --git a/playground/frontend/integration_test/standalone_miscellaneous_ui_test.dart b/playground/frontend/integration_test/standalone_miscellaneous_ui_test.dart new file mode 100644 index 000000000000..82bfd7175df6 --- /dev/null +++ b/playground/frontend/integration_test/standalone_miscellaneous_ui_test.dart @@ -0,0 +1,45 @@ +/* + * 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:integration_test/integration_test.dart'; + +import 'common/common.dart'; +import 'miscellaneous_ui/description_test.dart'; +import 'miscellaneous_ui/enjoy_playground_test.dart'; +import 'miscellaneous_ui/output_placement_test.dart'; +import 'miscellaneous_ui/resize_output_test.dart'; +import 'miscellaneous_ui/shortcuts_modal_test.dart'; +import 'miscellaneous_ui/toggle_brightness_mode_test.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + testWidgets( + 'Check UI, not connected with running examples', + (WidgetTester wt) async { + await init(wt); + + await checkEnjoyPlayground(wt); + await checkDescription(wt); + await checkOutputPlacement(wt); + await checkResizeOutput(wt); + await checkShortcutsModal(wt); + await checkToggleBrightnessMode(wt); + }, + ); +} diff --git a/playground/frontend/lib/modules/analytics/analytics_event.dart b/playground/frontend/lib/modules/analytics/analytics_event.dart new file mode 100644 index 000000000000..d82169e0b7bd --- /dev/null +++ b/playground/frontend/lib/modules/analytics/analytics_event.dart @@ -0,0 +1,44 @@ +/* + * 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:equatable/equatable.dart'; + +class AnalyticsEvent with EquatableMixin { + final String action; + final String category; + final String? label; + final Map? parameters; + final int? value; + + AnalyticsEvent({ + required this.action, + required this.category, + this.label, + this.parameters, + this.value, + }); + + @override + List get props => [ + action, + category, + label, + parameters, + value, + ]; +} diff --git a/playground/frontend/lib/modules/analytics/analytics_service.dart b/playground/frontend/lib/modules/analytics/analytics_service.dart index 0416e7dcc520..90853e9c3783 100644 --- a/playground/frontend/lib/modules/analytics/analytics_service.dart +++ b/playground/frontend/lib/modules/analytics/analytics_service.dart @@ -17,10 +17,13 @@ */ import 'package:flutter/widgets.dart'; +import 'package:playground/modules/analytics/analytics_event.dart'; import 'package:playground_components/playground_components.dart'; import 'package:provider/provider.dart'; abstract class AnalyticsService { + AnalyticsEvent? get lastSentEvent; + static AnalyticsService get(BuildContext context) { return Provider.of(context, listen: false); } diff --git a/playground/frontend/lib/modules/analytics/google_analytics_service.dart b/playground/frontend/lib/modules/analytics/google_analytics_service.dart index 7b083b3bc25e..ab3470708166 100644 --- a/playground/frontend/lib/modules/analytics/google_analytics_service.dart +++ b/playground/frontend/lib/modules/analytics/google_analytics_service.dart @@ -22,9 +22,14 @@ import 'package:playground/modules/analytics/analytics_service.dart'; import 'package:playground_components/playground_components.dart'; import 'package:usage/usage_html.dart'; +import 'analytics_event.dart'; + class GoogleAnalyticsService implements AnalyticsService { final _analytics = AnalyticsHtml(kAnalyticsUA, 'beam', '1.0'); + @override + AnalyticsEvent? lastSentEvent; + @override void trackSelectSdk(Sdk? oldSdk, Sdk newSdk) { safeSendEvent( @@ -137,6 +142,13 @@ class GoogleAnalyticsService implements AnalyticsService { value: value, parameters: parameters, ); + lastSentEvent = AnalyticsEvent( + category: category, + action: action, + label: label, + value: value, + parameters: parameters, + ); } catch (e) { // ignore analytics errors sync they don't affect app print(e); diff --git a/playground/frontend/lib/modules/output/components/output_header/output_placements.dart b/playground/frontend/lib/modules/output/components/output_header/output_placements.dart index f8eaf5710247..d82f0b12b884 100644 --- a/playground/frontend/lib/modules/output/components/output_header/output_placements.dart +++ b/playground/frontend/lib/modules/output/components/output_header/output_placements.dart @@ -42,6 +42,7 @@ class OutputPlacements extends StatelessWidget { '${AppLocalizations.of(context)!.outputPlacementSemantic}' ' ${placement.name(context)}', child: IconButton( + key: ValueKey(placement), splashRadius: kIconButtonSplashRadius, icon: SvgPicture.asset( placement.icon, diff --git a/playground/frontend/lib/modules/shortcuts/components/shortcut_row.dart b/playground/frontend/lib/modules/shortcuts/components/shortcut_row.dart index 27cd34357038..c68a1d95cbb8 100644 --- a/playground/frontend/lib/modules/shortcuts/components/shortcut_row.dart +++ b/playground/frontend/lib/modules/shortcuts/components/shortcut_row.dart @@ -31,15 +31,17 @@ class ShortcutRow extends StatelessWidget { // wrap with row to shrink container to child size return Row( children: [ - Container( - decoration: BoxDecoration( - border: Border.all(color: primaryColor), - borderRadius: BorderRadius.circular(kSmBorderRadius), - ), - padding: const EdgeInsets.all(kMdSpacing), - child: Text( - shortcut.title, - style: TextStyle(color: primaryColor), + Flexible( + child: Container( + decoration: BoxDecoration( + border: Border.all(color: primaryColor), + borderRadius: BorderRadius.circular(kSmBorderRadius), + ), + padding: const EdgeInsets.all(kMdSpacing), + child: Text( + shortcut.title, + style: TextStyle(color: primaryColor), + ), ), ), ], diff --git a/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart b/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart index 1f334327f6b0..f4332e55ebd4 100644 --- a/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart +++ b/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart @@ -64,6 +64,7 @@ class ShortcutsModal extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded(child: ShortcutRow(shortcut: shortcut)), + const SizedBox(width: kMdSpacing), Expanded( flex: 3, child: Text( diff --git a/playground/frontend/lib/pages/standalone_playground/widgets/feedback/feedback_dropdown_content.dart b/playground/frontend/lib/pages/standalone_playground/widgets/feedback/feedback_dropdown_content.dart index 2f161cecc64c..e997a270be19 100644 --- a/playground/frontend/lib/pages/standalone_playground/widgets/feedback/feedback_dropdown_content.dart +++ b/playground/frontend/lib/pages/standalone_playground/widgets/feedback/feedback_dropdown_content.dart @@ -35,6 +35,10 @@ const String kFeedbackContentText = 'Have feedback? We\'d love to hear it,' '\nHave questions? Try help or support.'; class FeedbackDropdownContent extends StatelessWidget { + static const textFieldKey = Key('feedbackTextFieldKey'); + static const cancelButtonKey = Key('cancelButtonKey'); + static const sendButtonKey = Key('sendFeedbackButtonKey'); + final void Function() close; final TextEditingController textController; @@ -46,7 +50,8 @@ class FeedbackDropdownContent extends StatelessWidget { @override Widget build(BuildContext context) { - final borderColor = Theme.of(context).extension()!.borderColor; + final borderColor = + Theme.of(context).extension()!.borderColor; final OutlineInputBorder border = OutlineInputBorder( borderSide: BorderSide(color: borderColor), @@ -110,6 +115,7 @@ class FeedbackDropdownContent extends StatelessWidget { child: ClipRRect( borderRadius: BorderRadius.circular(kMdBorderRadius), child: TextFormField( + key: textFieldKey, controller: textController, decoration: InputDecoration( focusedBorder: border, @@ -147,6 +153,7 @@ class FeedbackDropdownContent extends StatelessWidget { ), ), child: TextButton( + key: cancelButtonKey, onPressed: () { close(); textController.clear(); @@ -162,6 +169,7 @@ class FeedbackDropdownContent extends StatelessWidget { borderRadius: BorderRadius.circular(kSmBorderRadius), ), child: ElevatedButton( + key: sendButtonKey, onPressed: () { if (textController.text.isNotEmpty) { AnalyticsService.get(context).trackClickSendFeedback( diff --git a/playground/frontend/lib/pages/standalone_playground/widgets/feedback/playground_feedback.dart b/playground/frontend/lib/pages/standalone_playground/widgets/feedback/playground_feedback.dart index 4faeaaf49208..ce5651d175f4 100644 --- a/playground/frontend/lib/pages/standalone_playground/widgets/feedback/playground_feedback.dart +++ b/playground/frontend/lib/pages/standalone_playground/widgets/feedback/playground_feedback.dart @@ -28,6 +28,9 @@ import 'feedback_dropdown_icon_button.dart'; /// A status bar item for feedback. class PlaygroundFeedback extends StatelessWidget { + static const thumbUpKey = Key('thumbUp'); + static const thumbDownKey = Key('thumbDown'); + const PlaygroundFeedback({Key? key}) : super(key: key); @override @@ -42,6 +45,7 @@ class PlaygroundFeedback extends StatelessWidget { style: const TextStyle(fontWeight: kBoldWeight), ), FeedbackDropdownIconButton( + key: thumbUpKey, label: appLocale.enjoying, iconAsset: kThumbUpIconAsset, filledIconAsset: kThumbUpIconAssetFilled, @@ -49,6 +53,7 @@ class PlaygroundFeedback extends StatelessWidget { isSelected: isEnjoying != null && isEnjoying, ), FeedbackDropdownIconButton( + key: thumbDownKey, label: appLocale.notEnjoying, iconAsset: kThumbDownIconAsset, filledIconAsset: kThumbDownIconAssetFilled, @@ -62,8 +67,7 @@ class PlaygroundFeedback extends StatelessWidget { _setEnjoying(BuildContext context, bool isEnjoying) { return () { _getFeedbackState(context, false).setEnjoying(isEnjoying); - AnalyticsService.get(context) - .trackClickEnjoyPlayground(isEnjoying); + AnalyticsService.get(context).trackClickEnjoyPlayground(isEnjoying); }; } diff --git a/playground/frontend/playground_components/lib/src/theme/theme.dart b/playground/frontend/playground_components/lib/src/theme/theme.dart index 287eef0a14f3..1ebe89cb89b2 100644 --- a/playground/frontend/playground_components/lib/src/theme/theme.dart +++ b/playground/frontend/playground_components/lib/src/theme/theme.dart @@ -163,7 +163,7 @@ final kLightTheme = ThemeData( fontSize: codeFontSize, ), codeTheme: CodeThemeData( - styles: { + styles: const { 'root': TextStyle( backgroundColor: BeamLightThemeColors.primaryBackground, color: BeamLightThemeColors.text, @@ -194,8 +194,8 @@ final kLightTheme = ThemeData( 'symbol': TextStyle(color: BeamLightThemeColors.code2), 'bullet': TextStyle(color: BeamLightThemeColors.code2), 'link': TextStyle(color: BeamLightThemeColors.code2), - 'emphasis': const TextStyle(fontStyle: FontStyle.italic), - 'strong': const TextStyle(fontWeight: FontWeight.bold), + 'emphasis': TextStyle(fontStyle: FontStyle.italic), + 'strong': TextStyle(fontWeight: FontWeight.bold), }, ), ), @@ -239,7 +239,7 @@ final kDarkTheme = ThemeData( fontSize: codeFontSize, ), codeTheme: CodeThemeData( - styles: { + styles: const { 'root': TextStyle( backgroundColor: BeamDarkThemeColors.primaryBackground, color: BeamDarkThemeColors.text, @@ -270,8 +270,8 @@ final kDarkTheme = ThemeData( 'symbol': TextStyle(color: BeamDarkThemeColors.code2), 'bullet': TextStyle(color: BeamDarkThemeColors.code2), 'link': TextStyle(color: BeamDarkThemeColors.code2), - 'emphasis': const TextStyle(fontStyle: FontStyle.italic), - 'strong': const TextStyle(fontWeight: FontWeight.bold), + 'emphasis': TextStyle(fontStyle: FontStyle.italic), + 'strong': TextStyle(fontWeight: FontWeight.bold), }, ), ), diff --git a/playground/frontend/playground_components/lib/src/widgets/output/output.dart b/playground/frontend/playground_components/lib/src/widgets/output/output.dart index 194b5d754937..81a42f2a794e 100644 --- a/playground/frontend/playground_components/lib/src/widgets/output/output.dart +++ b/playground/frontend/playground_components/lib/src/widgets/output/output.dart @@ -79,11 +79,13 @@ class _OutputWidgetState extends State Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - TabHeader( - tabController: tabController, - tabsWidget: OutputTabs( - playgroundController: widget.playgroundController, + Flexible( + child: TabHeader( tabController: tabController, + tabsWidget: OutputTabs( + playgroundController: widget.playgroundController, + tabController: tabController, + ), ), ), if (widget.trailing != null) widget.trailing!, diff --git a/playground/frontend/playground_components/lib/src/widgets/split_view.dart b/playground/frontend/playground_components/lib/src/widgets/split_view.dart index 904d03788d61..6291161f135d 100644 --- a/playground/frontend/playground_components/lib/src/widgets/split_view.dart +++ b/playground/frontend/playground_components/lib/src/widgets/split_view.dart @@ -48,9 +48,9 @@ class _SplitViewState extends State { double _ratio = defaultRatio; double _maxSize = 0; - get _sizeFirst => _ratio * _maxSize; + int get _sizeFirst => (_ratio * _maxSize).toInt(); - get _sizeSecond => (1 - _ratio) * _maxSize; + int get _sizeSecond => ((1 - _ratio) * _maxSize).toInt(); get _isHorizontal => widget.direction == Axis.horizontal; @@ -78,13 +78,13 @@ class _SplitViewState extends State { width: constraints.maxWidth, child: Row( children: [ - SizedBox( - width: _sizeFirst, + Expanded( + flex: _sizeFirst, child: widget.first, ), _buildSeparator(context), - SizedBox( - width: _sizeSecond, + Expanded( + flex: _sizeSecond, child: widget.second, ), ], @@ -98,13 +98,13 @@ class _SplitViewState extends State { height: constraints.maxHeight, child: Column( children: [ - SizedBox( - height: _sizeFirst, + Expanded( + flex: _sizeFirst, child: widget.first, ), _buildSeparator(context), - SizedBox( - height: _sizeSecond, + Expanded( + flex: _sizeSecond, child: widget.second, ), ], diff --git a/playground/frontend/playground_components_dev/lib/src/common_finders.dart b/playground/frontend/playground_components_dev/lib/src/common_finders.dart index 7c01f7f29075..4180ae02a0d1 100644 --- a/playground/frontend/playground_components_dev/lib/src/common_finders.dart +++ b/playground/frontend/playground_components_dev/lib/src/common_finders.dart @@ -43,6 +43,10 @@ extension CommonFindersExtension on CommonFinders { ); } + Finder outputWidget() { + return byType(OutputWidget); + } + Finder resultTab() { // TODO(alexeyinkin): Use keys when output tabs get to use enum, https://github.com/apache/beam/issues/22663 return widgetWithText(OutputTab, 'Result'); @@ -51,4 +55,12 @@ extension CommonFindersExtension on CommonFinders { Finder runOrCancelButton() { return byType(RunOrCancelButton); } + + Finder splitView() { + return byType(SplitView); + } + + Finder toggleThemeButton() { + return byType(ToggleThemeButton); + } } diff --git a/playground/frontend/playground_components_dev/lib/src/widget_tester.dart b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart index 9aed823511ed..6a833f0bcb8c 100644 --- a/playground/frontend/playground_components_dev/lib/src/widget_tester.dart +++ b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart @@ -20,6 +20,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_code_editor/flutter_code_editor.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:playground_components/playground_components.dart'; +import 'package:provider/provider.dart'; import 'common_finders.dart'; @@ -44,4 +45,9 @@ extension WidgetTesterExtension on WidgetTester { return widget(selectableText).data; } + + PlaygroundController findPlaygroundController() { + final context = element(find.codeField()); + return context.read(); + } } diff --git a/playground/frontend/playground_components_dev/pubspec.yaml b/playground/frontend/playground_components_dev/pubspec.yaml index 66878bdaafa7..a4998c7c1bfd 100644 --- a/playground/frontend/playground_components_dev/pubspec.yaml +++ b/playground/frontend/playground_components_dev/pubspec.yaml @@ -31,4 +31,5 @@ dependencies: highlight: ^0.7.0 http: ^0.13.5 playground_components: { path: ../playground_components } + provider: ^6.0.3 total_lints: ^2.18.0 From 8d4f85f35bf779dc7c9711c5954fbfe61d200fbe Mon Sep 17 00:00:00 2001 From: malarg Date: Thu, 12 Jan 2023 11:34:40 +0400 Subject: [PATCH 08/52] integration tests run and editing --- playground/frontend/build.gradle | 25 ++- .../common/common_finders.dart | 26 ++++ ...tandalone_cancel_running_example_test.dart | 51 +++++++ ..._change_pipeline_options_and_run_test.dart | 96 ++++++++++++ .../standalone_editing_test.dart | 144 ++++++++++++++++++ .../standalone_run_shortcuts_test.dart | 82 ++++++++++ .../pipeline_options_dropdown_body.dart | 18 ++- .../pipeline_options_dropdown_input.dart | 3 + .../pipeline_options_form.dart | 3 + .../controllers/playground_controller.dart | 9 +- .../snippet_editing_controller.dart | 2 +- .../playground_controler_shortcuts_test.dart | 3 + .../lib/src/widget_tester.dart | 15 ++ 13 files changed, 470 insertions(+), 7 deletions(-) create mode 100644 playground/frontend/integration_test/standalone_cancel_running_example_test.dart create mode 100644 playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart create mode 100644 playground/frontend/integration_test/standalone_editing_test.dart create mode 100644 playground/frontend/integration_test/standalone_run_shortcuts_test.dart create mode 100644 playground/frontend/playground_components/test/src/controllers/playground_controler_shortcuts_test.dart diff --git a/playground/frontend/build.gradle b/playground/frontend/build.gradle index 4de0f867b363..fc786b877dd3 100644 --- a/playground/frontend/build.gradle +++ b/playground/frontend/build.gradle @@ -183,8 +183,12 @@ ext.deleteFilesByRegExp = { re -> } tasks.register("integrationTest") { - dependsOn("integrationTest_standalone_change_example_sdk_run") - dependsOn("integrationTest_standalone_miscellaneous_ui") + // dependsOn("integrationTest_standalone_change_example_sdk_run") + // dependsOn("integrationTest_standalone_miscellaneous_ui") + // dependsOn("integrationTest_standalone_run_shortcuts") + // dependsOn("integrationTest_standalone_cancel_running_example") + // dependsOn("integrationTest_standalone_change_pipeline_options_and_run") + dependsOn("integrationTest_standalone_editing") } tasks.register("integrationTest_standalone_change_example_sdk_run") { @@ -195,6 +199,22 @@ tasks.register("integrationTest_standalone_miscellaneous_ui") { runIntegrationTest("standalone_miscellaneous_ui", "/") } +tasks.register("integrationTest_standalone_run_shortcuts") { + runIntegrationTest("standalone_run_shortcuts", "/") +} + +tasks.register("integrationTest_standalone_cancel_running_example") { + runIntegrationTest("standalone_cancel_running_example", "/") +} + +tasks.register("integrationTest_standalone_change_pipeline_options_and_run") { + runIntegrationTest("standalone_change_pipeline_options_and_run", "/") +} + +tasks.register("integrationTest_standalone_editing") { + runIntegrationTest("standalone_editing", "/") +} + void runIntegrationTest(String path, String url) { exec { executable("flutter") @@ -202,6 +222,7 @@ void runIntegrationTest(String path, String url) { "drive", "--driver=test_driver/integration_test.dart", "--target=integration_test/${path}_test.dart", + "--web-port=4000", "--web-launch-url='$url'", "--device-id=chrome", ) diff --git a/playground/frontend/integration_test/common/common_finders.dart b/playground/frontend/integration_test/common/common_finders.dart index 7d906ee055ad..88b0ea29e7f3 100644 --- a/playground/frontend/integration_test/common/common_finders.dart +++ b/playground/frontend/integration_test/common/common_finders.dart @@ -18,6 +18,9 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:playground/components/dropdown_button/dropdown_button.dart'; +import 'package:playground/modules/actions/components/reset_action.dart'; +import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_body.dart'; import 'package:playground/modules/examples/components/description_popover/description_popover.dart'; import 'package:playground/modules/examples/components/description_popover/description_popover_button.dart'; import 'package:playground/modules/examples/example_selector.dart'; @@ -33,6 +36,13 @@ import 'package:playground_components/src/widgets/drag_handle.dart'; import 'package:playground_components_dev/playground_components_dev.dart'; extension CommonFindersExtension on CommonFinders { + Finder appDropdownButtonWithText(String text) { + return find.descendant( + of: byType(AppDropdownButton), + matching: find.text(text), + ); + } + Finder codeTextAreaWrapper() { return byType(CodeTextAreaWrapper); } @@ -85,6 +95,22 @@ extension CommonFindersExtension on CommonFinders { return byType(MoreActions); } + Finder pipelineOptionsOptionsTab() { + return find.byKey(PipelineOptionsDropdownBody.optionsTabKey); + } + + Finder pipelineOptionsRawTab() { + return find.byKey(PipelineOptionsDropdownBody.rawTabKey); + } + + Finder pipelineOptionsSaveAndCloseButton() { + return find.byKey(PipelineOptionsDropdownBody.saveAndCloseButtonKey); + } + + Finder resetButton() { + return find.byType(ResetAction); + } + Finder sdkItemInDropdown(Sdk sdk) { return find.byType(SdkSelectorRow).and(find.byKey(ValueKey(sdk))); } diff --git a/playground/frontend/integration_test/standalone_cancel_running_example_test.dart b/playground/frontend/integration_test/standalone_cancel_running_example_test.dart new file mode 100644 index 000000000000..3c251358ae6f --- /dev/null +++ b/playground/frontend/integration_test/standalone_cancel_running_example_test.dart @@ -0,0 +1,51 @@ +/* + * 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:integration_test/integration_test.dart'; +import 'package:playground_components_dev/playground_components_dev.dart'; + +import 'common/common.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('Cancel running example', (WidgetTester wt) async { + await init(wt); + + // Cancel unchanged example. + await _runAndCancelExample(wt, const Duration(milliseconds: 300)); + + final source = wt.findPlaygroundController().selectedExample?.source ?? ''; + await wt.enterText(find.codeField(), source + '//comment'); + await wt.pumpAndSettle(); + + // Cancel changed example. + await _runAndCancelExample(wt, const Duration(seconds: 2)); + }); +} + +Future _runAndCancelExample(WidgetTester wt, Duration duration) async { + await wt.tap(find.runOrCancelButton()); + await Future.delayed(duration); + + await wt.tapAndSettle(find.runOrCancelButton()); + + final playgroundController = wt.findPlaygroundController(); + expect(playgroundController.outputResult, contains('Pipeline cancelled')); +} diff --git a/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart b/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart new file mode 100644 index 000000000000..ff60a14e588c --- /dev/null +++ b/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart @@ -0,0 +1,96 @@ +/* + * 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/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_body.dart'; +import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_input.dart'; +import 'package:playground_components_dev/playground_components_dev.dart'; + +import 'common/common.dart'; +import 'common/common_finders.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets( + 'Changing pipeline options and press run', + (WidgetTester wt) async { + await init(wt); + + await wt.tapAndSettle(find.appDropdownButtonWithText('Pipeline Options')); + + await _addTwoOptions(wt); + + await wt.tapAndSettle(find.pipelineOptionsRawTab()); + + _checkIfRawTextCorrect('--some test --some2 test2'); + + await wt.tapAndSettle(find.pipelineOptionsOptionsTab()); + + await wt.tap(find.byKey(const ValueKey('PipelineOptionsDelete1'))); + + await wt.tapAndSettle(find.pipelineOptionsRawTab()); + + _checkIfRawTextCorrect('--some test'); + + await wt.tapAndSettle(find.pipelineOptionsSaveAndCloseButton()); + + await wt.tap(find.runOrCancelButton()); + await Future.delayed(const Duration(seconds: 2)); + + await wt.tapAndSettle(find.runOrCancelButton()); + + final playgroundController = wt.findPlaygroundController(); + expect(playgroundController.outputResult, contains('Pipeline cancelled')); + }, + ); +} + +Future _addTwoOptions(WidgetTester wt) async { + await wt.enterText( + find.byKey(const ValueKey('PipelineOptionsKey0')), + 'some', + ); + await wt.enterText( + find.byKey(const ValueKey('PipelineOptionsValue0')), + 'test', + ); + + await wt.tap(find.byKey(PipelineOptionsDropdownBody.addOptionButtonKey)); + await wt.pumpAndSettle(); + + await wt.enterText( + find.byKey(const ValueKey('PipelineOptionsKey1')), + 'some2', + ); + await wt.enterText( + find.byKey(const ValueKey('PipelineOptionsValue1')), + 'test2', + ); +} + +void _checkIfRawTextCorrect(String text) { + expect( + find.descendant( + of: find.byKey(PipelineOptionsDropdownInput.textFieldKey), + matching: find.text(text)), + findsOneWidget, + ); +} diff --git a/playground/frontend/integration_test/standalone_editing_test.dart b/playground/frontend/integration_test/standalone_editing_test.dart new file mode 100644 index 000000000000..08158827ecfd --- /dev/null +++ b/playground/frontend/integration_test/standalone_editing_test.dart @@ -0,0 +1,144 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:playground_components_dev/playground_components_dev.dart'; + +import 'common/common.dart'; +import 'common/common_finders.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('Testing editing code', (WidgetTester wt) async { + await init(wt); + // await _checkHotkeys(wt); + await _checkAutocomplete(wt); + // await _editingAndResettingChanges(wt); + // await _checkCodeHighlighting(wt); + // await _codeBlockFoldingTest(wt); + }); +} + +Future _checkAutocomplete(WidgetTester wt) async { + final codeController = wt.findOneCodeController(); + final sSuggestions = await codeController.autocompleter.getSuggestions('s'); + expect(sSuggestions, ['short', 'static', 'strictfp', 'super', 'switch', 'synchronized']); +} + +Future _editingAndResettingChanges(WidgetTester wt) async { + final playgroundController = wt.findPlaygroundController(); + + final code = playgroundController.source; + + expect(code, isNotNull); + + await wt.tapAndSettle(find.resetButton()); + + expect(playgroundController.source == code, true); + + await wt.enterText(find.codeField(), 'print("Hello World!'); + await wt.pumpAndSettle(); + + expect(playgroundController.source != code, true); + + await wt.tapAndSettle(find.resetButton()); + + expect(playgroundController.source, equals(code)); +} + +Future _checkCodeHighlighting(WidgetTester wt) async { + final codeController = wt.findOneCodeController(); + final colors = {}; + var textSpan = codeController.lastTextSpan; + _collectTextSpanTreeTextColors(textSpan, colors); + + expect(colors.length, greaterThan(1)); +} + +void _collectTextSpanTreeTextColors(InlineSpan? span, Set colors) { + if (span is TextSpan) { + if (span.style?.color != null) { + colors.add(span.style!.color!); + } + if (span.children != null) { + for (final child in span.children!) { + _collectTextSpanTreeTextColors(child, colors); + } + } + } +} + +Future _codeBlockFoldingTest(WidgetTester wt) async { + const code = ''' +public class MyClass { + public static void main(String[] args) { + System.out.print("Hello World!"); + } +} +'''; + + await wt.enterText(find.codeField(), code); + await wt.pumpAndSettle(); + + await wt.tapAndSettle(_getTopToggle(wt)); + + const foldedCode = ''' +public class MyClass { +'''; + + expect(wt.findOneCodeController().text, equals(foldedCode)); + + await wt.tapAndSettle(_getFoldToggles()); + + expect(wt.findOneCodeController().text, equals(code)); +} + +Finder _getTopToggle(WidgetTester wt) { + Finder foldToggles = _getFoldToggles(); + + Finder topToggle = + wt.getCenter(foldToggles.at(0)).dy < wt.getCenter(foldToggles.at(1)).dy + ? foldToggles.at(0) + : foldToggles.at(1); + return topToggle; +} + +Finder _getFoldToggles() { + Finder foldToggles = find.descendant( + of: find.byType(RotatedBox), + matching: find.byIcon(Icons.chevron_right), + ); + return foldToggles; +} + +// Future _checkHotkeys(WidgetTester wt) async { +// final undoKeys = { +// LogicalKeyboardKey.keyZ, +// LogicalKeyboardKey.meta, +// }; + +// final redoKeys = { +// LogicalKeyboardKey.keyZ, +// LogicalKeyboardKey.meta, +// LogicalKeyboardKey.shift, +// }; + +// final playgroundController = wt.findPlaygroundController(); + +// final code = playgroundController.source; + +// expect(code, isNotNull); + +// await wt.enterText(find.codeField(), 'print("Hello World!");'); +// await wt.pumpAndSettle(); + +// expect(playgroundController.source != code, true); + +// await wt.runShortcut(undoKeys); + +// expect(playgroundController.source, equals(code)); + +// await wt.runShortcut(redoKeys); + +// expect(playgroundController.source != code, true); +// } \ No newline at end of file diff --git a/playground/frontend/integration_test/standalone_run_shortcuts_test.dart b/playground/frontend/integration_test/standalone_run_shortcuts_test.dart new file mode 100644 index 000000000000..66c1ea4a0956 --- /dev/null +++ b/playground/frontend/integration_test/standalone_run_shortcuts_test.dart @@ -0,0 +1,82 @@ +/* + * 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:integration_test/integration_test.dart'; +import 'package:playground/modules/shortcuts/constants/global_shortcuts.dart'; +import 'package:playground_components/playground_components.dart'; +import 'package:playground_components_dev/playground_components_dev.dart'; + +import 'common/common.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets( + 'Shortcuts using test', + (WidgetTester wt) async { + await init(wt); + + final controller = wt.findPlaygroundController(); + + await _checkResetShortcut(wt, controller); + await _checkRunShortcut(wt, controller); + await _checkClearOutputShortcut(wt, controller); + }, + ); +} + +Future _checkResetShortcut( + WidgetTester wt, + PlaygroundController controller, +) async { + final startSource = controller.source; + await wt.enterText(find.codeField(), 'print("Hello World!'); + + expect(startSource != controller.source, true); + + await wt.runShortcut(controller.resetShortcut.shortcuts.keys); + await wt.pumpAndSettle(); + + expect(startSource == controller.source, true); +} + +Future _checkRunShortcut( + WidgetTester wt, + PlaygroundController controller, +) async { + final output = controller.outputResult; + await wt.runShortcut(controller.runShortcut.shortcuts.keys); + await wt.pumpAndSettle(); + + expect(output != controller.outputResult, true); +} + +Future _checkClearOutputShortcut( + WidgetTester wt, + PlaygroundController controller, +) async { + expect(controller.outputResult, isNotEmpty); + expect(controller.result, isNotNull); + + await wt.runShortcut(kClearOutputShortcut.shortcuts.keys); + await wt.pumpAndSettle(); + + expect(controller.outputResult, isEmpty); + expect(controller.result, isNull); +} diff --git a/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_body.dart b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_body.dart index 0b8cd5836b4b..ca12319525b3 100644 --- a/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_body.dart +++ b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_body.dart @@ -30,6 +30,12 @@ const kOptionsTabIndex = 0; const kRawTabIndex = 1; class PipelineOptionsDropdownBody extends StatefulWidget { + static const optionsTabKey = ValueKey('PipelineOptionsOptionsTab'); + static const rawTabKey = ValueKey('PipelineOptionsRawTab'); + + static const saveAndCloseButtonKey = ValueKey('PipelineOptionsSaveAndClose'); + static const addOptionButtonKey = ValueKey('PipelineOptionsAddOption'); + final String pipelineOptions; final void Function(String) setPipelineOptions; final void Function() close; @@ -105,8 +111,14 @@ class _PipelineOptionsDropdownBodyState TabBar( controller: tabController, tabs: [ - Tab(text: appLocale.optionsPipelineOptions), - Tab(text: appLocale.rawPipelineOptions), + Tab( + key: PipelineOptionsDropdownBody.optionsTabKey, + text: appLocale.optionsPipelineOptions, + ), + Tab( + key: PipelineOptionsDropdownBody.rawTabKey, + text: appLocale.rawPipelineOptions, + ), ], ), const PipelineOptionsDropdownSeparator(), @@ -137,6 +149,7 @@ class _PipelineOptionsDropdownBodyState SizedBox( height: kButtonHeight, child: ElevatedButton( + key: PipelineOptionsDropdownBody.saveAndCloseButtonKey, child: Text(appLocale.saveAndClose), onPressed: () => _save(context), ), @@ -146,6 +159,7 @@ class _PipelineOptionsDropdownBodyState SizedBox( height: kButtonHeight, child: OutlinedButton( + key: PipelineOptionsDropdownBody.addOptionButtonKey, child: Text(appLocale.addPipelineOptionParameter), onPressed: () => setState(() { pipelineOptionsList.add(PipelineOptionController()); diff --git a/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_input.dart b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_input.dart index c9cd125355b8..3174cb2cc496 100644 --- a/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_input.dart +++ b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_input.dart @@ -24,6 +24,8 @@ import 'package:playground/modules/editor/components/pipeline_options_dropdown/p const kPipelineOptionsInputLines = 8; class PipelineOptionsDropdownInput extends StatelessWidget { + static const textFieldKey = ValueKey('PipelineOptionsRawInput'); + final TextEditingController controller; const PipelineOptionsDropdownInput({ @@ -39,6 +41,7 @@ class PipelineOptionsDropdownInput extends StatelessWidget { children: [ PipelineOptionLabel(text: appLocale.input), PipelineOptionsTextField( + key: textFieldKey, lines: kPipelineOptionsInputLines, controller: controller, ), diff --git a/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_form.dart b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_form.dart index 6aa41c0c3cdf..ffc44c2e8f84 100644 --- a/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_form.dart +++ b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_form.dart @@ -55,6 +55,7 @@ class PipelineOptionsForm extends StatelessWidget { (index, controller) => Row( children: [ Expanded( + key: ValueKey('PipelineOptionsKey$index'), child: SizedBox( height: kTextFieldHeight, child: PipelineOptionsTextField( @@ -64,6 +65,7 @@ class PipelineOptionsForm extends StatelessWidget { ), kSpace, Expanded( + key: ValueKey('PipelineOptionsValue$index'), child: SizedBox( height: kTextFieldHeight, child: PipelineOptionsTextField( @@ -74,6 +76,7 @@ class PipelineOptionsForm extends StatelessWidget { SizedBox( width: kIconSizeLg, child: IconButton( + key: ValueKey('PipelineOptionsDelete$index'), iconSize: kIconSizeMd, splashRadius: kIconButtonSplashRadius, icon: const Icon( diff --git a/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart index 6ef86908c161..da6c5e2c42f3 100644 --- a/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart +++ b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart @@ -284,6 +284,7 @@ class PlaygroundController with ChangeNotifier { void clearOutput() { _result = null; + outputResult = ''; notifyListeners(); } @@ -378,7 +379,11 @@ class PlaygroundController with ChangeNotifier { // add a little delay to improve user experience await Future.delayed(kPrecompiledDelay); - String logs = selectedExample.logs ?? ''; + if (_result?.status != RunCodeStatus.preparation) { + return; + } + + final logs = selectedExample.logs ?? ''; _result = RunCodeResult( status: RunCodeStatus.finished, output: selectedExample.outputs, @@ -394,7 +399,7 @@ class PlaygroundController with ChangeNotifier { StreamController _createExecutionTimeStream() { StreamController? streamController; Timer? timer; - Duration timerInterval = const Duration(milliseconds: kExecutionTimeUpdate); + const timerInterval = Duration(milliseconds: kExecutionTimeUpdate); int ms = 0; void stopTimer() { diff --git a/playground/frontend/playground_components/lib/src/controllers/snippet_editing_controller.dart b/playground/frontend/playground_components/lib/src/controllers/snippet_editing_controller.dart index 052ee41aea18..007f5f5b923d 100644 --- a/playground/frontend/playground_components/lib/src/controllers/snippet_editing_controller.dart +++ b/playground/frontend/playground_components/lib/src/controllers/snippet_editing_controller.dart @@ -194,7 +194,7 @@ class SnippetEditingController extends ChangeNotifier { } void reset() { - codeController.text = _selectedExample?.source ?? ''; + codeController.fullText = _selectedExample?.source ?? ''; _pipelineOptions = _selectedExample?.pipelineOptions ?? ''; } diff --git a/playground/frontend/playground_components/test/src/controllers/playground_controler_shortcuts_test.dart b/playground/frontend/playground_components/test/src/controllers/playground_controler_shortcuts_test.dart new file mode 100644 index 000000000000..f3d44c8bc6cd --- /dev/null +++ b/playground/frontend/playground_components/test/src/controllers/playground_controler_shortcuts_test.dart @@ -0,0 +1,3 @@ +void main() { + +} \ No newline at end of file diff --git a/playground/frontend/playground_components_dev/lib/src/widget_tester.dart b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart index 6a833f0bcb8c..967f4fae845b 100644 --- a/playground/frontend/playground_components_dev/lib/src/widget_tester.dart +++ b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart @@ -17,6 +17,7 @@ */ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_code_editor/flutter_code_editor.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:playground_components/playground_components.dart'; @@ -50,4 +51,18 @@ extension WidgetTesterExtension on WidgetTester { final context = element(find.codeField()); return context.read(); } + + Future runShortcut(Set shortcut) async { + for (final key in shortcut) { + await sendKeyDownEvent(key); + } + for (final key in shortcut) { + await sendKeyUpEvent(key); + } + } + + Future tapAndSettle(Finder finder) async { + await tap(finder); + await pumpAndSettle(); + } } From 7dbd08bc9a6aa2ffdd463c7f8d9a4ddcf433ea90 Mon Sep 17 00:00:00 2001 From: malarg Date: Tue, 17 Jan 2023 13:12:17 +0400 Subject: [PATCH 09/52] example selector test --- playground/frontend/build.gradle | 36 ++++--- ...tandalone_cancel_running_example_test.dart | 10 +- ..._change_pipeline_options_and_run_test.dart | 2 +- .../standalone_editing_test.dart | 20 ++-- .../standalone_example_selector_test.dart | 97 +++++++++++++++++++ .../snippet_file_editing_controller.dart | 2 +- 6 files changed, 138 insertions(+), 29 deletions(-) create mode 100644 playground/frontend/integration_test/standalone_example_selector_test.dart diff --git a/playground/frontend/build.gradle b/playground/frontend/build.gradle index fc786b877dd3..bcd5f53660e4 100644 --- a/playground/frontend/build.gradle +++ b/playground/frontend/build.gradle @@ -183,36 +183,41 @@ ext.deleteFilesByRegExp = { re -> } tasks.register("integrationTest") { - // dependsOn("integrationTest_standalone_change_example_sdk_run") - // dependsOn("integrationTest_standalone_miscellaneous_ui") - // dependsOn("integrationTest_standalone_run_shortcuts") - // dependsOn("integrationTest_standalone_cancel_running_example") - // dependsOn("integrationTest_standalone_change_pipeline_options_and_run") + // dependsOn("integrationTest_standalone_change_example_sdk_run") - + dependsOn("integrationTest_standalone_cancel_running_example") + dependsOn("integrationTest_standalone_change_pipeline_options_and_run") dependsOn("integrationTest_standalone_editing") + dependsOn("integrationTest_standalone_example_selector") + dependsOn("integrationTest_standalone_miscellaneous_ui") + dependsOn("integrationTest_standalone_run_shortcuts") +} + +tasks.register("integrationTest_standalone_cancel_running_example") { + runIntegrationTest("standalone_cancel_running_example", "/") } tasks.register("integrationTest_standalone_change_example_sdk_run") { runIntegrationTest("standalone_change_example_sdk_run", "/") } -tasks.register("integrationTest_standalone_miscellaneous_ui") { - runIntegrationTest("standalone_miscellaneous_ui", "/") +tasks.register("integrationTest_standalone_change_pipeline_options_and_run") { + runIntegrationTest("standalone_change_pipeline_options_and_run", "/") } -tasks.register("integrationTest_standalone_run_shortcuts") { - runIntegrationTest("standalone_run_shortcuts", "/") +tasks.register("integrationTest_standalone_editing") { + runIntegrationTest("standalone_editing", "/") } -tasks.register("integrationTest_standalone_cancel_running_example") { - runIntegrationTest("standalone_cancel_running_example", "/") +tasks.register("integrationTest_standalone_example_selector") { + runIntegrationTest("standalone_example_selector", "/") } -tasks.register("integrationTest_standalone_change_pipeline_options_and_run") { - runIntegrationTest("standalone_change_pipeline_options_and_run", "/") +tasks.register("integrationTest_standalone_miscellaneous_ui") { + runIntegrationTest("standalone_miscellaneous_ui", "/") } -tasks.register("integrationTest_standalone_editing") { - runIntegrationTest("standalone_editing", "/") +tasks.register("integrationTest_standalone_run_shortcuts") { + runIntegrationTest("standalone_run_shortcuts", "/") } void runIntegrationTest(String path, String url) { @@ -222,7 +227,6 @@ void runIntegrationTest(String path, String url) { "drive", "--driver=test_driver/integration_test.dart", "--target=integration_test/${path}_test.dart", - "--web-port=4000", "--web-launch-url='$url'", "--device-id=chrome", ) diff --git a/playground/frontend/integration_test/standalone_cancel_running_example_test.dart b/playground/frontend/integration_test/standalone_cancel_running_example_test.dart index 273e2cc72180..cabd5586c773 100644 --- a/playground/frontend/integration_test/standalone_cancel_running_example_test.dart +++ b/playground/frontend/integration_test/standalone_cancel_running_example_test.dart @@ -29,20 +29,20 @@ void main() { await init(wt); // Cancel unchanged example. - await _runAndCancelExample(wt, const Duration(milliseconds: 300)); + await _runAndCancelExample(wt); final source = wt.findPlaygroundController().snippetEditingController?.activeFileController?.codeController.fullText ?? ''; - await wt.enterText(find.codeField(), source + '//comment'); + await wt.enterText(find.codeField(), '//comment\n' + source); await wt.pumpAndSettle(); // Cancel changed example. - await _runAndCancelExample(wt, const Duration(seconds: 2)); + await _runAndCancelExample(wt); }); } -Future _runAndCancelExample(WidgetTester wt, Duration duration) async { +Future _runAndCancelExample(WidgetTester wt) async { await wt.tap(find.runOrCancelButton()); - await Future.delayed(duration); + await Future.delayed(const Duration(milliseconds: 300)); await wt.tapAndSettle(find.runOrCancelButton()); diff --git a/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart b/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart index ff60a14e588c..66b85192d6c1 100644 --- a/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart +++ b/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart @@ -53,7 +53,7 @@ void main() { await wt.tapAndSettle(find.pipelineOptionsSaveAndCloseButton()); await wt.tap(find.runOrCancelButton()); - await Future.delayed(const Duration(seconds: 2)); + await Future.delayed(const Duration(milliseconds: 300)); await wt.tapAndSettle(find.runOrCancelButton()); diff --git a/playground/frontend/integration_test/standalone_editing_test.dart b/playground/frontend/integration_test/standalone_editing_test.dart index 08158827ecfd..91d9bff81e9c 100644 --- a/playground/frontend/integration_test/standalone_editing_test.dart +++ b/playground/frontend/integration_test/standalone_editing_test.dart @@ -11,18 +11,26 @@ void main() { testWidgets('Testing editing code', (WidgetTester wt) async { await init(wt); - // await _checkHotkeys(wt); await _checkAutocomplete(wt); - // await _editingAndResettingChanges(wt); - // await _checkCodeHighlighting(wt); - // await _codeBlockFoldingTest(wt); + await _editingAndResettingChanges(wt); + await _checkCodeHighlighting(wt); + await _codeBlockFoldingTest(wt); }); } Future _checkAutocomplete(WidgetTester wt) async { final codeController = wt.findOneCodeController(); - final sSuggestions = await codeController.autocompleter.getSuggestions('s'); - expect(sSuggestions, ['short', 'static', 'strictfp', 'super', 'switch', 'synchronized']); + final sSuggestions = await codeController.autocompleter.getSuggestions('sdk'); + print(sSuggestions.map((e) => "'$e'").join(', ')); + expect( + sSuggestions, + [ + 'sdkHttpMetadata', + 'sdkHttpMetadataWithoutHeaders', + 'sdkHttpResponse', + 'sdkHttpResponseWithoutHeaders' + ], + ); } Future _editingAndResettingChanges(WidgetTester wt) async { diff --git a/playground/frontend/integration_test/standalone_example_selector_test.dart b/playground/frontend/integration_test/standalone_example_selector_test.dart new file mode 100644 index 000000000000..512ce05d70d7 --- /dev/null +++ b/playground/frontend/integration_test/standalone_example_selector_test.dart @@ -0,0 +1,97 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:playground/modules/examples/components/example_list/example_item_actions.dart'; +import 'package:playground/modules/examples/components/filter/tag_bubble.dart'; +import 'package:playground/modules/examples/components/filter/type_bubble.dart'; +import 'package:playground/modules/examples/components/search_field/search_field.dart'; +import 'package:playground/modules/examples/examples_dropdown_content.dart'; +import 'package:playground/pages/standalone_playground/notifiers/example_selector_state.dart'; +import 'package:playground_components_dev/playground_components_dev.dart'; +import 'package:provider/provider.dart'; + +import 'common/common.dart'; +import 'common/common_finders.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('Example selector test', (WidgetTester wt) async { + await init(wt); + await _checkFilteringExamplesByTags(wt); + await _checkFilteringExamplesBySearchString(wt); + await _checkViewDescription(wt); + }); +} + +Future _checkFilteringExamplesByTags(WidgetTester wt) async { + await wt.tapAndSettle(find.exampleSelector()); + var allExamplesCount = _getExamplesCount(wt); + await wt.tapAndSettle(find.byType(TypeBubble).last); + var filteredExamplesCount = _getExamplesCount(wt); + + expect(allExamplesCount != filteredExamplesCount, true); + + await wt.tapAndSettle(find.exampleSelector()); + await wt.tapAndSettle(find.exampleSelector()); + + allExamplesCount = _getExamplesCount(wt); + await wt.tapAndSettle(find.byType(TagBubble).first); + filteredExamplesCount = _getExamplesCount(wt); + + expect(allExamplesCount != filteredExamplesCount, true); + + await wt.tapAndSettle(find.byType(TagBubble).at(1)); + final nextFilteredExamplesCount = _getExamplesCount(wt); + + expect(filteredExamplesCount != nextFilteredExamplesCount, true); + + await wt.tapAndSettle(find.exampleSelector()); +} + +int _getExamplesCount(WidgetTester wt) { + final state = wt + .element(find.byType(ExamplesDropdownContent)) + .read(); + + if (state.categories.isEmpty) { + return 0; + } + + return state.categories + .map((e) => e.examples.length) + .reduce((value, element) => value + element); +} + +Future _checkFilteringExamplesBySearchString(WidgetTester wt) async { + await wt.tapAndSettle(find.exampleSelector()); + final allExamplesCount = _getExamplesCount(wt); + + await wt.enterText(find.byType(SearchField), 'te'); + + final filteredExamplesCount = _getExamplesCount(wt); + + expect(allExamplesCount != filteredExamplesCount, true); + + await wt.enterText(find.byType(SearchField), ''); + + expect(_getExamplesCount(wt), allExamplesCount); + + await wt.tapAndSettle(find.exampleSelector()); +} + +Future _checkViewDescription(WidgetTester wt) async { + await wt.tapAndSettle(find.exampleSelector()); + + expect(find.descriptionPopover(), findsNothing); + + await wt.tapAndSettle( + find.descendant( + of: find.byType(ExampleItemActions), + matching: find.descriptionPopoverButton(), + ).first, + ); + + expect(find.descriptionPopover(), findsOneWidget); + + await wt.tapAndSettle(find.exampleSelector()); +} diff --git a/playground/frontend/playground_components/lib/src/controllers/snippet_file_editing_controller.dart b/playground/frontend/playground_components/lib/src/controllers/snippet_file_editing_controller.dart index d6802b9655c7..ba383ef03299 100644 --- a/playground/frontend/playground_components/lib/src/controllers/snippet_file_editing_controller.dart +++ b/playground/frontend/playground_components/lib/src/controllers/snippet_file_editing_controller.dart @@ -118,7 +118,7 @@ class SnippetFileEditingController extends ChangeNotifier { } void reset() { - codeController.text = savedFile.content; + codeController.fullText = savedFile.content; } void _onSymbolsNotifierChanged() { From 4fe215d5aa3fddcf6450e92d43c1754e71e9d49e Mon Sep 17 00:00:00 2001 From: malarg Date: Tue, 17 Jan 2023 13:17:21 +0400 Subject: [PATCH 10/52] minor fixes --- .../standalone_editing_test.dart | 32 ------------------- .../playground_controler_shortcuts_test.dart | 3 -- 2 files changed, 35 deletions(-) delete mode 100644 playground/frontend/playground_components/test/src/controllers/playground_controler_shortcuts_test.dart diff --git a/playground/frontend/integration_test/standalone_editing_test.dart b/playground/frontend/integration_test/standalone_editing_test.dart index 91d9bff81e9c..2bc235ddf29d 100644 --- a/playground/frontend/integration_test/standalone_editing_test.dart +++ b/playground/frontend/integration_test/standalone_editing_test.dart @@ -118,35 +118,3 @@ Finder _getFoldToggles() { ); return foldToggles; } - -// Future _checkHotkeys(WidgetTester wt) async { -// final undoKeys = { -// LogicalKeyboardKey.keyZ, -// LogicalKeyboardKey.meta, -// }; - -// final redoKeys = { -// LogicalKeyboardKey.keyZ, -// LogicalKeyboardKey.meta, -// LogicalKeyboardKey.shift, -// }; - -// final playgroundController = wt.findPlaygroundController(); - -// final code = playgroundController.source; - -// expect(code, isNotNull); - -// await wt.enterText(find.codeField(), 'print("Hello World!");'); -// await wt.pumpAndSettle(); - -// expect(playgroundController.source != code, true); - -// await wt.runShortcut(undoKeys); - -// expect(playgroundController.source, equals(code)); - -// await wt.runShortcut(redoKeys); - -// expect(playgroundController.source != code, true); -// } \ No newline at end of file diff --git a/playground/frontend/playground_components/test/src/controllers/playground_controler_shortcuts_test.dart b/playground/frontend/playground_components/test/src/controllers/playground_controler_shortcuts_test.dart deleted file mode 100644 index f3d44c8bc6cd..000000000000 --- a/playground/frontend/playground_components/test/src/controllers/playground_controler_shortcuts_test.dart +++ /dev/null @@ -1,3 +0,0 @@ -void main() { - -} \ No newline at end of file From 0e2a469b6d523f6249e336a814657b5881e81b6f Mon Sep 17 00:00:00 2001 From: malarg Date: Tue, 17 Jan 2023 13:22:45 +0400 Subject: [PATCH 11/52] rat --- .../standalone_editing_test.dart | 18 ++++++++++++++++++ .../standalone_example_selector_test.dart | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/playground/frontend/integration_test/standalone_editing_test.dart b/playground/frontend/integration_test/standalone_editing_test.dart index 2bc235ddf29d..6fe89eee29ec 100644 --- a/playground/frontend/integration_test/standalone_editing_test.dart +++ b/playground/frontend/integration_test/standalone_editing_test.dart @@ -1,3 +1,21 @@ +/* + * 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:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; diff --git a/playground/frontend/integration_test/standalone_example_selector_test.dart b/playground/frontend/integration_test/standalone_example_selector_test.dart index 512ce05d70d7..c4ef86828fe5 100644 --- a/playground/frontend/integration_test/standalone_example_selector_test.dart +++ b/playground/frontend/integration_test/standalone_example_selector_test.dart @@ -1,3 +1,21 @@ +/* + * 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:integration_test/integration_test.dart'; import 'package:playground/modules/examples/components/example_list/example_item_actions.dart'; From a33d0585d7acd04cfb72691f46cdff3d2d19c395 Mon Sep 17 00:00:00 2001 From: malarg Date: Tue, 24 Jan 2023 10:24:49 +0400 Subject: [PATCH 12/52] fix pr --- .../common/common_finders.dart | 9 +- ...tandalone_cancel_running_example_test.dart | 25 +++-- ..._change_pipeline_options_and_run_test.dart | 67 +++++++++++-- .../standalone_editing_test.dart | 53 +++++----- .../standalone_example_selector_test.dart | 99 +++++++++++++++---- .../pipeline_options_form.dart | 3 - .../shortcuts/constants/global_shortcuts.dart | 4 +- .../lib/playground_components.dart | 12 +-- .../controllers/playground_controller.dart | 24 ++++- .../lib/src/models/intents.dart | 5 + .../lib/src/repositories/code_repository.dart | 22 +++-- .../lib/src/util/logical_keyboard_key.dart | 9 ++ .../lib/src/util/native_platform.dart | 7 ++ .../playground_components/pubspec.yaml | 1 + 14 files changed, 251 insertions(+), 89 deletions(-) create mode 100644 playground/frontend/playground_components/lib/src/util/logical_keyboard_key.dart create mode 100644 playground/frontend/playground_components/lib/src/util/native_platform.dart diff --git a/playground/frontend/integration_test/common/common_finders.dart b/playground/frontend/integration_test/common/common_finders.dart index 88b0ea29e7f3..816f90099e1e 100644 --- a/playground/frontend/integration_test/common/common_finders.dart +++ b/playground/frontend/integration_test/common/common_finders.dart @@ -18,8 +18,8 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:playground/components/dropdown_button/dropdown_button.dart'; import 'package:playground/modules/actions/components/reset_action.dart'; +import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown.dart'; import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_body.dart'; import 'package:playground/modules/examples/components/description_popover/description_popover.dart'; import 'package:playground/modules/examples/components/description_popover/description_popover_button.dart'; @@ -37,10 +37,7 @@ import 'package:playground_components_dev/playground_components_dev.dart'; extension CommonFindersExtension on CommonFinders { Finder appDropdownButtonWithText(String text) { - return find.descendant( - of: byType(AppDropdownButton), - matching: find.text(text), - ); + return find.byType(PipelineOptionsDropdown); } Finder codeTextAreaWrapper() { @@ -95,7 +92,7 @@ extension CommonFindersExtension on CommonFinders { return byType(MoreActions); } - Finder pipelineOptionsOptionsTab() { + Finder pipelineOptionsListTab() { return find.byKey(PipelineOptionsDropdownBody.optionsTabKey); } diff --git a/playground/frontend/integration_test/standalone_cancel_running_example_test.dart b/playground/frontend/integration_test/standalone_cancel_running_example_test.dart index cabd5586c773..0e4d01ef2d2e 100644 --- a/playground/frontend/integration_test/standalone_cancel_running_example_test.dart +++ b/playground/frontend/integration_test/standalone_cancel_running_example_test.dart @@ -29,21 +29,34 @@ void main() { await init(wt); // Cancel unchanged example. - await _runAndCancelExample(wt); + await _runAndCancelExample(wt, const Duration(milliseconds: 300)); - final source = wt.findPlaygroundController().snippetEditingController?.activeFileController?.codeController.fullText ?? ''; + final source = wt + .findPlaygroundController() + .snippetEditingController + ?.activeFileController + ?.codeController + .fullText ?? + ''; await wt.enterText(find.codeField(), '//comment\n' + source); await wt.pumpAndSettle(); // Cancel changed example. - await _runAndCancelExample(wt); + await _runAndCancelExample(wt, const Duration(milliseconds: 5000)); }); } -Future _runAndCancelExample(WidgetTester wt) async { +Future _runAndCancelExample(WidgetTester wt, Duration duration) async { await wt.tap(find.runOrCancelButton()); - await Future.delayed(const Duration(milliseconds: 300)); - + try { + await wt.pumpAndSettle( + const Duration(milliseconds: 100), + EnginePhase.sendSemanticsUpdate, + duration, + ); + } catch (e) { + //ignore + } await wt.tapAndSettle(find.runOrCancelButton()); final playgroundController = wt.findPlaygroundController(); diff --git a/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart b/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart index 66b85192d6c1..b8998c8eb76c 100644 --- a/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart +++ b/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart @@ -16,11 +16,12 @@ * limitations under the License. */ -import 'package:flutter/widgets.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_body.dart'; import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_input.dart'; +import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_options_text_field.dart'; import 'package:playground_components_dev/playground_components_dev.dart'; import 'common/common.dart'; @@ -42,9 +43,9 @@ void main() { _checkIfRawTextCorrect('--some test --some2 test2'); - await wt.tapAndSettle(find.pipelineOptionsOptionsTab()); + await wt.tapAndSettle(find.pipelineOptionsListTab()); - await wt.tap(find.byKey(const ValueKey('PipelineOptionsDelete1'))); + await wt.tap(_getBottomDeleteIcon(wt)); await wt.tapAndSettle(find.pipelineOptionsRawTab()); @@ -58,6 +59,7 @@ void main() { await wt.tapAndSettle(find.runOrCancelButton()); final playgroundController = wt.findPlaygroundController(); + expect(playgroundController.outputResult, contains('--some test')); expect(playgroundController.outputResult, contains('Pipeline cancelled')); }, ); @@ -65,27 +67,74 @@ void main() { Future _addTwoOptions(WidgetTester wt) async { await wt.enterText( - find.byKey(const ValueKey('PipelineOptionsKey0')), + _getPipelineOptionsTextField(wt, _Placement.topLeft), 'some', ); await wt.enterText( - find.byKey(const ValueKey('PipelineOptionsValue0')), + _getPipelineOptionsTextField(wt, _Placement.topRight), 'test', ); - + await wt.tap(find.byKey(PipelineOptionsDropdownBody.addOptionButtonKey)); await wt.pumpAndSettle(); - + await wt.enterText( - find.byKey(const ValueKey('PipelineOptionsKey1')), + _getPipelineOptionsTextField(wt, _Placement.bottomLeft), 'some2', ); await wt.enterText( - find.byKey(const ValueKey('PipelineOptionsValue1')), + _getPipelineOptionsTextField(wt, _Placement.bottomRight), 'test2', ); } +Finder _getPipelineOptionsTextField(WidgetTester wt, _Placement placement) { + final fields = find.byType(PipelineOptionsTextField); + final positions = <_Point>[]; + for (var i = 0; i < fields.evaluate().length; i++) { + final element = fields.at(i); + final position = wt.getCenter(element); + positions.add(_Point(position.dx, position.dy)); + } + + late _Point Function(_Point, _Point) reduceFunc; + switch (placement) { + case _Placement.topLeft: + reduceFunc = (a, b) => a.x <= b.x && a.y <= b.y ? a : b; + break; + case _Placement.topRight: + reduceFunc = (a, b) => a.x >= b.x && a.y <= b.y ? a : b; + break; + case _Placement.bottomLeft: + reduceFunc = (a, b) => a.x <= b.x && a.y >= b.y ? a : b; + break; + case _Placement.bottomRight: + reduceFunc = (a, b) => a.x >= b.x && a.y >= b.y ? a : b; + break; + } + final position = positions.reduce(reduceFunc); + return fields.at(positions.indexOf(position)); +} + +enum _Placement { topLeft, topRight, bottomLeft, bottomRight } + +class _Point { + final double x; + final double y; + + _Point(this.x, this.y); +} + +Finder _getBottomDeleteIcon(WidgetTester wt) { + Finder deleteIcons = find.byIcon(Icons.delete_outlined); + + Finder bottomIcon = + wt.getCenter(deleteIcons.at(0)).dy > wt.getCenter(deleteIcons.at(1)).dy + ? deleteIcons.at(0) + : deleteIcons.at(1); + return bottomIcon; +} + void _checkIfRawTextCorrect(String text) { expect( find.descendant( diff --git a/playground/frontend/integration_test/standalone_editing_test.dart b/playground/frontend/integration_test/standalone_editing_test.dart index 6fe89eee29ec..eb6cd1c29511 100644 --- a/playground/frontend/integration_test/standalone_editing_test.dart +++ b/playground/frontend/integration_test/standalone_editing_test.dart @@ -29,29 +29,31 @@ void main() { testWidgets('Testing editing code', (WidgetTester wt) async { await init(wt); + await _checkResetDefaultCode(wt); + await _checkResetEditedCode(wt); await _checkAutocomplete(wt); - await _editingAndResettingChanges(wt); - await _checkCodeHighlighting(wt); - await _codeBlockFoldingTest(wt); + await _checkCodeHighlightedMultipleColors(wt); + await _checkCodeBlockFolding(wt); }); } Future _checkAutocomplete(WidgetTester wt) async { - final codeController = wt.findOneCodeController(); - final sSuggestions = await codeController.autocompleter.getSuggestions('sdk'); - print(sSuggestions.map((e) => "'$e'").join(', ')); - expect( - sSuggestions, - [ - 'sdkHttpMetadata', - 'sdkHttpMetadataWithoutHeaders', - 'sdkHttpResponse', - 'sdkHttpResponseWithoutHeaders' - ], - ); + await wt.enterText(find.codeField(), '\n\n\n\n\nsdk'); + + final playgroundController = wt.findPlaygroundController(); + await wt.runShortcut( + playgroundController.showAutocompleterShortcut.shortcuts.keys); + await wt.pumpAndSettle(); + + expect(find.text('sdkHttpMetadata'), findsOneWidget); + expect(find.text('sdkHttpMetadataWithoutHeaders'), findsOneWidget); + expect(find.text('sdkHttpResponse'), findsOneWidget); + expect(find.text('sdkHttpResponseWithoutHeaders'), findsOneWidget); + + await wt.tapAndSettle(find.resetButton()); } -Future _editingAndResettingChanges(WidgetTester wt) async { +Future _checkResetDefaultCode(WidgetTester wt) async { final playgroundController = wt.findPlaygroundController(); final code = playgroundController.source; @@ -60,19 +62,24 @@ Future _editingAndResettingChanges(WidgetTester wt) async { await wt.tapAndSettle(find.resetButton()); - expect(playgroundController.source == code, true); + expect(playgroundController.source, code); +} + +Future _checkResetEditedCode(WidgetTester wt) async { + final playgroundController = wt.findPlaygroundController(); + final code = playgroundController.source; await wt.enterText(find.codeField(), 'print("Hello World!'); await wt.pumpAndSettle(); - expect(playgroundController.source != code, true); + expect(playgroundController.source, isNot(code)); await wt.tapAndSettle(find.resetButton()); - expect(playgroundController.source, equals(code)); + expect(playgroundController.source, code); } -Future _checkCodeHighlighting(WidgetTester wt) async { +Future _checkCodeHighlightedMultipleColors(WidgetTester wt) async { final codeController = wt.findOneCodeController(); final colors = {}; var textSpan = codeController.lastTextSpan; @@ -94,7 +101,7 @@ void _collectTextSpanTreeTextColors(InlineSpan? span, Set colors) { } } -Future _codeBlockFoldingTest(WidgetTester wt) async { +Future _checkCodeBlockFolding(WidgetTester wt) async { const code = ''' public class MyClass { public static void main(String[] args) { @@ -112,11 +119,11 @@ public class MyClass { public class MyClass { '''; - expect(wt.findOneCodeController().text, equals(foldedCode)); + expect(wt.findOneCodeController().text, foldedCode); await wt.tapAndSettle(_getFoldToggles()); - expect(wt.findOneCodeController().text, equals(code)); + expect(wt.findOneCodeController().text, code); } Finder _getTopToggle(WidgetTester wt) { diff --git a/playground/frontend/integration_test/standalone_example_selector_test.dart b/playground/frontend/integration_test/standalone_example_selector_test.dart index c4ef86828fe5..dca2d0b8c882 100644 --- a/playground/frontend/integration_test/standalone_example_selector_test.dart +++ b/playground/frontend/integration_test/standalone_example_selector_test.dart @@ -24,6 +24,7 @@ import 'package:playground/modules/examples/components/filter/type_bubble.dart'; import 'package:playground/modules/examples/components/search_field/search_field.dart'; import 'package:playground/modules/examples/examples_dropdown_content.dart'; import 'package:playground/pages/standalone_playground/notifiers/example_selector_state.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:playground_components_dev/playground_components_dev.dart'; import 'package:provider/provider.dart'; @@ -35,51 +36,100 @@ void main() { testWidgets('Example selector test', (WidgetTester wt) async { await init(wt); + await _checkFilteringExamplesByTypes(wt); await _checkFilteringExamplesByTags(wt); await _checkFilteringExamplesBySearchString(wt); await _checkViewDescription(wt); }); } -Future _checkFilteringExamplesByTags(WidgetTester wt) async { +Future _checkFilteringExamplesByTypes(WidgetTester wt) async { await wt.tapAndSettle(find.exampleSelector()); - var allExamplesCount = _getExamplesCount(wt); - await wt.tapAndSettle(find.byType(TypeBubble).last); - var filteredExamplesCount = _getExamplesCount(wt); + final allExamplesCount = _getExamplesCount(wt); + await wt.tapAndSettle(find.widgetWithText(TypeBubble, ExampleType.test.name)); + + final filteredExamplesCount = _getExamplesCount(wt); - expect(allExamplesCount != filteredExamplesCount, true); + expect(allExamplesCount, isNot(filteredExamplesCount)); + + final categoriesWithExamples = _getCategoriesWithExamples(wt); + for (final example in categoriesWithExamples.expand((e) => e.examples)) { + expect(example.type, ExampleType.test); + } await wt.tapAndSettle(find.exampleSelector()); +} + +Future _checkFilteringExamplesByTags(WidgetTester wt) async { await wt.tapAndSettle(find.exampleSelector()); - allExamplesCount = _getExamplesCount(wt); - await wt.tapAndSettle(find.byType(TagBubble).first); - filteredExamplesCount = _getExamplesCount(wt); + final allExamplesCount = _getExamplesCount(wt); + final sortedTags = _getSortedTags(wt); + await wt.tapAndSettle(find.widgetWithText(TagBubble, sortedTags[0])); + final filteredExamplesCount = _getExamplesCount(wt); - expect(allExamplesCount != filteredExamplesCount, true); + expect(_areCategoriesContainsTag(wt, [sortedTags[0]]), isTrue); + expect(allExamplesCount, isNot(filteredExamplesCount)); - await wt.tapAndSettle(find.byType(TagBubble).at(1)); + await wt.tapAndSettle(find.widgetWithText(TagBubble, sortedTags[1])); final nextFilteredExamplesCount = _getExamplesCount(wt); - expect(filteredExamplesCount != nextFilteredExamplesCount, true); + expect(_areCategoriesContainsTag(wt, [sortedTags[0], sortedTags[1]]), isTrue); + expect(filteredExamplesCount, isNot(nextFilteredExamplesCount)); await wt.tapAndSettle(find.exampleSelector()); } +List _getSortedTags(WidgetTester wt) { + final categoriesWithExamples = _getCategoriesWithExamples(wt); + final tags = categoriesWithExamples + .expand((e) => e.examples) + .expand((e) => e.tags); + final tagsMap = {}; + for (final tag in tags) { + tagsMap[tag] = tagsMap[tag] == null ? 1 : tagsMap[tag]! + 1; + } + final tagsMapList = tagsMap.entries.toList()..sort((a, b) => b.value.compareTo(a.value)); + return tagsMapList.map((e) => e.key).toList(); +} + +bool _areCategoriesContainsTag(WidgetTester wt, List tags) { + final categoriesWithExamples = _getCategoriesWithExamples(wt); + final examples = categoriesWithExamples.expand((e) => e.examples); + + if (examples.isEmpty) { + return true; + } + + for (final example in examples) { + for (final tag in tags) { + if (!example.tags.contains(tag)) { + return false; + } + } + } + return true; +} + int _getExamplesCount(WidgetTester wt) { - final state = wt - .element(find.byType(ExamplesDropdownContent)) - .read(); + final categories = _getCategoriesWithExamples(wt); - if (state.categories.isEmpty) { + if (categories.isEmpty) { return 0; } - return state.categories + return categories .map((e) => e.examples.length) .reduce((value, element) => value + element); } +List _getCategoriesWithExamples(WidgetTester wt) { + return wt + .element(find.byType(ExamplesDropdownContent)) + .read() + .categories; +} + Future _checkFilteringExamplesBySearchString(WidgetTester wt) async { await wt.tapAndSettle(find.exampleSelector()); final allExamplesCount = _getExamplesCount(wt); @@ -90,6 +140,13 @@ Future _checkFilteringExamplesBySearchString(WidgetTester wt) async { expect(allExamplesCount != filteredExamplesCount, true); + final categories = _getCategoriesWithExamples(wt); + for (var category in categories) { + for (var example in category.examples) { + expect(example.name.toLowerCase(), contains('te')); + } + } + await wt.enterText(find.byType(SearchField), ''); expect(_getExamplesCount(wt), allExamplesCount); @@ -103,10 +160,12 @@ Future _checkViewDescription(WidgetTester wt) async { expect(find.descriptionPopover(), findsNothing); await wt.tapAndSettle( - find.descendant( - of: find.byType(ExampleItemActions), - matching: find.descriptionPopoverButton(), - ).first, + find + .descendant( + of: find.byType(ExampleItemActions), + matching: find.descriptionPopoverButton(), + ) + .first, ); expect(find.descriptionPopover(), findsOneWidget); diff --git a/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_form.dart b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_form.dart index ffc44c2e8f84..6aa41c0c3cdf 100644 --- a/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_form.dart +++ b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_form.dart @@ -55,7 +55,6 @@ class PipelineOptionsForm extends StatelessWidget { (index, controller) => Row( children: [ Expanded( - key: ValueKey('PipelineOptionsKey$index'), child: SizedBox( height: kTextFieldHeight, child: PipelineOptionsTextField( @@ -65,7 +64,6 @@ class PipelineOptionsForm extends StatelessWidget { ), kSpace, Expanded( - key: ValueKey('PipelineOptionsValue$index'), child: SizedBox( height: kTextFieldHeight, child: PipelineOptionsTextField( @@ -76,7 +74,6 @@ class PipelineOptionsForm extends StatelessWidget { SizedBox( width: kIconSizeLg, child: IconButton( - key: ValueKey('PipelineOptionsDelete$index'), iconSize: kIconSizeMd, splashRadius: kIconButtonSplashRadius, icon: const Icon( diff --git a/playground/frontend/lib/modules/shortcuts/constants/global_shortcuts.dart b/playground/frontend/lib/modules/shortcuts/constants/global_shortcuts.dart index 03797eaff3a0..ac0a744f97bd 100644 --- a/playground/frontend/lib/modules/shortcuts/constants/global_shortcuts.dart +++ b/playground/frontend/lib/modules/shortcuts/constants/global_shortcuts.dart @@ -32,7 +32,7 @@ class NewExampleIntent extends BeamIntent { final kClearOutputShortcut = BeamShortcut( shortcuts: LogicalKeySet( - LogicalKeyboardKey.meta, + LogicalKeyboardKeyExtension.metaOrControl, LogicalKeyboardKey.keyB, ), actionIntent: const ClearOutputIntent(), @@ -46,7 +46,7 @@ final kClearOutputShortcut = BeamShortcut( final kNewExampleShortcut = BeamShortcut( shortcuts: LogicalKeySet( - LogicalKeyboardKey.meta, + LogicalKeyboardKeyExtension.metaOrControl, LogicalKeyboardKey.keyM, ), actionIntent: const NewExampleIntent(), diff --git a/playground/frontend/playground_components/lib/playground_components.dart b/playground/frontend/playground_components/lib/playground_components.dart index 8d37335e9cca..ddbb6315eca5 100644 --- a/playground/frontend/playground_components/lib/playground_components.dart +++ b/playground/frontend/playground_components/lib/playground_components.dart @@ -17,16 +17,12 @@ */ export 'src/cache/example_cache.dart'; - export 'src/constants/colors.dart'; export 'src/constants/links.dart'; export 'src/constants/sizes.dart'; - export 'src/controllers/example_loaders/examples_loader.dart'; export 'src/controllers/playground_controller.dart'; - export 'src/enums/complexity.dart'; - export 'src/models/category_with_examples.dart'; export 'src/models/example.dart'; export 'src/models/example_base.dart'; @@ -47,23 +43,17 @@ export 'src/models/shortcut.dart'; export 'src/models/snippet_file.dart'; export 'src/models/toast.dart'; export 'src/models/toast_type.dart'; - export 'src/playground_components.dart'; - export 'src/repositories/code_client/grpc_code_client.dart'; export 'src/repositories/code_repository.dart'; export 'src/repositories/example_client/grpc_example_client.dart'; export 'src/repositories/example_repository.dart'; - export 'src/router/router_delegate.dart'; - export 'src/services/symbols/loaders/yaml.dart'; - export 'src/theme/switch_notifier.dart'; export 'src/theme/theme.dart'; - +export 'src/util/logical_keyboard_key.dart'; export 'src/util/pipeline_options.dart'; - export 'src/widgets/bubble.dart'; export 'src/widgets/clickable.dart'; export 'src/widgets/complexity.dart'; diff --git a/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart index 0be59e88aeae..638219e26643 100644 --- a/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart +++ b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart @@ -40,6 +40,7 @@ import '../repositories/models/run_code_request.dart'; import '../repositories/models/run_code_result.dart'; import '../services/symbols/loaders/map.dart'; import '../services/symbols/symbols_notifier.dart'; +import '../util/logical_keyboard_key.dart'; import '../util/pipeline_options.dart'; import 'example_loaders/examples_loader.dart'; import 'snippet_editing_controller.dart'; @@ -288,6 +289,12 @@ class PlaygroundController with ChangeNotifier { notifyListeners(); } + void showAutocompleter() { + snippetEditingController?.activeFileController?.codeController + .generateSuggestions(); + notifyListeners(); + } + void resetError() { if (result == null) { return; @@ -480,7 +487,7 @@ class PlaygroundController with ChangeNotifier { late BeamShortcut runShortcut = BeamShortcut( shortcuts: LogicalKeySet( - LogicalKeyboardKey.meta, + LogicalKeyboardKeyExtension.metaOrControl, LogicalKeyboardKey.enter, ), actionIntent: const RunIntent(), @@ -491,7 +498,7 @@ class PlaygroundController with ChangeNotifier { late BeamShortcut resetShortcut = BeamShortcut( shortcuts: LogicalKeySet( - LogicalKeyboardKey.meta, + LogicalKeyboardKeyExtension.metaOrControl, LogicalKeyboardKey.shift, LogicalKeyboardKey.keyE, ), @@ -501,8 +508,21 @@ class PlaygroundController with ChangeNotifier { ), ); + late BeamShortcut showAutocompleterShortcut = BeamShortcut( + shortcuts: LogicalKeySet( + LogicalKeyboardKeyExtension.metaOrControl, + LogicalKeyboardKey.shift, + LogicalKeyboardKey.keyS, + ), + actionIntent: const ShowAutocompleterIntent(), + createAction: (BuildContext context) => CallbackAction( + onInvoke: (_) => showAutocompleter(), + ), + ); + List get shortcuts => [ runShortcut, resetShortcut, + showAutocompleterShortcut, ]; } diff --git a/playground/frontend/playground_components/lib/src/models/intents.dart b/playground/frontend/playground_components/lib/src/models/intents.dart index 74ef7c4c557e..27e54bc5fd97 100644 --- a/playground/frontend/playground_components/lib/src/models/intents.dart +++ b/playground/frontend/playground_components/lib/src/models/intents.dart @@ -33,3 +33,8 @@ class ResetIntent extends BeamIntent { class RunIntent extends BeamIntent { const RunIntent() : super(slug: 'intents.playground.run'); } + +class ShowAutocompleterIntent extends BeamIntent { + const ShowAutocompleterIntent() + : super(slug: 'intents.playground.showAutocompleter'); +} diff --git a/playground/frontend/playground_components/lib/src/repositories/code_repository.dart b/playground/frontend/playground_components/lib/src/repositories/code_repository.dart index c9efc3146c09..8468bf12cf45 100644 --- a/playground/frontend/playground_components/lib/src/repositories/code_repository.dart +++ b/playground/frontend/playground_components/lib/src/repositories/code_repository.dart @@ -32,18 +32,28 @@ const kTimeoutErrorText = const kUnknownErrorText = 'Something went wrong. Please try again later or create a GitHub issue'; const kProcessingStartedText = 'The processing has started\n'; +const kProcessingStartedOptionsText = + 'The processing has started with pipeline options: '; // TODO(alexeyinkin): Rename. This is not a repository but a higher level client. class CodeRepository { final CodeClient _client; - CodeRepository({required CodeClient client,}): _client = client; + CodeRepository({ + required CodeClient client, + }) : _client = client; Stream runCode(RunCodeRequest request) async* { try { - const initResult = RunCodeResult( + final log = request.pipelineOptions.isEmpty + ? kProcessingStartedText + : kProcessingStartedOptionsText + + request.pipelineOptions.entries + .map((e) => '--${e.key} ${e.value}') + .join(' '); + final initResult = RunCodeResult( status: RunCodeStatus.preparation, - log: kProcessingStartedText, + log: log, ); yield initResult; @@ -139,8 +149,7 @@ class CodeRepository { ); case RunCodeStatus.validationError: - final output = - await _client.getValidationErrorOutput(pipelineUuid); + final output = await _client.getValidationErrorOutput(pipelineUuid); return RunCodeResult( status: status, output: output.output, @@ -149,8 +158,7 @@ class CodeRepository { ); case RunCodeStatus.preparationError: - final output = - await _client.getPreparationErrorOutput(pipelineUuid); + final output = await _client.getPreparationErrorOutput(pipelineUuid); return RunCodeResult( status: status, output: output.output, diff --git a/playground/frontend/playground_components/lib/src/util/logical_keyboard_key.dart b/playground/frontend/playground_components/lib/src/util/logical_keyboard_key.dart new file mode 100644 index 000000000000..d3a0b20b2b37 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/util/logical_keyboard_key.dart @@ -0,0 +1,9 @@ +import 'package:flutter/services.dart'; + +import 'native_platform.dart'; + +extension LogicalKeyboardKeyExtension on LogicalKeyboardKey { + static LogicalKeyboardKey get metaOrControl => NativePlatform.isMacOs + ? LogicalKeyboardKey.meta + : LogicalKeyboardKey.control; +} diff --git a/playground/frontend/playground_components/lib/src/util/native_platform.dart b/playground/frontend/playground_components/lib/src/util/native_platform.dart new file mode 100644 index 000000000000..ae0b972791c1 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/util/native_platform.dart @@ -0,0 +1,7 @@ +import 'package:os_detect/os_detect.dart' as platform; + +class NativePlatform { + static bool get isMacOs => + platform.operatingSystemVersion.contains('Mac OS') || + platform.operatingSystemVersion.contains('Macintosh'); +} diff --git a/playground/frontend/playground_components/pubspec.yaml b/playground/frontend/playground_components/pubspec.yaml index a87ac0f2a23a..e7751981f1b9 100644 --- a/playground/frontend/playground_components/pubspec.yaml +++ b/playground/frontend/playground_components/pubspec.yaml @@ -46,6 +46,7 @@ dependencies: json_annotation: ^4.7.0 keyed_collection_widgets: ^0.4.3 meta: ^1.7.0 + os_detect: ^2.0.1 protobuf: ^2.1.0 provider: ^6.0.3 rxdart: ^0.27.7 From b3503515ac03dfb2263eca42c384574bb1bd5acd Mon Sep 17 00:00:00 2001 From: malarg Date: Tue, 24 Jan 2023 10:27:48 +0400 Subject: [PATCH 13/52] minor --- playground/frontend/assets/translations/en.yaml | 1 + playground/frontend/pubspec.lock | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/playground/frontend/assets/translations/en.yaml b/playground/frontend/assets/translations/en.yaml index 023e0324c8e7..6e6c95bae797 100644 --- a/playground/frontend/assets/translations/en.yaml +++ b/playground/frontend/assets/translations/en.yaml @@ -19,4 +19,5 @@ intents: playground: clearOutput: 'Clear Output' newExample: 'New Example' + showAutocompleter: 'Show Autocompleter' usesEmulatedData: 'This examples uses emulated data' diff --git a/playground/frontend/pubspec.lock b/playground/frontend/pubspec.lock index c5d387dc1ec7..792d53e859b8 100644 --- a/playground/frontend/pubspec.lock +++ b/playground/frontend/pubspec.lock @@ -575,6 +575,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.2.0" + os_detect: + dependency: transitive + description: + name: os_detect + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" package_config: dependency: transitive description: From 01fe5a764fbb2e7ab73b2abff671ca91161aab62 Mon Sep 17 00:00:00 2001 From: malarg Date: Tue, 24 Jan 2023 11:58:17 +0400 Subject: [PATCH 14/52] minor --- .../standalone_cancel_running_example_test.dart | 3 ++- .../standalone_change_pipeline_options_and_run_test.dart | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/playground/frontend/integration_test/standalone_cancel_running_example_test.dart b/playground/frontend/integration_test/standalone_cancel_running_example_test.dart index bfe091e16cfe..9b26c8bef87b 100644 --- a/playground/frontend/integration_test/standalone_cancel_running_example_test.dart +++ b/playground/frontend/integration_test/standalone_cancel_running_example_test.dart @@ -18,6 +18,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:playground_components_dev/playground_components_dev.dart'; import 'common/common.dart'; @@ -62,6 +63,6 @@ Future _runAndCancelExample(WidgetTester wt, Duration duration) async { final playgroundController = wt.findPlaygroundController(); expect( playgroundController.codeRunner.resultLogOutput, - contains('Pipeline cancelled'), + contains(kExecutionCancelledText), ); } diff --git a/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart b/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart index 5c66f65db45b..2b43dc92b283 100644 --- a/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart +++ b/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart @@ -22,6 +22,7 @@ import 'package:integration_test/integration_test.dart'; import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_body.dart'; import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_input.dart'; import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_options_text_field.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:playground_components_dev/playground_components_dev.dart'; import 'common/common.dart'; @@ -65,7 +66,7 @@ void main() { ); expect( playgroundController.codeRunner.resultLogOutput, - contains('Pipeline cancelled'), + contains(kExecutionCancelledText), ); }, ); From 4275eb6ccd9ce8520eec0d33567e451717d70786 Mon Sep 17 00:00:00 2001 From: malarg Date: Tue, 24 Jan 2023 13:11:58 +0400 Subject: [PATCH 15/52] rat --- .../lib/src/util/logical_keyboard_key.dart | 18 ++++++++++++++++++ .../lib/src/util/native_platform.dart | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/playground/frontend/playground_components/lib/src/util/logical_keyboard_key.dart b/playground/frontend/playground_components/lib/src/util/logical_keyboard_key.dart index d3a0b20b2b37..2602bc07dad1 100644 --- a/playground/frontend/playground_components/lib/src/util/logical_keyboard_key.dart +++ b/playground/frontend/playground_components/lib/src/util/logical_keyboard_key.dart @@ -1,3 +1,21 @@ +/* + * 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/services.dart'; import 'native_platform.dart'; diff --git a/playground/frontend/playground_components/lib/src/util/native_platform.dart b/playground/frontend/playground_components/lib/src/util/native_platform.dart index ae0b972791c1..e67c20a92241 100644 --- a/playground/frontend/playground_components/lib/src/util/native_platform.dart +++ b/playground/frontend/playground_components/lib/src/util/native_platform.dart @@ -1,3 +1,21 @@ +/* + * 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:os_detect/os_detect.dart' as platform; class NativePlatform { From 7316da050013da3043277ba0b6ac2da67ca8b893 Mon Sep 17 00:00:00 2001 From: malarg Date: Thu, 26 Jan 2023 09:47:53 +0400 Subject: [PATCH 16/52] integration test finder written --- playground/frontend/build.gradle | 28 +++++-- .../integration_test/common/finder.dart | 72 ++++++++++++++++++ ..._change_pipeline_options_and_run_test.dart | 56 ++++---------- .../pipeline_options_form.dart | 42 ++--------- .../pipeline_options_row.dart | 75 +++++++++++++++++++ 5 files changed, 188 insertions(+), 85 deletions(-) create mode 100644 playground/frontend/integration_test/common/finder.dart create mode 100644 playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_row.dart diff --git a/playground/frontend/build.gradle b/playground/frontend/build.gradle index bcd5f53660e4..2e54c20f4cc1 100644 --- a/playground/frontend/build.gradle +++ b/playground/frontend/build.gradle @@ -193,31 +193,45 @@ tasks.register("integrationTest") { } tasks.register("integrationTest_standalone_cancel_running_example") { - runIntegrationTest("standalone_cancel_running_example", "/") + doLast { + runIntegrationTest("standalone_cancel_running_example", "/") + } } tasks.register("integrationTest_standalone_change_example_sdk_run") { - runIntegrationTest("standalone_change_example_sdk_run", "/") + doLast { + runIntegrationTest("standalone_change_example_sdk_run", "/") + } } tasks.register("integrationTest_standalone_change_pipeline_options_and_run") { - runIntegrationTest("standalone_change_pipeline_options_and_run", "/") + doLast { + runIntegrationTest("standalone_change_pipeline_options_and_run", "/") + } } tasks.register("integrationTest_standalone_editing") { - runIntegrationTest("standalone_editing", "/") + doLast { + runIntegrationTest("standalone_editing", "/") + } } tasks.register("integrationTest_standalone_example_selector") { - runIntegrationTest("standalone_example_selector", "/") + doLast { + runIntegrationTest("standalone_example_selector", "/") + } } tasks.register("integrationTest_standalone_miscellaneous_ui") { - runIntegrationTest("standalone_miscellaneous_ui", "/") + doLast { + runIntegrationTest("standalone_miscellaneous_ui", "/") + } } tasks.register("integrationTest_standalone_run_shortcuts") { - runIntegrationTest("standalone_run_shortcuts", "/") + doLast { + runIntegrationTest("standalone_run_shortcuts", "/") + } } void runIntegrationTest(String path, String url) { diff --git a/playground/frontend/integration_test/common/finder.dart b/playground/frontend/integration_test/common/finder.dart new file mode 100644 index 000000000000..de1d84b224e8 --- /dev/null +++ b/playground/frontend/integration_test/common/finder.dart @@ -0,0 +1,72 @@ +import 'package:flutter/painting.dart'; +import 'package:flutter_test/flutter_test.dart'; + +extension FinderExtension on Finder { + Finder getChildrenByType(Type childType) { + final finders = evaluate(); + final childElements = finders + .map((f) => collectAllElementsFrom(f, skipOffstage: true)) + .expand((e) => e) + .where((e) => e.widget.runtimeType == childType); + + return find.byElementPredicate( + (element) => childElements.contains(element), + ); + } + + Finder alignedIndexAt(int index, Axis axis, WidgetTester wt) { + final finders = evaluate(); + + if (index > finders.length - 1) { + throw IndexError(index, finders); + } + + final offsets = <_IndexAndOffset>[]; + + for (int i = 0; i < finders.length; i++) { + offsets.add(_IndexAndOffset(i, wt.getCenter(at(i)))); + } + + offsets.sort( + (a, b) => axis == Axis.vertical + ? _compareDoubles(a.offset.dy, b.offset.dy) + : _compareDoubles(a.offset.dx, b.offset.dx), + ); + + final result = find.byElementPredicate((element) { + return axis == Axis.vertical + ? finders.contains(element) && + wt.getCenter(at(finders.toList().indexOf(element))).dy == + offsets[index].offset.dy + : finders.contains(element) && + wt.getCenter(at(finders.toList().indexOf(element))).dx == + offsets[index].offset.dx; + }); + + print(result.evaluate().length); + return result; + } + + int _compareDoubles(double a, double b) { + if (a == b) { + return 0; + } else if (a > b) { + return 1; + } else { + return -1; + } + } + + Finder horizontallyAt(int index, WidgetTester wt) => + alignedIndexAt(index, Axis.horizontal, wt); + + Finder verticallyAt(int index, WidgetTester wt) => + alignedIndexAt(index, Axis.vertical, wt); +} + +class _IndexAndOffset { + final int index; + final Offset offset; + + _IndexAndOffset(this.index, this.offset); +} diff --git a/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart b/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart index 2b43dc92b283..2e62c61d9126 100644 --- a/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart +++ b/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart @@ -21,12 +21,14 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_body.dart'; import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_input.dart'; +import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_options_row.dart'; import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_options_text_field.dart'; import 'package:playground_components/playground_components.dart'; import 'package:playground_components_dev/playground_components_dev.dart'; import 'common/common.dart'; import 'common/common_finders.dart'; +import 'common/finder.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -74,11 +76,11 @@ void main() { Future _addTwoOptions(WidgetTester wt) async { await wt.enterText( - _getPipelineOptionsTextField(wt, _Placement.topLeft), + find.byType(PipelineOptionsTextField).horizontallyAt(0, wt), 'some', ); await wt.enterText( - _getPipelineOptionsTextField(wt, _Placement.topRight), + find.byType(PipelineOptionsTextField).horizontallyAt(1, wt), 'test', ); @@ -86,52 +88,24 @@ Future _addTwoOptions(WidgetTester wt) async { await wt.pumpAndSettle(); await wt.enterText( - _getPipelineOptionsTextField(wt, _Placement.bottomLeft), + find + .byType(PipelineOptionsRow) + .verticallyAt(1, wt) + .getChildrenByType(PipelineOptionsTextField) + .horizontallyAt(0, wt), 'some2', ); + await wt.enterText( - _getPipelineOptionsTextField(wt, _Placement.bottomRight), + find + .byType(PipelineOptionsRow) + .verticallyAt(1, wt) + .getChildrenByType(PipelineOptionsTextField) + .horizontallyAt(1, wt), 'test2', ); } -Finder _getPipelineOptionsTextField(WidgetTester wt, _Placement placement) { - final fields = find.byType(PipelineOptionsTextField); - final positions = <_Point>[]; - for (var i = 0; i < fields.evaluate().length; i++) { - final element = fields.at(i); - final position = wt.getCenter(element); - positions.add(_Point(position.dx, position.dy)); - } - - late _Point Function(_Point, _Point) reduceFunc; - switch (placement) { - case _Placement.topLeft: - reduceFunc = (a, b) => a.x <= b.x && a.y <= b.y ? a : b; - break; - case _Placement.topRight: - reduceFunc = (a, b) => a.x >= b.x && a.y <= b.y ? a : b; - break; - case _Placement.bottomLeft: - reduceFunc = (a, b) => a.x <= b.x && a.y >= b.y ? a : b; - break; - case _Placement.bottomRight: - reduceFunc = (a, b) => a.x >= b.x && a.y >= b.y ? a : b; - break; - } - final position = positions.reduce(reduceFunc); - return fields.at(positions.indexOf(position)); -} - -enum _Placement { topLeft, topRight, bottomLeft, bottomRight } - -class _Point { - final double x; - final double y; - - _Point(this.x, this.y); -} - Finder _getBottomDeleteIcon(WidgetTester wt) { Finder deleteIcons = find.byIcon(Icons.delete_outlined); diff --git a/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_form.dart b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_form.dart index 6aa41c0c3cdf..61b8cdef5845 100644 --- a/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_form.dart +++ b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_form.dart @@ -19,14 +19,12 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:playground/constants/colors.dart'; import 'package:playground/constants/sizes.dart'; import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_option_controller.dart'; import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_option_label.dart'; -import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_options_text_field.dart'; +import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_options_row.dart'; const kSpace = SizedBox(width: kMdSpacing); -const kTextFieldHeight = 50.0; class PipelineOptionsForm extends StatelessWidget { final List options; @@ -52,41 +50,11 @@ class PipelineOptionsForm extends StatelessWidget { ], ), ...options.mapIndexed( - (index, controller) => Row( - children: [ - Expanded( - child: SizedBox( - height: kTextFieldHeight, - child: PipelineOptionsTextField( - controller: controller.nameController, - ), - ), - ), - kSpace, - Expanded( - child: SizedBox( - height: kTextFieldHeight, - child: PipelineOptionsTextField( - controller: controller.valueController, - ), - ), - ), - SizedBox( - width: kIconSizeLg, - child: IconButton( - iconSize: kIconSizeMd, - splashRadius: kIconButtonSplashRadius, - icon: const Icon( - Icons.delete_outlined, - color: kLightPrimary, - ), - color: Theme.of(context).dividerColor, - onPressed: () => onDelete(index), - ), - ), - ], + (index, controller) => PipelineOptionsRow( + controller: controller, + onDelete: () => onDelete(index), ), - ) + ), ], ); } diff --git a/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_row.dart b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_row.dart new file mode 100644 index 000000000000..126f9c9170f3 --- /dev/null +++ b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_row.dart @@ -0,0 +1,75 @@ +/* + * 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/editor/components/pipeline_options_dropdown/pipeline_option_controller.dart'; +import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_options_form.dart'; +import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_options_text_field.dart'; + +import '../../../../constants/colors.dart'; +import '../../../../constants/sizes.dart'; + +const kTextFieldHeight = 50.0; + +class PipelineOptionsRow extends StatelessWidget { + final void Function() onDelete; + final PipelineOptionController controller; + + const PipelineOptionsRow({ + required this.controller, + required this.onDelete, + }); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: SizedBox( + height: kTextFieldHeight, + child: PipelineOptionsTextField( + controller: controller.nameController, + ), + ), + ), + kSpace, + Expanded( + child: SizedBox( + height: kTextFieldHeight, + child: PipelineOptionsTextField( + controller: controller.valueController, + ), + ), + ), + SizedBox( + width: kIconSizeLg, + child: IconButton( + iconSize: kIconSizeMd, + splashRadius: kIconButtonSplashRadius, + icon: const Icon( + Icons.delete_outlined, + color: kLightPrimary, + ), + color: Theme.of(context).dividerColor, + onPressed: () => onDelete(), + ), + ), + ], + ); + } +} From c8deff816d90130cdba6e1418f13cb2c7bda6424 Mon Sep 17 00:00:00 2001 From: malarg Date: Thu, 26 Jan 2023 12:07:21 +0400 Subject: [PATCH 17/52] integration test minor fixes --- .../frontend/assets/translations/en.yaml | 2 +- .../common/common_finders.dart | 8 +-- .../integration_test/common/finder.dart | 30 ++++------ ...tandalone_cancel_running_example_test.dart | 19 +------ ..._change_pipeline_options_and_run_test.dart | 24 ++++---- .../standalone_editing_test.dart | 26 +++------ .../standalone_example_selector_test.dart | 56 +++++++------------ .../controllers/playground_controller.dart | 10 ++-- .../lib/src/models/intents.dart | 6 +- .../examples_loader_test.mocks.dart | 6 +- .../lib/src/widget_tester.dart | 14 +++++ 11 files changed, 83 insertions(+), 118 deletions(-) diff --git a/playground/frontend/assets/translations/en.yaml b/playground/frontend/assets/translations/en.yaml index 6e6c95bae797..9a77bc34174c 100644 --- a/playground/frontend/assets/translations/en.yaml +++ b/playground/frontend/assets/translations/en.yaml @@ -19,5 +19,5 @@ intents: playground: clearOutput: 'Clear Output' newExample: 'New Example' - showAutocompleter: 'Show Autocompleter' + showSuggestions: 'Show Suggestions' usesEmulatedData: 'This examples uses emulated data' diff --git a/playground/frontend/integration_test/common/common_finders.dart b/playground/frontend/integration_test/common/common_finders.dart index 816f90099e1e..9b470f4b2ad0 100644 --- a/playground/frontend/integration_test/common/common_finders.dart +++ b/playground/frontend/integration_test/common/common_finders.dart @@ -36,10 +36,6 @@ import 'package:playground_components/src/widgets/drag_handle.dart'; import 'package:playground_components_dev/playground_components_dev.dart'; extension CommonFindersExtension on CommonFinders { - Finder appDropdownButtonWithText(String text) { - return find.byType(PipelineOptionsDropdown); - } - Finder codeTextAreaWrapper() { return byType(CodeTextAreaWrapper); } @@ -92,6 +88,10 @@ extension CommonFindersExtension on CommonFinders { return byType(MoreActions); } + Finder pipelineOptions() { + return find.byType(PipelineOptionsDropdown); + } + Finder pipelineOptionsListTab() { return find.byKey(PipelineOptionsDropdownBody.optionsTabKey); } diff --git a/playground/frontend/integration_test/common/finder.dart b/playground/frontend/integration_test/common/finder.dart index de1d84b224e8..97c11cc4062f 100644 --- a/playground/frontend/integration_test/common/finder.dart +++ b/playground/frontend/integration_test/common/finder.dart @@ -5,16 +5,23 @@ extension FinderExtension on Finder { Finder getChildrenByType(Type childType) { final finders = evaluate(); final childElements = finders - .map((f) => collectAllElementsFrom(f, skipOffstage: true)) + .map((e) => collectAllElementsFrom(e, skipOffstage: true)) .expand((e) => e) .where((e) => e.widget.runtimeType == childType); + //todo: may be there are a way to create finder more efficiently return find.byElementPredicate( (element) => childElements.contains(element), ); } - Finder alignedIndexAt(int index, Axis axis, WidgetTester wt) { + Finder horizontallyAt(int index, WidgetTester wt) => + _alignedIndexAt(index, Axis.horizontal, wt); + + Finder verticallyAt(int index, WidgetTester wt) => + _alignedIndexAt(index, Axis.vertical, wt); + + Finder _alignedIndexAt(int index, Axis axis, WidgetTester wt) { final finders = evaluate(); if (index > finders.length - 1) { @@ -33,18 +40,7 @@ extension FinderExtension on Finder { : _compareDoubles(a.offset.dx, b.offset.dx), ); - final result = find.byElementPredicate((element) { - return axis == Axis.vertical - ? finders.contains(element) && - wt.getCenter(at(finders.toList().indexOf(element))).dy == - offsets[index].offset.dy - : finders.contains(element) && - wt.getCenter(at(finders.toList().indexOf(element))).dx == - offsets[index].offset.dx; - }); - - print(result.evaluate().length); - return result; + return at(offsets[index].index); } int _compareDoubles(double a, double b) { @@ -56,12 +52,6 @@ extension FinderExtension on Finder { return -1; } } - - Finder horizontallyAt(int index, WidgetTester wt) => - alignedIndexAt(index, Axis.horizontal, wt); - - Finder verticallyAt(int index, WidgetTester wt) => - alignedIndexAt(index, Axis.vertical, wt); } class _IndexAndOffset { diff --git a/playground/frontend/integration_test/standalone_cancel_running_example_test.dart b/playground/frontend/integration_test/standalone_cancel_running_example_test.dart index 9b26c8bef87b..b53e20d1de00 100644 --- a/playground/frontend/integration_test/standalone_cancel_running_example_test.dart +++ b/playground/frontend/integration_test/standalone_cancel_running_example_test.dart @@ -32,13 +32,7 @@ void main() { // Cancel unchanged example. await _runAndCancelExample(wt, const Duration(milliseconds: 300)); - final source = wt - .findPlaygroundController() - .snippetEditingController - ?.activeFileController - ?.codeController - .fullText ?? - ''; + final source = wt.findPlaygroundController().source ?? ''; await wt.enterText(find.codeField(), '//comment\n' + source); await wt.pumpAndSettle(); @@ -49,15 +43,8 @@ void main() { Future _runAndCancelExample(WidgetTester wt, Duration duration) async { await wt.tap(find.runOrCancelButton()); - try { - await wt.pumpAndSettle( - const Duration(milliseconds: 100), - EnginePhase.sendSemanticsUpdate, - duration, - ); - } catch (e) { - //ignore - } + + await wt.wait(duration); await wt.tapAndSettle(find.runOrCancelButton()); final playgroundController = wt.findPlaygroundController(); diff --git a/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart b/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart index 2e62c61d9126..bc369e0eda25 100644 --- a/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart +++ b/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart @@ -38,27 +38,28 @@ void main() { (WidgetTester wt) async { await init(wt); - await wt.tapAndSettle(find.appDropdownButtonWithText('Pipeline Options')); + await wt.tapAndSettle(find.pipelineOptions()); await _addTwoOptions(wt); await wt.tapAndSettle(find.pipelineOptionsRawTab()); - _checkIfRawTextCorrect('--some test --some2 test2'); + _expectRawText('--some test --some2 test2'); await wt.tapAndSettle(find.pipelineOptionsListTab()); - await wt.tap(_getBottomDeleteIcon(wt)); + await wt.tap(_getDeleteIcon(1, wt)); await wt.tapAndSettle(find.pipelineOptionsRawTab()); - _checkIfRawTextCorrect('--some test'); + _expectRawText('--some test'); await wt.tapAndSettle(find.pipelineOptionsSaveAndCloseButton()); await wt.tap(find.runOrCancelButton()); await Future.delayed(const Duration(milliseconds: 300)); + // Cancel execution just for test speed. await wt.tapAndSettle(find.runOrCancelButton()); final playgroundController = wt.findPlaygroundController(); @@ -106,17 +107,14 @@ Future _addTwoOptions(WidgetTester wt) async { ); } -Finder _getBottomDeleteIcon(WidgetTester wt) { - Finder deleteIcons = find.byIcon(Icons.delete_outlined); - - Finder bottomIcon = - wt.getCenter(deleteIcons.at(0)).dy > wt.getCenter(deleteIcons.at(1)).dy - ? deleteIcons.at(0) - : deleteIcons.at(1); - return bottomIcon; +Finder _getDeleteIcon(int index, WidgetTester wt) { + return find.descendant( + of: find.byType(PipelineOptionsRow).verticallyAt(index, wt), + matching: find.byIcon(Icons.delete_outlined), + ); } -void _checkIfRawTextCorrect(String text) { +void _expectRawText(String text) { expect( find.descendant( of: find.byKey(PipelineOptionsDropdownInput.textFieldKey), diff --git a/playground/frontend/integration_test/standalone_editing_test.dart b/playground/frontend/integration_test/standalone_editing_test.dart index eb6cd1c29511..40506c68a98d 100644 --- a/playground/frontend/integration_test/standalone_editing_test.dart +++ b/playground/frontend/integration_test/standalone_editing_test.dart @@ -23,13 +23,14 @@ import 'package:playground_components_dev/playground_components_dev.dart'; import 'common/common.dart'; import 'common/common_finders.dart'; +import 'common/finder.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('Testing editing code', (WidgetTester wt) async { await init(wt); - await _checkResetDefaultCode(wt); + await _checkResetUnchangedCode(wt); await _checkResetEditedCode(wt); await _checkAutocomplete(wt); await _checkCodeHighlightedMultipleColors(wt); @@ -38,11 +39,12 @@ void main() { } Future _checkAutocomplete(WidgetTester wt) async { + // Several newlines are required here because suggestion popup works incorrectly. Remove when fixed await wt.enterText(find.codeField(), '\n\n\n\n\nsdk'); final playgroundController = wt.findPlaygroundController(); await wt.runShortcut( - playgroundController.showAutocompleterShortcut.shortcuts.keys); + playgroundController.showSuggestionsShortcut.shortcuts.keys); await wt.pumpAndSettle(); expect(find.text('sdkHttpMetadata'), findsOneWidget); @@ -53,7 +55,7 @@ Future _checkAutocomplete(WidgetTester wt) async { await wt.tapAndSettle(find.resetButton()); } -Future _checkResetDefaultCode(WidgetTester wt) async { +Future _checkResetUnchangedCode(WidgetTester wt) async { final playgroundController = wt.findPlaygroundController(); final code = playgroundController.source; @@ -121,25 +123,15 @@ public class MyClass { expect(wt.findOneCodeController().text, foldedCode); - await wt.tapAndSettle(_getFoldToggles()); + await wt.tapAndSettle(_getTopToggle(wt)); expect(wt.findOneCodeController().text, code); } Finder _getTopToggle(WidgetTester wt) { - Finder foldToggles = _getFoldToggles(); - - Finder topToggle = - wt.getCenter(foldToggles.at(0)).dy < wt.getCenter(foldToggles.at(1)).dy - ? foldToggles.at(0) - : foldToggles.at(1); - return topToggle; -} - -Finder _getFoldToggles() { - Finder foldToggles = find.descendant( + return find.descendant( of: find.byType(RotatedBox), matching: find.byIcon(Icons.chevron_right), - ); - return foldToggles; + ).verticallyAt(0, wt); } + diff --git a/playground/frontend/integration_test/standalone_example_selector_test.dart b/playground/frontend/integration_test/standalone_example_selector_test.dart index dca2d0b8c882..0698b5a691b9 100644 --- a/playground/frontend/integration_test/standalone_example_selector_test.dart +++ b/playground/frontend/integration_test/standalone_example_selector_test.dart @@ -45,12 +45,7 @@ void main() { Future _checkFilteringExamplesByTypes(WidgetTester wt) async { await wt.tapAndSettle(find.exampleSelector()); - final allExamplesCount = _getExamplesCount(wt); await wt.tapAndSettle(find.widgetWithText(TypeBubble, ExampleType.test.name)); - - final filteredExamplesCount = _getExamplesCount(wt); - - expect(allExamplesCount, isNot(filteredExamplesCount)); final categoriesWithExamples = _getCategoriesWithExamples(wt); for (final example in categoriesWithExamples.expand((e) => e.examples)) { @@ -63,44 +58,39 @@ Future _checkFilteringExamplesByTypes(WidgetTester wt) async { Future _checkFilteringExamplesByTags(WidgetTester wt) async { await wt.tapAndSettle(find.exampleSelector()); - final allExamplesCount = _getExamplesCount(wt); final sortedTags = _getSortedTags(wt); + await wt.tapAndSettle(find.widgetWithText(TagBubble, sortedTags[0])); - final filteredExamplesCount = _getExamplesCount(wt); expect(_areCategoriesContainsTag(wt, [sortedTags[0]]), isTrue); - expect(allExamplesCount, isNot(filteredExamplesCount)); await wt.tapAndSettle(find.widgetWithText(TagBubble, sortedTags[1])); - final nextFilteredExamplesCount = _getExamplesCount(wt); expect(_areCategoriesContainsTag(wt, [sortedTags[0], sortedTags[1]]), isTrue); - expect(filteredExamplesCount, isNot(nextFilteredExamplesCount)); await wt.tapAndSettle(find.exampleSelector()); } List _getSortedTags(WidgetTester wt) { final categoriesWithExamples = _getCategoriesWithExamples(wt); - final tags = categoriesWithExamples - .expand((e) => e.examples) - .expand((e) => e.tags); + final tags = + categoriesWithExamples.expand((e) => e.examples).expand((e) => e.tags); + final tagsMap = {}; for (final tag in tags) { - tagsMap[tag] = tagsMap[tag] == null ? 1 : tagsMap[tag]! + 1; + tagsMap[tag] = (tagsMap[tag] ?? 0) + 1; } - final tagsMapList = tagsMap.entries.toList()..sort((a, b) => b.value.compareTo(a.value)); + + final tagsMapList = tagsMap.entries.toList() + ..sort((a, b) => b.value.compareTo(a.value)); + return tagsMapList.map((e) => e.key).toList(); } bool _areCategoriesContainsTag(WidgetTester wt, List tags) { final categoriesWithExamples = _getCategoriesWithExamples(wt); final examples = categoriesWithExamples.expand((e) => e.examples); - - if (examples.isEmpty) { - return true; - } - + for (final example in examples) { for (final tag in tags) { if (!example.tags.contains(tag)) { @@ -111,18 +101,6 @@ bool _areCategoriesContainsTag(WidgetTester wt, List tags) { return true; } -int _getExamplesCount(WidgetTester wt) { - final categories = _getCategoriesWithExamples(wt); - - if (categories.isEmpty) { - return 0; - } - - return categories - .map((e) => e.examples.length) - .reduce((value, element) => value + element); -} - List _getCategoriesWithExamples(WidgetTester wt) { return wt .element(find.byType(ExamplesDropdownContent)) @@ -136,10 +114,6 @@ Future _checkFilteringExamplesBySearchString(WidgetTester wt) async { await wt.enterText(find.byType(SearchField), 'te'); - final filteredExamplesCount = _getExamplesCount(wt); - - expect(allExamplesCount != filteredExamplesCount, true); - final categories = _getCategoriesWithExamples(wt); for (var category in categories) { for (var example in category.examples) { @@ -154,6 +128,16 @@ Future _checkFilteringExamplesBySearchString(WidgetTester wt) async { await wt.tapAndSettle(find.exampleSelector()); } +int _getExamplesCount(WidgetTester wt) { + final categories = _getCategoriesWithExamples(wt); + + if (categories.isEmpty) { + return 0; + } + + return categories.map((e) => e.examples.length).reduce((a, b) => a + b); +} + Future _checkViewDescription(WidgetTester wt) async { await wt.tapAndSettle(find.exampleSelector()); diff --git a/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart index b24b738de233..2705430d399e 100644 --- a/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart +++ b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart @@ -256,7 +256,7 @@ class PlaygroundController with ChangeNotifier { notifyListeners(); } - void showAutocompleter() { + void showSuggestions() { snippetEditingController?.activeFileController?.codeController .generateSuggestions(); notifyListeners(); @@ -337,22 +337,22 @@ class PlaygroundController with ChangeNotifier { ), ); - late BeamShortcut showAutocompleterShortcut = BeamShortcut( + late BeamShortcut showSuggestionsShortcut = BeamShortcut( shortcuts: LogicalKeySet( LogicalKeyboardKeyExtension.metaOrControl, LogicalKeyboardKey.shift, LogicalKeyboardKey.keyS, ), - actionIntent: const ShowAutocompleterIntent(), + actionIntent: const ShowSuggestionsIntent(), createAction: (BuildContext context) => CallbackAction( - onInvoke: (_) => showAutocompleter(), + onInvoke: (_) => showSuggestions(), ), ); List get shortcuts => [ runShortcut, resetShortcut, - showAutocompleterShortcut, + showSuggestionsShortcut, ]; @override diff --git a/playground/frontend/playground_components/lib/src/models/intents.dart b/playground/frontend/playground_components/lib/src/models/intents.dart index 27e54bc5fd97..801d96bd9fd1 100644 --- a/playground/frontend/playground_components/lib/src/models/intents.dart +++ b/playground/frontend/playground_components/lib/src/models/intents.dart @@ -34,7 +34,7 @@ class RunIntent extends BeamIntent { const RunIntent() : super(slug: 'intents.playground.run'); } -class ShowAutocompleterIntent extends BeamIntent { - const ShowAutocompleterIntent() - : super(slug: 'intents.playground.showAutocompleter'); +class ShowSuggestionsIntent extends BeamIntent { + const ShowSuggestionsIntent() + : super(slug: 'intents.playground.showSuggestions'); } diff --git a/playground/frontend/playground_components/test/src/controllers/example_loaders/examples_loader_test.mocks.dart b/playground/frontend/playground_components/test/src/controllers/example_loaders/examples_loader_test.mocks.dart index 22f9817ab0ae..daf009d7d322 100644 --- a/playground/frontend/playground_components/test/src/controllers/example_loaders/examples_loader_test.mocks.dart +++ b/playground/frontend/playground_components/test/src/controllers/example_loaders/examples_loader_test.mocks.dart @@ -131,12 +131,12 @@ class MockPlaygroundController extends _i1.Mock returnValueForMissingStub: null, ); @override - _i6.BeamShortcut get showAutocompleterShortcut => (super.noSuchMethod( + _i6.BeamShortcut get showSuggestionsShortcut => (super.noSuchMethod( Invocation.getter(#showAutocompleterShortcut), returnValue: _FakeBeamShortcut_4(), ) as _i6.BeamShortcut); @override - set showAutocompleterShortcut(_i6.BeamShortcut? _showAutocompleterShortcut) => + set showSuggestionsShortcut(_i6.BeamShortcut? _showAutocompleterShortcut) => super.noSuchMethod( Invocation.setter( #showAutocompleterShortcut, @@ -244,7 +244,7 @@ class MockPlaygroundController extends _i1.Mock returnValueForMissingStub: Future.value(), ) as _i14.Future); @override - void showAutocompleter() => super.noSuchMethod( + void showSuggestions() => super.noSuchMethod( Invocation.method( #showAutocompleter, [], diff --git a/playground/frontend/playground_components_dev/lib/src/widget_tester.dart b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart index 967f4fae845b..0539969de299 100644 --- a/playground/frontend/playground_components_dev/lib/src/widget_tester.dart +++ b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart @@ -65,4 +65,18 @@ extension WidgetTesterExtension on WidgetTester { await tap(finder); await pumpAndSettle(); } + + /// Waits without blocking the main isolate (Animations are able to play). + Future wait(Duration duration) async { + try { + await pumpAndSettle( + const Duration(milliseconds: 100), + EnginePhase.sendSemanticsUpdate, + duration, + ); + // ignore: avoid_catches_without_on_clauses + } catch (e) { + //ignore + } + } } From e3fcab5d0b023846002ebf5272cdcdda3db07919 Mon Sep 17 00:00:00 2001 From: malarg Date: Thu, 26 Jan 2023 12:22:04 +0400 Subject: [PATCH 18/52] minor fixes --- .../integration_test/common/finder.dart | 18 ++++++++++++++++++ ...e_change_pipeline_options_and_run_test.dart | 4 ++-- .../standalone_editing_test.dart | 14 ++++++++------ .../src/controllers/playground_controller.dart | 1 + .../lib/src/repositories/code_repository.dart | 4 ++-- .../lib/src/util/logical_keyboard_key.dart | 2 +- 6 files changed, 32 insertions(+), 11 deletions(-) diff --git a/playground/frontend/integration_test/common/finder.dart b/playground/frontend/integration_test/common/finder.dart index 97c11cc4062f..51c612fb04ce 100644 --- a/playground/frontend/integration_test/common/finder.dart +++ b/playground/frontend/integration_test/common/finder.dart @@ -1,3 +1,21 @@ +/* + * 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/painting.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart b/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart index bc369e0eda25..1dfb243b7b96 100644 --- a/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart +++ b/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart @@ -48,7 +48,7 @@ void main() { await wt.tapAndSettle(find.pipelineOptionsListTab()); - await wt.tap(_getDeleteIcon(1, wt)); + await wt.tap(_getDeleteIconByIndex(1, wt)); await wt.tapAndSettle(find.pipelineOptionsRawTab()); @@ -107,7 +107,7 @@ Future _addTwoOptions(WidgetTester wt) async { ); } -Finder _getDeleteIcon(int index, WidgetTester wt) { +Finder _getDeleteIconByIndex(int index, WidgetTester wt) { return find.descendant( of: find.byType(PipelineOptionsRow).verticallyAt(index, wt), matching: find.byIcon(Icons.delete_outlined), diff --git a/playground/frontend/integration_test/standalone_editing_test.dart b/playground/frontend/integration_test/standalone_editing_test.dart index 40506c68a98d..8a762a927b3a 100644 --- a/playground/frontend/integration_test/standalone_editing_test.dart +++ b/playground/frontend/integration_test/standalone_editing_test.dart @@ -44,7 +44,8 @@ Future _checkAutocomplete(WidgetTester wt) async { final playgroundController = wt.findPlaygroundController(); await wt.runShortcut( - playgroundController.showSuggestionsShortcut.shortcuts.keys); + playgroundController.showSuggestionsShortcut.shortcuts.keys, + ); await wt.pumpAndSettle(); expect(find.text('sdkHttpMetadata'), findsOneWidget); @@ -129,9 +130,10 @@ public class MyClass { } Finder _getTopToggle(WidgetTester wt) { - return find.descendant( - of: find.byType(RotatedBox), - matching: find.byIcon(Icons.chevron_right), - ).verticallyAt(0, wt); + return find + .descendant( + of: find.byType(RotatedBox), + matching: find.byIcon(Icons.chevron_right), + ) + .verticallyAt(0, wt); } - diff --git a/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart index 2705430d399e..77129aac0e97 100644 --- a/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart +++ b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart @@ -253,6 +253,7 @@ class PlaygroundController with ChangeNotifier { Future reset() async { await codeRunner.cancelRun(); snippetEditingController?.reset(); + codeRunner.clearResult(); notifyListeners(); } diff --git a/playground/frontend/playground_components/lib/src/repositories/code_repository.dart b/playground/frontend/playground_components/lib/src/repositories/code_repository.dart index 8468bf12cf45..94872888f1c2 100644 --- a/playground/frontend/playground_components/lib/src/repositories/code_repository.dart +++ b/playground/frontend/playground_components/lib/src/repositories/code_repository.dart @@ -31,9 +31,9 @@ const kTimeoutErrorText = 'to try examples without timeout limitation.'; const kUnknownErrorText = 'Something went wrong. Please try again later or create a GitHub issue'; -const kProcessingStartedText = 'The processing has started\n'; +const kProcessingStartedText = 'The processing has been started\n'; const kProcessingStartedOptionsText = - 'The processing has started with pipeline options: '; + 'The processing has been started with the pipeline options: '; // TODO(alexeyinkin): Rename. This is not a repository but a higher level client. class CodeRepository { diff --git a/playground/frontend/playground_components/lib/src/util/logical_keyboard_key.dart b/playground/frontend/playground_components/lib/src/util/logical_keyboard_key.dart index 2602bc07dad1..9ebbc099a4e0 100644 --- a/playground/frontend/playground_components/lib/src/util/logical_keyboard_key.dart +++ b/playground/frontend/playground_components/lib/src/util/logical_keyboard_key.dart @@ -20,7 +20,7 @@ import 'package:flutter/services.dart'; import 'native_platform.dart'; -extension LogicalKeyboardKeyExtension on LogicalKeyboardKey { +class LogicalKeyboardKeyExtension { static LogicalKeyboardKey get metaOrControl => NativePlatform.isMacOs ? LogicalKeyboardKey.meta : LogicalKeyboardKey.control; From 77f372f33f3a3d59f34047944809138937f2f077 Mon Sep 17 00:00:00 2001 From: malarg Date: Thu, 26 Jan 2023 12:28:42 +0400 Subject: [PATCH 19/52] removed comment --- playground/frontend/integration_test/common/finder.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/playground/frontend/integration_test/common/finder.dart b/playground/frontend/integration_test/common/finder.dart index 51c612fb04ce..155493ab9da2 100644 --- a/playground/frontend/integration_test/common/finder.dart +++ b/playground/frontend/integration_test/common/finder.dart @@ -27,7 +27,6 @@ extension FinderExtension on Finder { .expand((e) => e) .where((e) => e.widget.runtimeType == childType); - //todo: may be there are a way to create finder more efficiently return find.byElementPredicate( (element) => childElements.contains(element), ); From 52ad461a1d02115c64614a4893686cdab58d4285 Mon Sep 17 00:00:00 2001 From: malarg Date: Fri, 27 Jan 2023 14:02:18 +0400 Subject: [PATCH 20/52] minor fixes --- .../frontend/integration_test/standalone_editing_test.dart | 3 ++- .../integration_test/standalone_example_selector_test.dart | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/playground/frontend/integration_test/standalone_editing_test.dart b/playground/frontend/integration_test/standalone_editing_test.dart index 8a762a927b3a..220273b6fc03 100644 --- a/playground/frontend/integration_test/standalone_editing_test.dart +++ b/playground/frontend/integration_test/standalone_editing_test.dart @@ -39,7 +39,8 @@ void main() { } Future _checkAutocomplete(WidgetTester wt) async { - // Several newlines are required here because suggestion popup works incorrectly. Remove when fixed + // Several newlines are required here because suggestion + // popup works incorrectly. Remove when fixed await wt.enterText(find.codeField(), '\n\n\n\n\nsdk'); final playgroundController = wt.findPlaygroundController(); diff --git a/playground/frontend/integration_test/standalone_example_selector_test.dart b/playground/frontend/integration_test/standalone_example_selector_test.dart index 0698b5a691b9..383ae263b335 100644 --- a/playground/frontend/integration_test/standalone_example_selector_test.dart +++ b/playground/frontend/integration_test/standalone_example_selector_test.dart @@ -81,8 +81,8 @@ List _getSortedTags(WidgetTester wt) { tagsMap[tag] = (tagsMap[tag] ?? 0) + 1; } - final tagsMapList = tagsMap.entries.toList() - ..sort((a, b) => b.value.compareTo(a.value)); + final tagsMapList = tagsMap.entries.toList(); + tagsMapList.sort((a, b) => b.value.compareTo(a.value)); return tagsMapList.map((e) => e.key).toList(); } From b047239cf70280fd682d04de23dd12f559dc0f04 Mon Sep 17 00:00:00 2001 From: malarg Date: Mon, 30 Jan 2023 16:24:29 +0400 Subject: [PATCH 21/52] playground integration tests minor fixes --- .../integration_test/common/common_finders.dart | 2 +- .../frontend/integration_test/common/finder.dart | 14 ++++---------- ...alone_change_pipeline_options_and_run_test.dart | 2 +- .../lib/src/controllers/code_runner.dart | 3 ++- .../lib/src/widget_tester.dart | 12 +++--------- 5 files changed, 11 insertions(+), 22 deletions(-) diff --git a/playground/frontend/integration_test/common/common_finders.dart b/playground/frontend/integration_test/common/common_finders.dart index 9b470f4b2ad0..d0863dbfcbb7 100644 --- a/playground/frontend/integration_test/common/common_finders.dart +++ b/playground/frontend/integration_test/common/common_finders.dart @@ -88,7 +88,7 @@ extension CommonFindersExtension on CommonFinders { return byType(MoreActions); } - Finder pipelineOptions() { + Finder pipelineOptionsDropdown() { return find.byType(PipelineOptionsDropdown); } diff --git a/playground/frontend/integration_test/common/finder.dart b/playground/frontend/integration_test/common/finder.dart index 155493ab9da2..a9037f715eae 100644 --- a/playground/frontend/integration_test/common/finder.dart +++ b/playground/frontend/integration_test/common/finder.dart @@ -33,12 +33,12 @@ extension FinderExtension on Finder { } Finder horizontallyAt(int index, WidgetTester wt) => - _alignedIndexAt(index, Axis.horizontal, wt); + _atIndexOnAxis(index, Axis.horizontal, wt); Finder verticallyAt(int index, WidgetTester wt) => - _alignedIndexAt(index, Axis.vertical, wt); + _atIndexOnAxis(index, Axis.vertical, wt); - Finder _alignedIndexAt(int index, Axis axis, WidgetTester wt) { + Finder _atIndexOnAxis(int index, Axis axis, WidgetTester wt) { final finders = evaluate(); if (index > finders.length - 1) { @@ -61,13 +61,7 @@ extension FinderExtension on Finder { } int _compareDoubles(double a, double b) { - if (a == b) { - return 0; - } else if (a > b) { - return 1; - } else { - return -1; - } + return (a - b).sign.toInt(); } } diff --git a/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart b/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart index 1dfb243b7b96..940bf1d9375c 100644 --- a/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart +++ b/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart @@ -38,7 +38,7 @@ void main() { (WidgetTester wt) async { await init(wt); - await wt.tapAndSettle(find.pipelineOptions()); + await wt.tapAndSettle(find.pipelineOptionsDropdown()); await _addTwoOptions(wt); diff --git a/playground/frontend/playground_components/lib/src/controllers/code_runner.dart b/playground/frontend/playground_components/lib/src/controllers/code_runner.dart index 48af3aa299ba..a2793bdb232e 100644 --- a/playground/frontend/playground_components/lib/src/controllers/code_runner.dart +++ b/playground/frontend/playground_components/lib/src/controllers/code_runner.dart @@ -123,7 +123,8 @@ class CodeRunner extends ChangeNotifier { Future cancelRun() async { snippetEditingController = null; - await _runSubscription?.cancel(); + //TODO: https://github.com/apache/beam/issues/25213 + unawaited(_runSubscription?.cancel()); final pipelineUuid = _result?.pipelineUuid ?? ''; if (pipelineUuid.isNotEmpty) { diff --git a/playground/frontend/playground_components_dev/lib/src/widget_tester.dart b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart index 0539969de299..92f61c80dce4 100644 --- a/playground/frontend/playground_components_dev/lib/src/widget_tester.dart +++ b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart @@ -68,15 +68,9 @@ extension WidgetTesterExtension on WidgetTester { /// Waits without blocking the main isolate (Animations are able to play). Future wait(Duration duration) async { - try { - await pumpAndSettle( - const Duration(milliseconds: 100), - EnginePhase.sendSemanticsUpdate, - duration, - ); - // ignore: avoid_catches_without_on_clauses - } catch (e) { - //ignore + final DateTime endTime = binding.clock.fromNowBy(duration); + while (binding.clock.now().isBefore(endTime)) { + await binding.pump(); } } } From 5514e5931d64e0a0aac0f1cb2d74865628060624 Mon Sep 17 00:00:00 2001 From: malarg Date: Tue, 31 Jan 2023 14:48:37 +0400 Subject: [PATCH 22/52] integration test pumpAnSettleNoException --- ...tandalone_cancel_running_example_test.dart | 2 +- .../lib/src/widget_tester.dart | 23 ++++++++++++++----- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/playground/frontend/integration_test/standalone_cancel_running_example_test.dart b/playground/frontend/integration_test/standalone_cancel_running_example_test.dart index b53e20d1de00..3d429b1b77fa 100644 --- a/playground/frontend/integration_test/standalone_cancel_running_example_test.dart +++ b/playground/frontend/integration_test/standalone_cancel_running_example_test.dart @@ -44,7 +44,7 @@ void main() { Future _runAndCancelExample(WidgetTester wt, Duration duration) async { await wt.tap(find.runOrCancelButton()); - await wt.wait(duration); + await wt.pumpAndSettleNoException(timeout: duration); await wt.tapAndSettle(find.runOrCancelButton()); final playgroundController = wt.findPlaygroundController(); diff --git a/playground/frontend/playground_components_dev/lib/src/widget_tester.dart b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart index 92f61c80dce4..a11203da457a 100644 --- a/playground/frontend/playground_components_dev/lib/src/widget_tester.dart +++ b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart @@ -66,11 +66,22 @@ extension WidgetTesterExtension on WidgetTester { await pumpAndSettle(); } - /// Waits without blocking the main isolate (Animations are able to play). - Future wait(Duration duration) async { - final DateTime endTime = binding.clock.fromNowBy(duration); - while (binding.clock.now().isBefore(endTime)) { - await binding.pump(); - } + Future pumpAndSettleNoException({ + Duration duration = const Duration(milliseconds: 100), + EnginePhase phase = EnginePhase.sendSemanticsUpdate, + Duration timeout = const Duration(minutes: 10), + }) async { + return TestAsyncUtils.guard(() async { + final DateTime endTime = binding.clock.fromNowBy(timeout); + int count = 0; + do { + if (binding.clock.now().isAfter(endTime)) { + return count; + } + await binding.pump(duration, phase); + count += 1; + } while (binding.hasScheduledFrame); + return count; + }); } } From 305e4cde9aec42610da7ec02d86c376d20c8b89a Mon Sep 17 00:00:00 2001 From: malarg Date: Tue, 31 Jan 2023 19:17:34 +0400 Subject: [PATCH 23/52] integration test shortcut refactor --- .../standalone_editing_test.dart | 13 ++++++++----- .../standalone_run_shortcuts_test.dart | 18 ++++++++++++++---- .../lib/src/widget_tester.dart | 4 ++-- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/playground/frontend/integration_test/standalone_editing_test.dart b/playground/frontend/integration_test/standalone_editing_test.dart index 220273b6fc03..c0d1aa875ac3 100644 --- a/playground/frontend/integration_test/standalone_editing_test.dart +++ b/playground/frontend/integration_test/standalone_editing_test.dart @@ -17,8 +17,10 @@ */ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:playground_components_dev/playground_components_dev.dart'; import 'common/common.dart'; @@ -39,14 +41,15 @@ void main() { } Future _checkAutocomplete(WidgetTester wt) async { - // Several newlines are required here because suggestion + // Several newlines are required here because suggestion // popup works incorrectly. Remove when fixed await wt.enterText(find.codeField(), '\n\n\n\n\nsdk'); - final playgroundController = wt.findPlaygroundController(); - await wt.runShortcut( - playgroundController.showSuggestionsShortcut.shortcuts.keys, - ); + await wt.runShortcut([ + LogicalKeyboardKeyExtension.metaOrControl, + LogicalKeyboardKey.shift, + LogicalKeyboardKey.keyS, + ]); await wt.pumpAndSettle(); expect(find.text('sdkHttpMetadata'), findsOneWidget); diff --git a/playground/frontend/integration_test/standalone_run_shortcuts_test.dart b/playground/frontend/integration_test/standalone_run_shortcuts_test.dart index 8976291e1645..41006dd98132 100644 --- a/playground/frontend/integration_test/standalone_run_shortcuts_test.dart +++ b/playground/frontend/integration_test/standalone_run_shortcuts_test.dart @@ -16,9 +16,9 @@ * limitations under the License. */ +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; -import 'package:playground/modules/shortcuts/constants/global_shortcuts.dart'; import 'package:playground_components/playground_components.dart'; import 'package:playground_components_dev/playground_components_dev.dart'; @@ -50,7 +50,11 @@ Future _checkResetShortcut( expect(controller.source, isNot(startSource)); - await wt.runShortcut(controller.resetShortcut.shortcuts.keys); + await wt.runShortcut([ + LogicalKeyboardKeyExtension.metaOrControl, + LogicalKeyboardKey.shift, + LogicalKeyboardKey.keyE, + ]); await wt.pumpAndSettle(); expect(startSource, controller.source); @@ -61,7 +65,10 @@ Future _checkRunShortcut( PlaygroundController controller, ) async { final output = controller.codeRunner.resultLogOutput; - await wt.runShortcut(controller.runShortcut.shortcuts.keys); + await wt.runShortcut([ + LogicalKeyboardKeyExtension.metaOrControl, + LogicalKeyboardKey.enter, + ]); await wt.pumpAndSettle(); expect(output, isNot(controller.codeRunner.resultLogOutput)); @@ -74,7 +81,10 @@ Future _checkClearOutputShortcut( expect(controller.codeRunner.resultLogOutput, isNotEmpty); expect(controller.codeRunner.resultLogOutput, isNotNull); - await wt.runShortcut(kClearOutputShortcut.shortcuts.keys); + await wt.runShortcut([ + LogicalKeyboardKeyExtension.metaOrControl, + LogicalKeyboardKey.keyB, + ]); await wt.pumpAndSettle(); expect(controller.codeRunner.resultLogOutput, isEmpty); diff --git a/playground/frontend/playground_components_dev/lib/src/widget_tester.dart b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart index a11203da457a..72703d26bc1a 100644 --- a/playground/frontend/playground_components_dev/lib/src/widget_tester.dart +++ b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart @@ -52,11 +52,11 @@ extension WidgetTesterExtension on WidgetTester { return context.read(); } - Future runShortcut(Set shortcut) async { + Future runShortcut(List shortcut) async { for (final key in shortcut) { await sendKeyDownEvent(key); } - for (final key in shortcut) { + for (final key in shortcut.reversed) { await sendKeyUpEvent(key); } } From 01a065981f0b65b10d9be56a06d37e7b17da933c Mon Sep 17 00:00:00 2001 From: malarg Date: Tue, 31 Jan 2023 19:41:32 +0400 Subject: [PATCH 24/52] integration test another changing shortcuts running --- playground/frontend/build.gradle | 2 +- .../standalone_editing_test.dart | 9 ++++----- .../standalone_example_selector_test.dart | 6 +++--- .../standalone_run_shortcuts_test.dart | 18 ++++-------------- .../lib/src/widget_tester.dart | 7 ++++--- 5 files changed, 16 insertions(+), 26 deletions(-) diff --git a/playground/frontend/build.gradle b/playground/frontend/build.gradle index e837d8787fd5..2d4dc6cf7127 100644 --- a/playground/frontend/build.gradle +++ b/playground/frontend/build.gradle @@ -181,7 +181,7 @@ ext.deleteFilesByRegExp = { re -> } tasks.register("integrationTest") { - // dependsOn("integrationTest_standalone_change_example_sdk_run") - + dependsOn("integrationTest_standalone_change_example_sdk_run") dependsOn("integrationTest_standalone_cancel_running_example") dependsOn("integrationTest_standalone_change_pipeline_options_and_run") dependsOn("integrationTest_standalone_editing") diff --git a/playground/frontend/integration_test/standalone_editing_test.dart b/playground/frontend/integration_test/standalone_editing_test.dart index c0d1aa875ac3..dfe0ba76f5f9 100644 --- a/playground/frontend/integration_test/standalone_editing_test.dart +++ b/playground/frontend/integration_test/standalone_editing_test.dart @@ -45,11 +45,10 @@ Future _checkAutocomplete(WidgetTester wt) async { // popup works incorrectly. Remove when fixed await wt.enterText(find.codeField(), '\n\n\n\n\nsdk'); - await wt.runShortcut([ - LogicalKeyboardKeyExtension.metaOrControl, - LogicalKeyboardKey.shift, - LogicalKeyboardKey.keyS, - ]); + final playgroundController = wt.findPlaygroundController(); + await wt.runShortcut( + playgroundController.showSuggestionsShortcut.shortcuts, + ); await wt.pumpAndSettle(); expect(find.text('sdkHttpMetadata'), findsOneWidget); diff --git a/playground/frontend/integration_test/standalone_example_selector_test.dart b/playground/frontend/integration_test/standalone_example_selector_test.dart index 383ae263b335..6422e3779394 100644 --- a/playground/frontend/integration_test/standalone_example_selector_test.dart +++ b/playground/frontend/integration_test/standalone_example_selector_test.dart @@ -62,11 +62,11 @@ Future _checkFilteringExamplesByTags(WidgetTester wt) async { await wt.tapAndSettle(find.widgetWithText(TagBubble, sortedTags[0])); - expect(_areCategoriesContainsTag(wt, [sortedTags[0]]), isTrue); + expect(_doVisibleCategoriesHaveAllTags(wt, [sortedTags[0]]), isTrue); await wt.tapAndSettle(find.widgetWithText(TagBubble, sortedTags[1])); - expect(_areCategoriesContainsTag(wt, [sortedTags[0], sortedTags[1]]), isTrue); + expect(_doVisibleCategoriesHaveAllTags(wt, [sortedTags[0], sortedTags[1]]), isTrue,); await wt.tapAndSettle(find.exampleSelector()); } @@ -87,7 +87,7 @@ List _getSortedTags(WidgetTester wt) { return tagsMapList.map((e) => e.key).toList(); } -bool _areCategoriesContainsTag(WidgetTester wt, List tags) { +bool _doVisibleCategoriesHaveAllTags(WidgetTester wt, List tags) { final categoriesWithExamples = _getCategoriesWithExamples(wt); final examples = categoriesWithExamples.expand((e) => e.examples); diff --git a/playground/frontend/integration_test/standalone_run_shortcuts_test.dart b/playground/frontend/integration_test/standalone_run_shortcuts_test.dart index 41006dd98132..7e89525923ae 100644 --- a/playground/frontend/integration_test/standalone_run_shortcuts_test.dart +++ b/playground/frontend/integration_test/standalone_run_shortcuts_test.dart @@ -16,9 +16,9 @@ * limitations under the License. */ -import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; +import 'package:playground/modules/shortcuts/constants/global_shortcuts.dart'; import 'package:playground_components/playground_components.dart'; import 'package:playground_components_dev/playground_components_dev.dart'; @@ -50,11 +50,7 @@ Future _checkResetShortcut( expect(controller.source, isNot(startSource)); - await wt.runShortcut([ - LogicalKeyboardKeyExtension.metaOrControl, - LogicalKeyboardKey.shift, - LogicalKeyboardKey.keyE, - ]); + await wt.runShortcut(controller.resetShortcut.shortcuts); await wt.pumpAndSettle(); expect(startSource, controller.source); @@ -65,10 +61,7 @@ Future _checkRunShortcut( PlaygroundController controller, ) async { final output = controller.codeRunner.resultLogOutput; - await wt.runShortcut([ - LogicalKeyboardKeyExtension.metaOrControl, - LogicalKeyboardKey.enter, - ]); + await wt.runShortcut(controller.runShortcut.shortcuts); await wt.pumpAndSettle(); expect(output, isNot(controller.codeRunner.resultLogOutput)); @@ -81,10 +74,7 @@ Future _checkClearOutputShortcut( expect(controller.codeRunner.resultLogOutput, isNotEmpty); expect(controller.codeRunner.resultLogOutput, isNotNull); - await wt.runShortcut([ - LogicalKeyboardKeyExtension.metaOrControl, - LogicalKeyboardKey.keyB, - ]); + await wt.runShortcut(kClearOutputShortcut.shortcuts); await wt.pumpAndSettle(); expect(controller.codeRunner.resultLogOutput, isEmpty); diff --git a/playground/frontend/playground_components_dev/lib/src/widget_tester.dart b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart index 72703d26bc1a..bc178e8d2427 100644 --- a/playground/frontend/playground_components_dev/lib/src/widget_tester.dart +++ b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart @@ -52,11 +52,12 @@ extension WidgetTesterExtension on WidgetTester { return context.read(); } - Future runShortcut(List shortcut) async { - for (final key in shortcut) { + Future runShortcut(LogicalKeySet shortcut) async { + final list = shortcut.keys.toList(); + for (final key in list) { await sendKeyDownEvent(key); } - for (final key in shortcut.reversed) { + for (final key in list.reversed) { await sendKeyUpEvent(key); } } From 014906fc504eeff979e81ab7f01b03cd01bc6070 Mon Sep 17 00:00:00 2001 From: malarg Date: Mon, 6 Feb 2023 16:29:04 +0400 Subject: [PATCH 25/52] upgrade to flutter 3.7.1 --- .../integration_test/standalone_editing_test.dart | 8 +++----- .../integration_test/standalone_run_shortcuts_test.dart | 2 +- .../playground_components_dev/lib/src/widget_tester.dart | 7 ++++++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/playground/frontend/integration_test/standalone_editing_test.dart b/playground/frontend/integration_test/standalone_editing_test.dart index dfe0ba76f5f9..35bb35f2f32d 100644 --- a/playground/frontend/integration_test/standalone_editing_test.dart +++ b/playground/frontend/integration_test/standalone_editing_test.dart @@ -17,10 +17,8 @@ */ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; -import 'package:playground_components/playground_components.dart'; import 'package:playground_components_dev/playground_components_dev.dart'; import 'common/common.dart'; @@ -43,7 +41,7 @@ void main() { Future _checkAutocomplete(WidgetTester wt) async { // Several newlines are required here because suggestion // popup works incorrectly. Remove when fixed - await wt.enterText(find.codeField(), '\n\n\n\n\nsdk'); + await wt.enterCodeFieldText('\n\n\n\n\nsdk'); final playgroundController = wt.findPlaygroundController(); await wt.runShortcut( @@ -75,7 +73,7 @@ Future _checkResetEditedCode(WidgetTester wt) async { final playgroundController = wt.findPlaygroundController(); final code = playgroundController.source; - await wt.enterText(find.codeField(), 'print("Hello World!'); + await wt.enterCodeFieldText('print("Hello World!'); await wt.pumpAndSettle(); expect(playgroundController.source, isNot(code)); @@ -116,7 +114,7 @@ public class MyClass { } '''; - await wt.enterText(find.codeField(), code); + await wt.enterCodeFieldText(code); await wt.pumpAndSettle(); await wt.tapAndSettle(_getTopToggle(wt)); diff --git a/playground/frontend/integration_test/standalone_run_shortcuts_test.dart b/playground/frontend/integration_test/standalone_run_shortcuts_test.dart index 7e89525923ae..188cc0a2d2a2 100644 --- a/playground/frontend/integration_test/standalone_run_shortcuts_test.dart +++ b/playground/frontend/integration_test/standalone_run_shortcuts_test.dart @@ -46,7 +46,7 @@ Future _checkResetShortcut( PlaygroundController controller, ) async { final startSource = controller.source; - await wt.enterText(find.codeField(), 'print("Hello World!'); + await wt.enterCodeFieldText('print("Hello World!'); expect(controller.source, isNot(startSource)); diff --git a/playground/frontend/playground_components_dev/lib/src/widget_tester.dart b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart index bc178e8d2427..37dec0ee6ad1 100644 --- a/playground/frontend/playground_components_dev/lib/src/widget_tester.dart +++ b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart @@ -17,7 +17,6 @@ */ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_code_editor/flutter_code_editor.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:playground_components/playground_components.dart'; @@ -26,6 +25,12 @@ import 'package:provider/provider.dart'; import 'common_finders.dart'; extension WidgetTesterExtension on WidgetTester { + Future enterCodeFieldText(String text) async { + final codeField = widget(find.codeField()); + (codeField as CodeField).controller.fullText = text; + codeField.focusNode?.requestFocus(); + } + CodeController findOneCodeController() { final codeField = find.codeField(); expect(codeField, findsOneWidget); From e527f59b3e832c8d4d5a370da521ffa2cd234998 Mon Sep 17 00:00:00 2001 From: malarg Date: Mon, 6 Feb 2023 16:33:18 +0400 Subject: [PATCH 26/52] workaround comment --- .../playground_components_dev/lib/src/widget_tester.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/playground/frontend/playground_components_dev/lib/src/widget_tester.dart b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart index 37dec0ee6ad1..e816b8d90daa 100644 --- a/playground/frontend/playground_components_dev/lib/src/widget_tester.dart +++ b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart @@ -25,6 +25,7 @@ import 'package:provider/provider.dart'; import 'common_finders.dart'; extension WidgetTesterExtension on WidgetTester { + //workaround for https://github.com/flutter/flutter/issues/120060 Future enterCodeFieldText(String text) async { final codeField = widget(find.codeField()); (codeField as CodeField).controller.fullText = text; From 7f9adc0ce7361f1cadc70d0231847b8ed83052fb Mon Sep 17 00:00:00 2001 From: malarg Date: Tue, 7 Feb 2023 07:18:56 +0400 Subject: [PATCH 27/52] playground frontend updated major versions --- playground/frontend/playground_components/pubspec.yaml | 10 +++++----- playground/frontend/pubspec.yaml | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/playground/frontend/playground_components/pubspec.yaml b/playground/frontend/playground_components/pubspec.yaml index e7751981f1b9..44cfe152ac6f 100644 --- a/playground/frontend/playground_components/pubspec.yaml +++ b/playground/frontend/playground_components/pubspec.yaml @@ -26,7 +26,7 @@ environment: dependencies: aligned_dialog: ^0.0.6 - app_state: ^0.8.4 + app_state: ^0.9.1 collection: ^1.16.0 easy_localization: ^3.0.1 easy_localization_ext: ^0.1.1 @@ -36,10 +36,10 @@ dependencies: flutter: { sdk: flutter } flutter_code_editor: ^0.2.5 flutter_markdown: ^0.6.12 - flutter_svg: ^1.0.3 + flutter_svg: ^2.0.0+1 fluttertoast: ^8.1.1 get_it: ^7.2.0 - google_fonts: ^3.0.1 + google_fonts: ^4.0.3 grpc: ^3.0.2 highlight: ^0.7.0 http: ^0.13.5 @@ -56,10 +56,10 @@ dependencies: dev_dependencies: build_runner: ^2.2.0 enum_map_gen: ^0.2.0 - flutter_gen_runner: ^4.3.0 + flutter_gen_runner: ^5.1.0+1 flutter_test: { sdk: flutter } json_serializable: ^6.4.1 - mockito: 5.2.0 + mockito: 5.3.2 total_lints: ^2.17.4 flutter: diff --git a/playground/frontend/pubspec.yaml b/playground/frontend/pubspec.yaml index 3c65a5aab129..5933b009bc50 100644 --- a/playground/frontend/pubspec.yaml +++ b/playground/frontend/pubspec.yaml @@ -27,7 +27,7 @@ environment: dependencies: akvelon_flutter_issue_106664_workaround: ^0.1.2 aligned_dialog: ^0.0.6 - app_state: ^0.8.4 + app_state: ^0.9.1 collection: ^1.15.0 easy_localization: ^3.0.1 easy_localization_ext: ^0.1.1 @@ -36,10 +36,10 @@ dependencies: expansion_widget: ^0.0.2 flutter: { sdk: flutter } flutter_localizations: { sdk: flutter } - flutter_svg: ^1.0.3 + flutter_svg: ^2.0.0+1 flutter_web_plugins: { sdk: flutter } get_it: ^7.2.0 - google_fonts: ^3.0.1 + google_fonts: ^4.0.3 highlight: ^0.7.0 intl: ^0.17.0 onmessage: ^0.2.0 From d4fd93fcfc5a0180985078cd349598aa254d51cc Mon Sep 17 00:00:00 2001 From: malarg Date: Thu, 9 Feb 2023 09:45:34 +0400 Subject: [PATCH 28/52] issues 25329 25331 25336 --- .../lib/src/controllers/code_runner.dart | 35 ++++++++++++++++++- .../controllers/playground_controller.dart | 6 ++-- .../code_client/grpc_code_client.dart | 15 +++++--- .../lib/src/repositories/code_repository.dart | 4 ++- .../repositories/models/run_code_result.dart | 34 +++++++++--------- .../playground_components/pubspec.yaml | 1 + 6 files changed, 69 insertions(+), 26 deletions(-) diff --git a/playground/frontend/playground_components/lib/src/controllers/code_runner.dart b/playground/frontend/playground_components/lib/src/controllers/code_runner.dart index ad2df180d940..08b8c60cf869 100644 --- a/playground/frontend/playground_components/lib/src/controllers/code_runner.dart +++ b/playground/frontend/playground_components/lib/src/controllers/code_runner.dart @@ -18,6 +18,7 @@ import 'dart:async'; +import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/material.dart'; import '../../playground_components.dart'; @@ -62,6 +63,16 @@ class CodeRunner extends ChangeNotifier { notifyListeners(); } + Future reset() async { + if (isCodeRunning) { + await cancelRun(); + } + _runStartDate = null; + _runStopDate = null; + _result = null; + notifyListeners(); + } + void runCode({void Function()? onFinish}) { _runStartDate = DateTime.now(); _runStopDate = null; @@ -122,8 +133,21 @@ class CodeRunner extends ChangeNotifier { } Future cancelRun() async { + final hasInternet = (await Connectivity().checkConnectivity()).isConnected; + if (!hasInternet) { + _result = RunCodeResult( + status: _result?.status ?? RunCodeStatus.unspecified, + output: _result?.output, + log: _result?.log ?? '', + errorMessage: kInternetConnectionUnavailable, + graph: _result?.graph, + ); + notifyListeners(); + return; + } + snippetEditingController = null; - await _runSubscription?.cancel(); + unawaited(_runSubscription?.cancel()); final pipelineUuid = _result?.pipelineUuid ?? ''; if (pipelineUuid.isNotEmpty) { @@ -163,3 +187,12 @@ class CodeRunner extends ChangeNotifier { notifyListeners(); } } + +extension ConnectivityResultExtension on ConnectivityResult { + bool get isConnected => [ + ConnectivityResult.ethernet, + ConnectivityResult.mobile, + ConnectivityResult.vpn, + ConnectivityResult.wifi, + ].contains(this); +} diff --git a/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart index de8ee9112ed0..e7e73b9c2b7a 100644 --- a/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart +++ b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart @@ -49,6 +49,7 @@ const kTitle = 'Catalog'; const kExecutionCancelledText = '\nPipeline cancelled'; const kPipelineOptionsParseError = 'Failed to parse pipeline options, please check the format (example: --key1 value1 --key2 value2), only alphanumeric and ",*,/,-,:,;,\',. symbols are allowed'; +const kInternetConnectionUnavailable = 'Internet connection unavailable'; const kCachedResultsLog = 'The results of this example are taken from the Apache Beam Playground cache.\n'; @@ -218,7 +219,7 @@ class PlaygroundController with ChangeNotifier { controller.setExample(example, descriptor: descriptor); } - codeRunner.clearResult(); + codeRunner.reset(); notifyListeners(); } @@ -250,9 +251,8 @@ class PlaygroundController with ChangeNotifier { } Future reset() async { - await codeRunner.cancelRun(); snippetEditingController?.reset(); - codeRunner.clearResult(); + await codeRunner.reset(); } void resetErrorMessageText() { diff --git a/playground/frontend/playground_components/lib/src/repositories/code_client/grpc_code_client.dart b/playground/frontend/playground_components/lib/src/repositories/code_client/grpc_code_client.dart index bfeb517966eb..c8163608f3a0 100644 --- a/playground/frontend/playground_components/lib/src/repositories/code_client/grpc_code_client.dart +++ b/playground/frontend/playground_components/lib/src/repositories/code_client/grpc_code_client.dart @@ -18,10 +18,9 @@ import 'package:grpc/grpc.dart'; +import '../../../playground_components.dart'; import '../../api/iis_workaround_channel.dart'; import '../../api/v1/api.pbgrpc.dart' as grpc; -import '../../models/sdk.dart'; -import '../../util/pipeline_options.dart'; import '../dataset_grpc_extension.dart'; import '../models/check_status_response.dart'; import '../models/output_response.dart'; @@ -73,8 +72,11 @@ class GrpcCodeClient implements CodeClient { @override Future cancelExecution(String pipelineUuid) { - return _runSafely(() => - _defaultClient.cancel(grpc.CancelRequest(pipelineUuid: pipelineUuid))); + return _runSafely( + () => _defaultClient.cancel( + grpc.CancelRequest(pipelineUuid: pipelineUuid), + ), + ); } @override @@ -192,6 +194,11 @@ class GrpcCodeClient implements CodeClient { try { return await invoke(); } on GrpcError catch (error) { + //TODO(Malarg): check if internet connection available here using connectivity_plus + //throw internet unavailable exception, if not + if (error.message?.contains('CORS') ?? false) { + throw const RunCodeError(message: kInternetConnectionUnavailable); + } throw RunCodeError(message: error.message); } on Exception catch (_) { throw const RunCodeError(); diff --git a/playground/frontend/playground_components/lib/src/repositories/code_repository.dart b/playground/frontend/playground_components/lib/src/repositories/code_repository.dart index c9efc3146c09..2aacebd7cd16 100644 --- a/playground/frontend/playground_components/lib/src/repositories/code_repository.dart +++ b/playground/frontend/playground_components/lib/src/repositories/code_repository.dart @@ -37,7 +37,9 @@ const kProcessingStartedText = 'The processing has started\n'; class CodeRepository { final CodeClient _client; - CodeRepository({required CodeClient client,}): _client = client; + CodeRepository({ + required CodeClient client, + }) : _client = client; Stream runCode(RunCodeRequest request) async* { try { diff --git a/playground/frontend/playground_components/lib/src/repositories/models/run_code_result.dart b/playground/frontend/playground_components/lib/src/repositories/models/run_code_result.dart index 5a3bcd876916..dacfa1fa59ee 100644 --- a/playground/frontend/playground_components/lib/src/repositories/models/run_code_result.dart +++ b/playground/frontend/playground_components/lib/src/repositories/models/run_code_result.dart @@ -19,27 +19,27 @@ import 'package:equatable/equatable.dart'; enum RunCodeStatus { - unspecified, - preparation, - preparationError, - validationError, - compiling, compileError, + compiling, executing, - runError, finished, + preparation, + preparationError, + runError, timeout, unknownError, + unspecified, + validationError, } const kFinishedStatuses = [ - RunCodeStatus.unknownError, - RunCodeStatus.timeout, RunCodeStatus.compileError, + RunCodeStatus.finished, + RunCodeStatus.preparationError, RunCodeStatus.runError, + RunCodeStatus.timeout, + RunCodeStatus.unknownError, RunCodeStatus.validationError, - RunCodeStatus.preparationError, - RunCodeStatus.finished, ]; class RunCodeResult with EquatableMixin { @@ -65,13 +65,13 @@ class RunCodeResult with EquatableMixin { @override List get props => [ - status, - pipelineUuid, - output, - log, - graph, - errorMessage, - ]; + errorMessage, + graph, + log, + output, + pipelineUuid, + status, + ]; @override String toString() { diff --git a/playground/frontend/playground_components/pubspec.yaml b/playground/frontend/playground_components/pubspec.yaml index a87ac0f2a23a..63eeb9624bd5 100644 --- a/playground/frontend/playground_components/pubspec.yaml +++ b/playground/frontend/playground_components/pubspec.yaml @@ -28,6 +28,7 @@ dependencies: aligned_dialog: ^0.0.6 app_state: ^0.8.4 collection: ^1.16.0 + connectivity_plus: ^3.0.2 easy_localization: ^3.0.1 easy_localization_ext: ^0.1.1 easy_localization_loader: ^1.0.0 From 8034768ed44630cd4d403d5dd76aae44270c06d3 Mon Sep 17 00:00:00 2001 From: malarg Date: Thu, 9 Feb 2023 11:48:48 +0400 Subject: [PATCH 29/52] 25329 extract connectivity extension to separate file --- .../lib/src/controllers/code_runner.dart | 10 +------ .../lib/src/util/connectivity_result.dart | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 playground/frontend/playground_components/lib/src/util/connectivity_result.dart diff --git a/playground/frontend/playground_components/lib/src/controllers/code_runner.dart b/playground/frontend/playground_components/lib/src/controllers/code_runner.dart index 08b8c60cf869..c9a2e17936d5 100644 --- a/playground/frontend/playground_components/lib/src/controllers/code_runner.dart +++ b/playground/frontend/playground_components/lib/src/controllers/code_runner.dart @@ -24,6 +24,7 @@ import 'package:flutter/material.dart'; import '../../playground_components.dart'; import '../repositories/models/run_code_request.dart'; import '../repositories/models/run_code_result.dart'; +import '../util/connectivity_result.dart'; import 'snippet_editing_controller.dart'; class CodeRunner extends ChangeNotifier { @@ -187,12 +188,3 @@ class CodeRunner extends ChangeNotifier { notifyListeners(); } } - -extension ConnectivityResultExtension on ConnectivityResult { - bool get isConnected => [ - ConnectivityResult.ethernet, - ConnectivityResult.mobile, - ConnectivityResult.vpn, - ConnectivityResult.wifi, - ].contains(this); -} diff --git a/playground/frontend/playground_components/lib/src/util/connectivity_result.dart b/playground/frontend/playground_components/lib/src/util/connectivity_result.dart new file mode 100644 index 000000000000..e602cbe9d1ae --- /dev/null +++ b/playground/frontend/playground_components/lib/src/util/connectivity_result.dart @@ -0,0 +1,28 @@ +/* + * 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:connectivity_plus/connectivity_plus.dart'; + +extension ConnectivityResultExtension on ConnectivityResult { + bool get isConnected => [ + ConnectivityResult.ethernet, + ConnectivityResult.mobile, + ConnectivityResult.vpn, + ConnectivityResult.wifi, + ].contains(this); +} From e77ffafb003c5b09b0747ab010b1b3ff9608bcfe Mon Sep 17 00:00:00 2001 From: Alexey Inkin Date: Tue, 14 Feb 2023 10:34:41 +0400 Subject: [PATCH 30/52] Upgrade Flutter to 3.7.3 in integration tests (#24730) --- .../workflows/playground_frontend_test.yml | 2 +- .../lib/src/assets/assets.gen.dart | 22 +- .../playground_components/pubspec.yaml | 14 +- .../test/src/common/example_cache.mocks.dart | 76 +- .../common/example_repository_mock.mocks.dart | 62 +- .../examples_loader_test.mocks.dart | 250 ++++-- .../http_example_loader_test.mocks.dart | 76 +- .../playground_controller_test.mocks.dart | 101 ++- .../code_repository_test.mocks.dart | 115 ++- .../example_repository_test.mocks.dart | 156 +++- .../playground_components_dev/pubspec.yaml | 4 +- playground/frontend/pubspec.lock | 811 ++++++++++++------ playground/frontend/pubspec.yaml | 10 +- .../example_selector_state_test.mocks.dart | 56 +- 14 files changed, 1290 insertions(+), 465 deletions(-) diff --git a/.github/workflows/playground_frontend_test.yml b/.github/workflows/playground_frontend_test.yml index 129234887b16..4813b6c69944 100644 --- a/.github/workflows/playground_frontend_test.yml +++ b/.github/workflows/playground_frontend_test.yml @@ -35,7 +35,7 @@ jobs: runs-on: ubuntu-latest env: - FLUTTER_VERSION: '3.3.2' + FLUTTER_VERSION: '3.7.3' steps: - uses: actions/checkout@v3 diff --git a/playground/frontend/playground_components/lib/src/assets/assets.gen.dart b/playground/frontend/playground_components/lib/src/assets/assets.gen.dart index 2b64543628d7..288b595d38ba 100644 --- a/playground/frontend/playground_components/lib/src/assets/assets.gen.dart +++ b/playground/frontend/playground_components/lib/src/assets/assets.gen.dart @@ -5,7 +5,7 @@ // coverage:ignore-file // ignore_for_file: type=lint -// ignore_for_file: directives_ordering,unnecessary_import +// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use import 'package:flutter/widgets.dart'; @@ -17,6 +17,9 @@ class $AssetsButtonsGen { /// File path: assets/buttons/theme-mode.svg String get themeMode => 'assets/buttons/theme-mode.svg'; + + /// List of all assets + List get values => [reset, themeMode]; } class $AssetsNotificationIconsGen { @@ -33,6 +36,9 @@ class $AssetsNotificationIconsGen { /// File path: assets/notification_icons/warning.svg String get warning => 'assets/notification_icons/warning.svg'; + + /// List of all assets + List get values => [error, info, success, warning]; } class $AssetsPngGen { @@ -40,6 +46,9 @@ class $AssetsPngGen { /// File path: assets/png/beam-logo.png AssetGenImage get beamLogo => const AssetGenImage('assets/png/beam-logo.png'); + + /// List of all assets + List get values => [beamLogo]; } class $AssetsSvgGen { @@ -50,6 +59,9 @@ class $AssetsSvgGen { /// File path: assets/svg/drag-vertical.svg String get dragVertical => 'assets/svg/drag-vertical.svg'; + + /// List of all assets + List get values => [dragHorizontal, dragVertical]; } class $AssetsSymbolsGen { @@ -63,6 +75,9 @@ class $AssetsSymbolsGen { /// File path: assets/symbols/python.g.yaml String get pythonG => 'assets/symbols/python.g.yaml'; + + /// List of all assets + List get values => [goG, javaG, pythonG]; } class $AssetsTranslationsGen { @@ -70,6 +85,9 @@ class $AssetsTranslationsGen { /// File path: assets/translations/en.yaml String get en => 'assets/translations/en.yaml'; + + /// List of all assets + List get values => [en]; } class Assets { @@ -142,6 +160,8 @@ class AssetGenImage { ); } + ImageProvider provider() => AssetImage(_assetName); + String get path => _assetName; String get keyName => _assetName; diff --git a/playground/frontend/playground_components/pubspec.yaml b/playground/frontend/playground_components/pubspec.yaml index e7751981f1b9..7f256d55c331 100644 --- a/playground/frontend/playground_components/pubspec.yaml +++ b/playground/frontend/playground_components/pubspec.yaml @@ -21,12 +21,12 @@ version: 0.0.1 publish_to: none environment: - sdk: '>=2.18.1 <3.0.0' - flutter: '>=3.3.2' + sdk: '>=2.19.2 <4.0.0' + flutter: '>=3.7.3' dependencies: aligned_dialog: ^0.0.6 - app_state: ^0.8.4 + app_state: ^0.9.2 collection: ^1.16.0 easy_localization: ^3.0.1 easy_localization_ext: ^0.1.1 @@ -36,10 +36,10 @@ dependencies: flutter: { sdk: flutter } flutter_code_editor: ^0.2.5 flutter_markdown: ^0.6.12 - flutter_svg: ^1.0.3 + flutter_svg: ^2.0.1 fluttertoast: ^8.1.1 get_it: ^7.2.0 - google_fonts: ^3.0.1 + google_fonts: ^4.0.3 grpc: ^3.0.2 highlight: ^0.7.0 http: ^0.13.5 @@ -56,10 +56,10 @@ dependencies: dev_dependencies: build_runner: ^2.2.0 enum_map_gen: ^0.2.0 - flutter_gen_runner: ^4.3.0 + flutter_gen_runner: ^5.2.0 flutter_test: { sdk: flutter } json_serializable: ^6.4.1 - mockito: 5.2.0 + mockito: ^5.3.2 total_lints: ^2.17.4 flutter: diff --git a/playground/frontend/playground_components/test/src/common/example_cache.mocks.dart b/playground/frontend/playground_components/test/src/common/example_cache.mocks.dart index 5ce9a48c2f3c..2aea0d8433bd 100644 --- a/playground/frontend/playground_components/test/src/common/example_cache.mocks.dart +++ b/playground/frontend/playground_components/test/src/common/example_cache.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.2.0 from annotations +// Mocks generated by Mockito 5.3.2 from annotations // in playground_components/test/src/common/example_cache.dart. // Do not manually edit this file. @@ -25,10 +25,27 @@ import 'package:playground_components/src/models/snippet_file.dart' as _i9; // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class -class _FakeExampleBase_0 extends _i1.Fake implements _i2.ExampleBase {} +class _FakeExampleBase_0 extends _i1.SmartFake implements _i2.ExampleBase { + _FakeExampleBase_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeExample_1 extends _i1.Fake implements _i3.Example {} +class _FakeExample_1 extends _i1.SmartFake implements _i3.Example { + _FakeExample_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} /// A class which mocks [ExampleCache]. /// @@ -65,7 +82,7 @@ class MockExampleCache extends _i1.Mock implements _i4.ExampleCache { @override _i7.Future get allExamplesFuture => (super.noSuchMethod( Invocation.getter(#allExamplesFuture), - returnValue: Future.value(), + returnValue: _i7.Future.value(), ) as _i7.Future); @override _i8.LoadingStatus get catalogStatus => (super.noSuchMethod( @@ -83,8 +100,8 @@ class MockExampleCache extends _i1.Mock implements _i4.ExampleCache { #loadAllPrecompiledObjectsIfNot, [], ), - returnValue: Future.value(), - returnValueForMissingStub: Future.value(), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); @override List<_i6.CategoryWithExamples> getCategories(_i5.Sdk? sdk) => @@ -108,7 +125,16 @@ class MockExampleCache extends _i1.Mock implements _i4.ExampleCache { sdk, ], ), - returnValue: Future<_i2.ExampleBase>.value(_FakeExampleBase_0()), + returnValue: _i7.Future<_i2.ExampleBase>.value(_FakeExampleBase_0( + this, + Invocation.method( + #getPrecompiledObject, + [ + path, + sdk, + ], + ), + )), ) as _i7.Future<_i2.ExampleBase>); @override _i7.Future<_i3.Example> loadSharedExample(String? id) => (super.noSuchMethod( @@ -116,13 +142,19 @@ class MockExampleCache extends _i1.Mock implements _i4.ExampleCache { #loadSharedExample, [id], ), - returnValue: Future<_i3.Example>.value(_FakeExample_1()), + returnValue: _i7.Future<_i3.Example>.value(_FakeExample_1( + this, + Invocation.method( + #loadSharedExample, + [id], + ), + )), ) as _i7.Future<_i3.Example>); @override _i7.Future saveSnippet({ - List<_i9.SnippetFile>? files, - _i5.Sdk? sdk, - String? pipelineOptions, + required List<_i9.SnippetFile>? files, + required _i5.Sdk? sdk, + required String? pipelineOptions, }) => (super.noSuchMethod( Invocation.method( @@ -134,7 +166,7 @@ class MockExampleCache extends _i1.Mock implements _i4.ExampleCache { #pipelineOptions: pipelineOptions, }, ), - returnValue: Future.value(''), + returnValue: _i7.Future.value(''), ) as _i7.Future); @override _i7.Future<_i3.Example> loadExampleInfo(_i2.ExampleBase? example) => @@ -143,7 +175,13 @@ class MockExampleCache extends _i1.Mock implements _i4.ExampleCache { #loadExampleInfo, [example], ), - returnValue: Future<_i3.Example>.value(_FakeExample_1()), + returnValue: _i7.Future<_i3.Example>.value(_FakeExample_1( + this, + Invocation.method( + #loadExampleInfo, + [example], + ), + )), ) as _i7.Future<_i3.Example>); @override void setSelectorOpened(bool? value) => super.noSuchMethod( @@ -160,7 +198,7 @@ class MockExampleCache extends _i1.Mock implements _i4.ExampleCache { #getDefaultExampleBySdk, [sdk], ), - returnValue: Future<_i3.Example?>.value(), + returnValue: _i7.Future<_i3.Example?>.value(), ) as _i7.Future<_i3.Example?>); @override _i7.Future loadDefaultPrecompiledObjects() => (super.noSuchMethod( @@ -168,8 +206,8 @@ class MockExampleCache extends _i1.Mock implements _i4.ExampleCache { #loadDefaultPrecompiledObjects, [], ), - returnValue: Future.value(), - returnValueForMissingStub: Future.value(), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); @override _i7.Future loadDefaultPrecompiledObjectsIfNot() => (super.noSuchMethod( @@ -177,8 +215,8 @@ class MockExampleCache extends _i1.Mock implements _i4.ExampleCache { #loadDefaultPrecompiledObjectsIfNot, [], ), - returnValue: Future.value(), - returnValueForMissingStub: Future.value(), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); @override _i7.Future<_i2.ExampleBase?> getCatalogExampleByPath(String? path) => @@ -187,7 +225,7 @@ class MockExampleCache extends _i1.Mock implements _i4.ExampleCache { #getCatalogExampleByPath, [path], ), - returnValue: Future<_i2.ExampleBase?>.value(), + returnValue: _i7.Future<_i2.ExampleBase?>.value(), ) as _i7.Future<_i2.ExampleBase?>); @override void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( diff --git a/playground/frontend/playground_components/test/src/common/example_repository_mock.mocks.dart b/playground/frontend/playground_components/test/src/common/example_repository_mock.mocks.dart index aa55c5193fbe..b426f3c01502 100644 --- a/playground/frontend/playground_components/test/src/common/example_repository_mock.mocks.dart +++ b/playground/frontend/playground_components/test/src/common/example_repository_mock.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.2.0 from annotations +// Mocks generated by Mockito 5.3.2 from annotations // in playground_components/test/src/common/example_repository_mock.dart. // Do not manually edit this file. @@ -35,11 +35,28 @@ import 'package:playground_components/src/repositories/models/save_snippet_reque // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class -class _FakeExampleBase_0 extends _i1.Fake implements _i2.ExampleBase {} +class _FakeExampleBase_0 extends _i1.SmartFake implements _i2.ExampleBase { + _FakeExampleBase_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeGetSnippetResponse_1 extends _i1.Fake - implements _i3.GetSnippetResponse {} +class _FakeGetSnippetResponse_1 extends _i1.SmartFake + implements _i3.GetSnippetResponse { + _FakeGetSnippetResponse_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} /// A class which mocks [ExampleRepository]. /// @@ -58,7 +75,7 @@ class MockExampleRepository extends _i1.Mock implements _i4.ExampleRepository { [request], ), returnValue: - Future>>.value( + _i5.Future>>.value( <_i6.Sdk, List<_i7.CategoryWithExamples>>{}), ) as _i5.Future>>); @override @@ -69,7 +86,13 @@ class MockExampleRepository extends _i1.Mock implements _i4.ExampleRepository { #getDefaultPrecompiledObject, [request], ), - returnValue: Future<_i2.ExampleBase>.value(_FakeExampleBase_0()), + returnValue: _i5.Future<_i2.ExampleBase>.value(_FakeExampleBase_0( + this, + Invocation.method( + #getDefaultPrecompiledObject, + [request], + ), + )), ) as _i5.Future<_i2.ExampleBase>); @override _i5.Future> getPrecompiledObjectCode( @@ -79,7 +102,8 @@ class MockExampleRepository extends _i1.Mock implements _i4.ExampleRepository { #getPrecompiledObjectCode, [request], ), - returnValue: Future>.value(<_i10.SnippetFile>[]), + returnValue: + _i5.Future>.value(<_i10.SnippetFile>[]), ) as _i5.Future>); @override _i5.Future getPrecompiledObjectOutput( @@ -89,7 +113,7 @@ class MockExampleRepository extends _i1.Mock implements _i4.ExampleRepository { #getPrecompiledObjectOutput, [request], ), - returnValue: Future.value(''), + returnValue: _i5.Future.value(''), ) as _i5.Future); @override _i5.Future getPrecompiledObjectLogs( @@ -99,7 +123,7 @@ class MockExampleRepository extends _i1.Mock implements _i4.ExampleRepository { #getPrecompiledObjectLogs, [request], ), - returnValue: Future.value(''), + returnValue: _i5.Future.value(''), ) as _i5.Future); @override _i5.Future getPrecompiledObjectGraph( @@ -109,7 +133,7 @@ class MockExampleRepository extends _i1.Mock implements _i4.ExampleRepository { #getPrecompiledObjectGraph, [request], ), - returnValue: Future.value(''), + returnValue: _i5.Future.value(''), ) as _i5.Future); @override _i5.Future<_i2.ExampleBase> getPrecompiledObject( @@ -119,7 +143,13 @@ class MockExampleRepository extends _i1.Mock implements _i4.ExampleRepository { #getPrecompiledObject, [request], ), - returnValue: Future<_i2.ExampleBase>.value(_FakeExampleBase_0()), + returnValue: _i5.Future<_i2.ExampleBase>.value(_FakeExampleBase_0( + this, + Invocation.method( + #getPrecompiledObject, + [request], + ), + )), ) as _i5.Future<_i2.ExampleBase>); @override _i5.Future<_i3.GetSnippetResponse> getSnippet( @@ -130,7 +160,13 @@ class MockExampleRepository extends _i1.Mock implements _i4.ExampleRepository { [request], ), returnValue: - Future<_i3.GetSnippetResponse>.value(_FakeGetSnippetResponse_1()), + _i5.Future<_i3.GetSnippetResponse>.value(_FakeGetSnippetResponse_1( + this, + Invocation.method( + #getSnippet, + [request], + ), + )), ) as _i5.Future<_i3.GetSnippetResponse>); @override _i5.Future saveSnippet(_i13.SaveSnippetRequest? request) => @@ -139,6 +175,6 @@ class MockExampleRepository extends _i1.Mock implements _i4.ExampleRepository { #saveSnippet, [request], ), - returnValue: Future.value(''), + returnValue: _i5.Future.value(''), ) as _i5.Future); } diff --git a/playground/frontend/playground_components/test/src/controllers/example_loaders/examples_loader_test.mocks.dart b/playground/frontend/playground_components/test/src/controllers/example_loaders/examples_loader_test.mocks.dart index daf009d7d322..094da9ccd16f 100644 --- a/playground/frontend/playground_components/test/src/controllers/example_loaders/examples_loader_test.mocks.dart +++ b/playground/frontend/playground_components/test/src/controllers/example_loaders/examples_loader_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.2.0 from annotations +// Mocks generated by Mockito 5.3.2 from annotations // in playground_components/test/src/controllers/example_loaders/examples_loader_test.dart. // Do not manually edit this file. @@ -41,30 +41,112 @@ import 'package:playground_components/src/models/snippet_file.dart' as _i19; // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class -class _FakeExampleCache_0 extends _i1.Fake implements _i2.ExampleCache {} +class _FakeExampleCache_0 extends _i1.SmartFake implements _i2.ExampleCache { + _FakeExampleCache_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeExamplesLoader_1 extends _i1.Fake implements _i3.ExamplesLoader {} +class _FakeExamplesLoader_1 extends _i1.SmartFake + implements _i3.ExamplesLoader { + _FakeExamplesLoader_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeOutputFilterTypeController_2 extends _i1.Fake - implements _i4.OutputFilterTypeController {} +class _FakeOutputFilterTypeController_2 extends _i1.SmartFake + implements _i4.OutputFilterTypeController { + _FakeOutputFilterTypeController_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeCodeRunner_3 extends _i1.Fake implements _i5.CodeRunner {} +class _FakeCodeRunner_3 extends _i1.SmartFake implements _i5.CodeRunner { + _FakeCodeRunner_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeBeamShortcut_4 extends _i1.Fake implements _i6.BeamShortcut {} +class _FakeBeamShortcut_4 extends _i1.SmartFake implements _i6.BeamShortcut { + _FakeBeamShortcut_4( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeSnippetEditingController_5 extends _i1.Fake - implements _i7.SnippetEditingController {} +class _FakeSnippetEditingController_5 extends _i1.SmartFake + implements _i7.SnippetEditingController { + _FakeSnippetEditingController_5( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeUserSharedExampleLoadingDescriptor_6 extends _i1.Fake - implements _i8.UserSharedExampleLoadingDescriptor {} +class _FakeUserSharedExampleLoadingDescriptor_6 extends _i1.SmartFake + implements _i8.UserSharedExampleLoadingDescriptor { + _FakeUserSharedExampleLoadingDescriptor_6( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeExamplesLoadingDescriptor_7 extends _i1.Fake - implements _i9.ExamplesLoadingDescriptor {} +class _FakeExamplesLoadingDescriptor_7 extends _i1.SmartFake + implements _i9.ExamplesLoadingDescriptor { + _FakeExamplesLoadingDescriptor_7( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeExampleBase_8 extends _i1.Fake implements _i10.ExampleBase {} +class _FakeExampleBase_8 extends _i1.SmartFake implements _i10.ExampleBase { + _FakeExampleBase_8( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeExample_9 extends _i1.Fake implements _i11.Example {} +class _FakeExample_9 extends _i1.SmartFake implements _i11.Example { + _FakeExample_9( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} /// A class which mocks [PlaygroundController]. /// @@ -78,23 +160,35 @@ class MockPlaygroundController extends _i1.Mock @override _i2.ExampleCache get exampleCache => (super.noSuchMethod( Invocation.getter(#exampleCache), - returnValue: _FakeExampleCache_0(), + returnValue: _FakeExampleCache_0( + this, + Invocation.getter(#exampleCache), + ), ) as _i2.ExampleCache); @override _i3.ExamplesLoader get examplesLoader => (super.noSuchMethod( Invocation.getter(#examplesLoader), - returnValue: _FakeExamplesLoader_1(), + returnValue: _FakeExamplesLoader_1( + this, + Invocation.getter(#examplesLoader), + ), ) as _i3.ExamplesLoader); @override _i4.OutputFilterTypeController get outputTypeController => (super.noSuchMethod( Invocation.getter(#outputTypeController), - returnValue: _FakeOutputFilterTypeController_2(), + returnValue: _FakeOutputFilterTypeController_2( + this, + Invocation.getter(#outputTypeController), + ), ) as _i4.OutputFilterTypeController); @override _i5.CodeRunner get codeRunner => (super.noSuchMethod( Invocation.getter(#codeRunner), - returnValue: _FakeCodeRunner_3(), + returnValue: _FakeCodeRunner_3( + this, + Invocation.getter(#codeRunner), + ), ) as _i5.CodeRunner); @override set codeRunner(_i5.CodeRunner? _codeRunner) => super.noSuchMethod( @@ -107,7 +201,10 @@ class MockPlaygroundController extends _i1.Mock @override _i6.BeamShortcut get runShortcut => (super.noSuchMethod( Invocation.getter(#runShortcut), - returnValue: _FakeBeamShortcut_4(), + returnValue: _FakeBeamShortcut_4( + this, + Invocation.getter(#runShortcut), + ), ) as _i6.BeamShortcut); @override set runShortcut(_i6.BeamShortcut? _runShortcut) => super.noSuchMethod( @@ -120,7 +217,10 @@ class MockPlaygroundController extends _i1.Mock @override _i6.BeamShortcut get resetShortcut => (super.noSuchMethod( Invocation.getter(#resetShortcut), - returnValue: _FakeBeamShortcut_4(), + returnValue: _FakeBeamShortcut_4( + this, + Invocation.getter(#resetShortcut), + ), ) as _i6.BeamShortcut); @override set resetShortcut(_i6.BeamShortcut? _resetShortcut) => super.noSuchMethod( @@ -132,15 +232,18 @@ class MockPlaygroundController extends _i1.Mock ); @override _i6.BeamShortcut get showSuggestionsShortcut => (super.noSuchMethod( - Invocation.getter(#showAutocompleterShortcut), - returnValue: _FakeBeamShortcut_4(), + Invocation.getter(#showSuggestionsShortcut), + returnValue: _FakeBeamShortcut_4( + this, + Invocation.getter(#showSuggestionsShortcut), + ), ) as _i6.BeamShortcut); @override - set showSuggestionsShortcut(_i6.BeamShortcut? _showAutocompleterShortcut) => + set showSuggestionsShortcut(_i6.BeamShortcut? _showSuggestionsShortcut) => super.noSuchMethod( Invocation.setter( - #showAutocompleterShortcut, - _showAutocompleterShortcut, + #showSuggestionsShortcut, + _showSuggestionsShortcut, ), returnValueForMissingStub: null, ); @@ -171,7 +274,13 @@ class MockPlaygroundController extends _i1.Mock #requireSnippetEditingController, [], ), - returnValue: _FakeSnippetEditingController_5(), + returnValue: _FakeSnippetEditingController_5( + this, + Invocation.method( + #requireSnippetEditingController, + [], + ), + ), ) as _i7.SnippetEditingController); @override void setEmptyIfNoSdk(_i13.Sdk? sdk) => super.noSuchMethod( @@ -184,7 +293,7 @@ class MockPlaygroundController extends _i1.Mock @override void setEmptyIfNotExists( _i13.Sdk? sdk, { - bool? setCurrentSdk, + required bool? setCurrentSdk, }) => super.noSuchMethod( Invocation.method( @@ -201,14 +310,14 @@ class MockPlaygroundController extends _i1.Mock #setExampleBase, [exampleBase], ), - returnValue: Future.value(), - returnValueForMissingStub: Future.value(), + returnValue: _i14.Future.value(), + returnValueForMissingStub: _i14.Future.value(), ) as _i14.Future); @override void setExample( _i11.Example? example, { - _i15.ExampleLoadingDescriptor? descriptor, - bool? setCurrentSdk, + required _i15.ExampleLoadingDescriptor? descriptor, + required bool? setCurrentSdk, }) => super.noSuchMethod( Invocation.method( @@ -240,13 +349,13 @@ class MockPlaygroundController extends _i1.Mock #reset, [], ), - returnValue: Future.value(), - returnValueForMissingStub: Future.value(), + returnValue: _i14.Future.value(), + returnValueForMissingStub: _i14.Future.value(), ) as _i14.Future); @override void showSuggestions() => super.noSuchMethod( Invocation.method( - #showAutocompleter, + #showSuggestions, [], ), returnValueForMissingStub: null, @@ -274,8 +383,14 @@ class MockPlaygroundController extends _i1.Mock #saveSnippet, [], ), - returnValue: Future<_i8.UserSharedExampleLoadingDescriptor>.value( - _FakeUserSharedExampleLoadingDescriptor_6()), + returnValue: _i14.Future<_i8.UserSharedExampleLoadingDescriptor>.value( + _FakeUserSharedExampleLoadingDescriptor_6( + this, + Invocation.method( + #saveSnippet, + [], + ), + )), ) as _i14.Future<_i8.UserSharedExampleLoadingDescriptor>); @override _i9.ExamplesLoadingDescriptor getLoadingDescriptor() => (super.noSuchMethod( @@ -283,7 +398,13 @@ class MockPlaygroundController extends _i1.Mock #getLoadingDescriptor, [], ), - returnValue: _FakeExamplesLoadingDescriptor_7(), + returnValue: _FakeExamplesLoadingDescriptor_7( + this, + Invocation.method( + #getLoadingDescriptor, + [], + ), + ), ) as _i9.ExamplesLoadingDescriptor); @override void dispose() => super.noSuchMethod( @@ -354,7 +475,7 @@ class MockExampleCache extends _i1.Mock implements _i2.ExampleCache { @override _i14.Future get allExamplesFuture => (super.noSuchMethod( Invocation.getter(#allExamplesFuture), - returnValue: Future.value(), + returnValue: _i14.Future.value(), ) as _i14.Future); @override _i18.LoadingStatus get catalogStatus => (super.noSuchMethod( @@ -372,8 +493,8 @@ class MockExampleCache extends _i1.Mock implements _i2.ExampleCache { #loadAllPrecompiledObjectsIfNot, [], ), - returnValue: Future.value(), - returnValueForMissingStub: Future.value(), + returnValue: _i14.Future.value(), + returnValueForMissingStub: _i14.Future.value(), ) as _i14.Future); @override List<_i17.CategoryWithExamples> getCategories(_i13.Sdk? sdk) => @@ -397,7 +518,16 @@ class MockExampleCache extends _i1.Mock implements _i2.ExampleCache { sdk, ], ), - returnValue: Future<_i10.ExampleBase>.value(_FakeExampleBase_8()), + returnValue: _i14.Future<_i10.ExampleBase>.value(_FakeExampleBase_8( + this, + Invocation.method( + #getPrecompiledObject, + [ + path, + sdk, + ], + ), + )), ) as _i14.Future<_i10.ExampleBase>); @override _i14.Future<_i11.Example> loadSharedExample(String? id) => @@ -406,13 +536,19 @@ class MockExampleCache extends _i1.Mock implements _i2.ExampleCache { #loadSharedExample, [id], ), - returnValue: Future<_i11.Example>.value(_FakeExample_9()), + returnValue: _i14.Future<_i11.Example>.value(_FakeExample_9( + this, + Invocation.method( + #loadSharedExample, + [id], + ), + )), ) as _i14.Future<_i11.Example>); @override _i14.Future saveSnippet({ - List<_i19.SnippetFile>? files, - _i13.Sdk? sdk, - String? pipelineOptions, + required List<_i19.SnippetFile>? files, + required _i13.Sdk? sdk, + required String? pipelineOptions, }) => (super.noSuchMethod( Invocation.method( @@ -424,7 +560,7 @@ class MockExampleCache extends _i1.Mock implements _i2.ExampleCache { #pipelineOptions: pipelineOptions, }, ), - returnValue: Future.value(''), + returnValue: _i14.Future.value(''), ) as _i14.Future); @override _i14.Future<_i11.Example> loadExampleInfo(_i10.ExampleBase? example) => @@ -433,7 +569,13 @@ class MockExampleCache extends _i1.Mock implements _i2.ExampleCache { #loadExampleInfo, [example], ), - returnValue: Future<_i11.Example>.value(_FakeExample_9()), + returnValue: _i14.Future<_i11.Example>.value(_FakeExample_9( + this, + Invocation.method( + #loadExampleInfo, + [example], + ), + )), ) as _i14.Future<_i11.Example>); @override void setSelectorOpened(bool? value) => super.noSuchMethod( @@ -450,7 +592,7 @@ class MockExampleCache extends _i1.Mock implements _i2.ExampleCache { #getDefaultExampleBySdk, [sdk], ), - returnValue: Future<_i11.Example?>.value(), + returnValue: _i14.Future<_i11.Example?>.value(), ) as _i14.Future<_i11.Example?>); @override _i14.Future loadDefaultPrecompiledObjects() => (super.noSuchMethod( @@ -458,8 +600,8 @@ class MockExampleCache extends _i1.Mock implements _i2.ExampleCache { #loadDefaultPrecompiledObjects, [], ), - returnValue: Future.value(), - returnValueForMissingStub: Future.value(), + returnValue: _i14.Future.value(), + returnValueForMissingStub: _i14.Future.value(), ) as _i14.Future); @override _i14.Future loadDefaultPrecompiledObjectsIfNot() => (super.noSuchMethod( @@ -467,8 +609,8 @@ class MockExampleCache extends _i1.Mock implements _i2.ExampleCache { #loadDefaultPrecompiledObjectsIfNot, [], ), - returnValue: Future.value(), - returnValueForMissingStub: Future.value(), + returnValue: _i14.Future.value(), + returnValueForMissingStub: _i14.Future.value(), ) as _i14.Future); @override _i14.Future<_i10.ExampleBase?> getCatalogExampleByPath(String? path) => @@ -477,7 +619,7 @@ class MockExampleCache extends _i1.Mock implements _i2.ExampleCache { #getCatalogExampleByPath, [path], ), - returnValue: Future<_i10.ExampleBase?>.value(), + returnValue: _i14.Future<_i10.ExampleBase?>.value(), ) as _i14.Future<_i10.ExampleBase?>); @override void addListener(_i16.VoidCallback? listener) => super.noSuchMethod( diff --git a/playground/frontend/playground_components/test/src/controllers/example_loaders/http_example_loader_test.mocks.dart b/playground/frontend/playground_components/test/src/controllers/example_loaders/http_example_loader_test.mocks.dart index a697f1105ad4..bd122604cbd9 100644 --- a/playground/frontend/playground_components/test/src/controllers/example_loaders/http_example_loader_test.mocks.dart +++ b/playground/frontend/playground_components/test/src/controllers/example_loaders/http_example_loader_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.2.0 from annotations +// Mocks generated by Mockito 5.3.2 from annotations // in playground_components/test/src/controllers/example_loaders/http_example_loader_test.dart. // Do not manually edit this file. @@ -25,10 +25,27 @@ import 'package:playground_components/src/models/snippet_file.dart' as _i9; // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class -class _FakeExampleBase_0 extends _i1.Fake implements _i2.ExampleBase {} +class _FakeExampleBase_0 extends _i1.SmartFake implements _i2.ExampleBase { + _FakeExampleBase_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeExample_1 extends _i1.Fake implements _i3.Example {} +class _FakeExample_1 extends _i1.SmartFake implements _i3.Example { + _FakeExample_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} /// A class which mocks [ExampleCache]. /// @@ -65,7 +82,7 @@ class MockExampleCache extends _i1.Mock implements _i4.ExampleCache { @override _i7.Future get allExamplesFuture => (super.noSuchMethod( Invocation.getter(#allExamplesFuture), - returnValue: Future.value(), + returnValue: _i7.Future.value(), ) as _i7.Future); @override _i8.LoadingStatus get catalogStatus => (super.noSuchMethod( @@ -83,8 +100,8 @@ class MockExampleCache extends _i1.Mock implements _i4.ExampleCache { #loadAllPrecompiledObjectsIfNot, [], ), - returnValue: Future.value(), - returnValueForMissingStub: Future.value(), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); @override List<_i6.CategoryWithExamples> getCategories(_i5.Sdk? sdk) => @@ -108,7 +125,16 @@ class MockExampleCache extends _i1.Mock implements _i4.ExampleCache { sdk, ], ), - returnValue: Future<_i2.ExampleBase>.value(_FakeExampleBase_0()), + returnValue: _i7.Future<_i2.ExampleBase>.value(_FakeExampleBase_0( + this, + Invocation.method( + #getPrecompiledObject, + [ + path, + sdk, + ], + ), + )), ) as _i7.Future<_i2.ExampleBase>); @override _i7.Future<_i3.Example> loadSharedExample(String? id) => (super.noSuchMethod( @@ -116,13 +142,19 @@ class MockExampleCache extends _i1.Mock implements _i4.ExampleCache { #loadSharedExample, [id], ), - returnValue: Future<_i3.Example>.value(_FakeExample_1()), + returnValue: _i7.Future<_i3.Example>.value(_FakeExample_1( + this, + Invocation.method( + #loadSharedExample, + [id], + ), + )), ) as _i7.Future<_i3.Example>); @override _i7.Future saveSnippet({ - List<_i9.SnippetFile>? files, - _i5.Sdk? sdk, - String? pipelineOptions, + required List<_i9.SnippetFile>? files, + required _i5.Sdk? sdk, + required String? pipelineOptions, }) => (super.noSuchMethod( Invocation.method( @@ -134,7 +166,7 @@ class MockExampleCache extends _i1.Mock implements _i4.ExampleCache { #pipelineOptions: pipelineOptions, }, ), - returnValue: Future.value(''), + returnValue: _i7.Future.value(''), ) as _i7.Future); @override _i7.Future<_i3.Example> loadExampleInfo(_i2.ExampleBase? example) => @@ -143,7 +175,13 @@ class MockExampleCache extends _i1.Mock implements _i4.ExampleCache { #loadExampleInfo, [example], ), - returnValue: Future<_i3.Example>.value(_FakeExample_1()), + returnValue: _i7.Future<_i3.Example>.value(_FakeExample_1( + this, + Invocation.method( + #loadExampleInfo, + [example], + ), + )), ) as _i7.Future<_i3.Example>); @override void setSelectorOpened(bool? value) => super.noSuchMethod( @@ -160,7 +198,7 @@ class MockExampleCache extends _i1.Mock implements _i4.ExampleCache { #getDefaultExampleBySdk, [sdk], ), - returnValue: Future<_i3.Example?>.value(), + returnValue: _i7.Future<_i3.Example?>.value(), ) as _i7.Future<_i3.Example?>); @override _i7.Future loadDefaultPrecompiledObjects() => (super.noSuchMethod( @@ -168,8 +206,8 @@ class MockExampleCache extends _i1.Mock implements _i4.ExampleCache { #loadDefaultPrecompiledObjects, [], ), - returnValue: Future.value(), - returnValueForMissingStub: Future.value(), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); @override _i7.Future loadDefaultPrecompiledObjectsIfNot() => (super.noSuchMethod( @@ -177,8 +215,8 @@ class MockExampleCache extends _i1.Mock implements _i4.ExampleCache { #loadDefaultPrecompiledObjectsIfNot, [], ), - returnValue: Future.value(), - returnValueForMissingStub: Future.value(), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); @override _i7.Future<_i2.ExampleBase?> getCatalogExampleByPath(String? path) => @@ -187,7 +225,7 @@ class MockExampleCache extends _i1.Mock implements _i4.ExampleCache { #getCatalogExampleByPath, [path], ), - returnValue: Future<_i2.ExampleBase?>.value(), + returnValue: _i7.Future<_i2.ExampleBase?>.value(), ) as _i7.Future<_i2.ExampleBase?>); @override void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( diff --git a/playground/frontend/playground_components/test/src/controllers/playground_controller_test.mocks.dart b/playground/frontend/playground_components/test/src/controllers/playground_controller_test.mocks.dart index 9bccb3f91f74..d2d6f8bb2390 100644 --- a/playground/frontend/playground_components/test/src/controllers/playground_controller_test.mocks.dart +++ b/playground/frontend/playground_components/test/src/controllers/playground_controller_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.2.0 from annotations +// Mocks generated by Mockito 5.3.2 from annotations // in playground_components/test/src/controllers/playground_controller_test.dart. // Do not manually edit this file. @@ -33,13 +33,38 @@ import 'package:playground_components/src/models/snippet_file.dart' as _i13; // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class -class _FakeExampleLoaderFactory_0 extends _i1.Fake - implements _i2.ExampleLoaderFactory {} +class _FakeExampleLoaderFactory_0 extends _i1.SmartFake + implements _i2.ExampleLoaderFactory { + _FakeExampleLoaderFactory_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeExampleBase_1 extends _i1.Fake implements _i3.ExampleBase {} +class _FakeExampleBase_1 extends _i1.SmartFake implements _i3.ExampleBase { + _FakeExampleBase_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeExample_2 extends _i1.Fake implements _i4.Example {} +class _FakeExample_2 extends _i1.SmartFake implements _i4.Example { + _FakeExample_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} /// A class which mocks [ExamplesLoader]. /// @@ -52,7 +77,10 @@ class MockExamplesLoader extends _i1.Mock implements _i5.ExamplesLoader { @override _i2.ExampleLoaderFactory get defaultFactory => (super.noSuchMethod( Invocation.getter(#defaultFactory), - returnValue: _FakeExampleLoaderFactory_0(), + returnValue: _FakeExampleLoaderFactory_0( + this, + Invocation.getter(#defaultFactory), + ), ) as _i2.ExampleLoaderFactory); @override void setPlaygroundController(_i6.PlaygroundController? value) => @@ -70,8 +98,8 @@ class MockExamplesLoader extends _i1.Mock implements _i5.ExamplesLoader { #load, [descriptor], ), - returnValue: Future.value(), - returnValueForMissingStub: Future.value(), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); @override _i7.Future loadDefaultIfAny(_i9.Sdk? sdk) => (super.noSuchMethod( @@ -79,8 +107,8 @@ class MockExamplesLoader extends _i1.Mock implements _i5.ExamplesLoader { #loadDefaultIfAny, [sdk], ), - returnValue: Future.value(), - returnValueForMissingStub: Future.value(), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); } @@ -119,7 +147,7 @@ class MockExampleCache extends _i1.Mock implements _i10.ExampleCache { @override _i7.Future get allExamplesFuture => (super.noSuchMethod( Invocation.getter(#allExamplesFuture), - returnValue: Future.value(), + returnValue: _i7.Future.value(), ) as _i7.Future); @override _i12.LoadingStatus get catalogStatus => (super.noSuchMethod( @@ -137,8 +165,8 @@ class MockExampleCache extends _i1.Mock implements _i10.ExampleCache { #loadAllPrecompiledObjectsIfNot, [], ), - returnValue: Future.value(), - returnValueForMissingStub: Future.value(), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); @override List<_i11.CategoryWithExamples> getCategories(_i9.Sdk? sdk) => @@ -162,7 +190,16 @@ class MockExampleCache extends _i1.Mock implements _i10.ExampleCache { sdk, ], ), - returnValue: Future<_i3.ExampleBase>.value(_FakeExampleBase_1()), + returnValue: _i7.Future<_i3.ExampleBase>.value(_FakeExampleBase_1( + this, + Invocation.method( + #getPrecompiledObject, + [ + path, + sdk, + ], + ), + )), ) as _i7.Future<_i3.ExampleBase>); @override _i7.Future<_i4.Example> loadSharedExample(String? id) => (super.noSuchMethod( @@ -170,13 +207,19 @@ class MockExampleCache extends _i1.Mock implements _i10.ExampleCache { #loadSharedExample, [id], ), - returnValue: Future<_i4.Example>.value(_FakeExample_2()), + returnValue: _i7.Future<_i4.Example>.value(_FakeExample_2( + this, + Invocation.method( + #loadSharedExample, + [id], + ), + )), ) as _i7.Future<_i4.Example>); @override _i7.Future saveSnippet({ - List<_i13.SnippetFile>? files, - _i9.Sdk? sdk, - String? pipelineOptions, + required List<_i13.SnippetFile>? files, + required _i9.Sdk? sdk, + required String? pipelineOptions, }) => (super.noSuchMethod( Invocation.method( @@ -188,7 +231,7 @@ class MockExampleCache extends _i1.Mock implements _i10.ExampleCache { #pipelineOptions: pipelineOptions, }, ), - returnValue: Future.value(''), + returnValue: _i7.Future.value(''), ) as _i7.Future); @override _i7.Future<_i4.Example> loadExampleInfo(_i3.ExampleBase? example) => @@ -197,7 +240,13 @@ class MockExampleCache extends _i1.Mock implements _i10.ExampleCache { #loadExampleInfo, [example], ), - returnValue: Future<_i4.Example>.value(_FakeExample_2()), + returnValue: _i7.Future<_i4.Example>.value(_FakeExample_2( + this, + Invocation.method( + #loadExampleInfo, + [example], + ), + )), ) as _i7.Future<_i4.Example>); @override void setSelectorOpened(bool? value) => super.noSuchMethod( @@ -214,7 +263,7 @@ class MockExampleCache extends _i1.Mock implements _i10.ExampleCache { #getDefaultExampleBySdk, [sdk], ), - returnValue: Future<_i4.Example?>.value(), + returnValue: _i7.Future<_i4.Example?>.value(), ) as _i7.Future<_i4.Example?>); @override _i7.Future loadDefaultPrecompiledObjects() => (super.noSuchMethod( @@ -222,8 +271,8 @@ class MockExampleCache extends _i1.Mock implements _i10.ExampleCache { #loadDefaultPrecompiledObjects, [], ), - returnValue: Future.value(), - returnValueForMissingStub: Future.value(), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); @override _i7.Future loadDefaultPrecompiledObjectsIfNot() => (super.noSuchMethod( @@ -231,8 +280,8 @@ class MockExampleCache extends _i1.Mock implements _i10.ExampleCache { #loadDefaultPrecompiledObjectsIfNot, [], ), - returnValue: Future.value(), - returnValueForMissingStub: Future.value(), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); @override _i7.Future<_i3.ExampleBase?> getCatalogExampleByPath(String? path) => @@ -241,7 +290,7 @@ class MockExampleCache extends _i1.Mock implements _i10.ExampleCache { #getCatalogExampleByPath, [path], ), - returnValue: Future<_i3.ExampleBase?>.value(), + returnValue: _i7.Future<_i3.ExampleBase?>.value(), ) as _i7.Future<_i3.ExampleBase?>); @override void addListener(_i14.VoidCallback? listener) => super.noSuchMethod( diff --git a/playground/frontend/playground_components/test/src/repositories/code_repository_test.mocks.dart b/playground/frontend/playground_components/test/src/repositories/code_repository_test.mocks.dart index 78c1cdb89592..8963431373af 100644 --- a/playground/frontend/playground_components/test/src/repositories/code_repository_test.mocks.dart +++ b/playground/frontend/playground_components/test/src/repositories/code_repository_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.2.0 from annotations +// Mocks generated by Mockito 5.3.2 from annotations // in playground_components/test/src/repositories/code_repository_test.dart. // Do not manually edit this file. @@ -26,13 +26,40 @@ import 'package:playground_components/src/repositories/models/run_code_response. // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class -class _FakeRunCodeResponse_0 extends _i1.Fake implements _i2.RunCodeResponse {} +class _FakeRunCodeResponse_0 extends _i1.SmartFake + implements _i2.RunCodeResponse { + _FakeRunCodeResponse_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeCheckStatusResponse_1 extends _i1.Fake - implements _i3.CheckStatusResponse {} +class _FakeCheckStatusResponse_1 extends _i1.SmartFake + implements _i3.CheckStatusResponse { + _FakeCheckStatusResponse_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeOutputResponse_2 extends _i1.Fake implements _i4.OutputResponse {} +class _FakeOutputResponse_2 extends _i1.SmartFake + implements _i4.OutputResponse { + _FakeOutputResponse_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} /// A class which mocks [CodeClient]. /// @@ -50,7 +77,13 @@ class MockCodeClient extends _i1.Mock implements _i5.CodeClient { [request], ), returnValue: - Future<_i2.RunCodeResponse>.value(_FakeRunCodeResponse_0()), + _i6.Future<_i2.RunCodeResponse>.value(_FakeRunCodeResponse_0( + this, + Invocation.method( + #runCode, + [request], + ), + )), ) as _i6.Future<_i2.RunCodeResponse>); @override _i6.Future cancelExecution(String? pipelineUuid) => (super.noSuchMethod( @@ -58,8 +91,8 @@ class MockCodeClient extends _i1.Mock implements _i5.CodeClient { #cancelExecution, [pipelineUuid], ), - returnValue: Future.value(), - returnValueForMissingStub: Future.value(), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); @override _i6.Future<_i3.CheckStatusResponse> checkStatus(String? pipelineUuid) => @@ -68,8 +101,14 @@ class MockCodeClient extends _i1.Mock implements _i5.CodeClient { #checkStatus, [pipelineUuid], ), - returnValue: - Future<_i3.CheckStatusResponse>.value(_FakeCheckStatusResponse_1()), + returnValue: _i6.Future<_i3.CheckStatusResponse>.value( + _FakeCheckStatusResponse_1( + this, + Invocation.method( + #checkStatus, + [pipelineUuid], + ), + )), ) as _i6.Future<_i3.CheckStatusResponse>); @override _i6.Future<_i4.OutputResponse> getCompileOutput(String? pipelineUuid) => @@ -78,7 +117,13 @@ class MockCodeClient extends _i1.Mock implements _i5.CodeClient { #getCompileOutput, [pipelineUuid], ), - returnValue: Future<_i4.OutputResponse>.value(_FakeOutputResponse_2()), + returnValue: _i6.Future<_i4.OutputResponse>.value(_FakeOutputResponse_2( + this, + Invocation.method( + #getCompileOutput, + [pipelineUuid], + ), + )), ) as _i6.Future<_i4.OutputResponse>); @override _i6.Future<_i4.OutputResponse> getRunOutput(String? pipelineUuid) => @@ -87,7 +132,13 @@ class MockCodeClient extends _i1.Mock implements _i5.CodeClient { #getRunOutput, [pipelineUuid], ), - returnValue: Future<_i4.OutputResponse>.value(_FakeOutputResponse_2()), + returnValue: _i6.Future<_i4.OutputResponse>.value(_FakeOutputResponse_2( + this, + Invocation.method( + #getRunOutput, + [pipelineUuid], + ), + )), ) as _i6.Future<_i4.OutputResponse>); @override _i6.Future<_i4.OutputResponse> getLogOutput(String? pipelineUuid) => @@ -96,7 +147,13 @@ class MockCodeClient extends _i1.Mock implements _i5.CodeClient { #getLogOutput, [pipelineUuid], ), - returnValue: Future<_i4.OutputResponse>.value(_FakeOutputResponse_2()), + returnValue: _i6.Future<_i4.OutputResponse>.value(_FakeOutputResponse_2( + this, + Invocation.method( + #getLogOutput, + [pipelineUuid], + ), + )), ) as _i6.Future<_i4.OutputResponse>); @override _i6.Future<_i4.OutputResponse> getRunErrorOutput(String? pipelineUuid) => @@ -105,7 +162,13 @@ class MockCodeClient extends _i1.Mock implements _i5.CodeClient { #getRunErrorOutput, [pipelineUuid], ), - returnValue: Future<_i4.OutputResponse>.value(_FakeOutputResponse_2()), + returnValue: _i6.Future<_i4.OutputResponse>.value(_FakeOutputResponse_2( + this, + Invocation.method( + #getRunErrorOutput, + [pipelineUuid], + ), + )), ) as _i6.Future<_i4.OutputResponse>); @override _i6.Future<_i4.OutputResponse> getValidationErrorOutput( @@ -115,7 +178,13 @@ class MockCodeClient extends _i1.Mock implements _i5.CodeClient { #getValidationErrorOutput, [pipelineUuid], ), - returnValue: Future<_i4.OutputResponse>.value(_FakeOutputResponse_2()), + returnValue: _i6.Future<_i4.OutputResponse>.value(_FakeOutputResponse_2( + this, + Invocation.method( + #getValidationErrorOutput, + [pipelineUuid], + ), + )), ) as _i6.Future<_i4.OutputResponse>); @override _i6.Future<_i4.OutputResponse> getPreparationErrorOutput( @@ -125,7 +194,13 @@ class MockCodeClient extends _i1.Mock implements _i5.CodeClient { #getPreparationErrorOutput, [pipelineUuid], ), - returnValue: Future<_i4.OutputResponse>.value(_FakeOutputResponse_2()), + returnValue: _i6.Future<_i4.OutputResponse>.value(_FakeOutputResponse_2( + this, + Invocation.method( + #getPreparationErrorOutput, + [pipelineUuid], + ), + )), ) as _i6.Future<_i4.OutputResponse>); @override _i6.Future<_i4.OutputResponse> getGraphOutput(String? pipelineUuid) => @@ -134,6 +209,12 @@ class MockCodeClient extends _i1.Mock implements _i5.CodeClient { #getGraphOutput, [pipelineUuid], ), - returnValue: Future<_i4.OutputResponse>.value(_FakeOutputResponse_2()), + returnValue: _i6.Future<_i4.OutputResponse>.value(_FakeOutputResponse_2( + this, + Invocation.method( + #getGraphOutput, + [pipelineUuid], + ), + )), ) as _i6.Future<_i4.OutputResponse>); } diff --git a/playground/frontend/playground_components/test/src/repositories/example_repository_test.mocks.dart b/playground/frontend/playground_components/test/src/repositories/example_repository_test.mocks.dart index 30c1109f2bdb..ab944a04c75f 100644 --- a/playground/frontend/playground_components/test/src/repositories/example_repository_test.mocks.dart +++ b/playground/frontend/playground_components/test/src/repositories/example_repository_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.2.0 from annotations +// Mocks generated by Mockito 5.3.2 from annotations // in playground_components/test/src/repositories/example_repository_test.dart. // Do not manually edit this file. @@ -40,23 +40,73 @@ import 'package:playground_components/src/repositories/models/save_snippet_respo // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class -class _FakeGetPrecompiledObjectsResponse_0 extends _i1.Fake - implements _i2.GetPrecompiledObjectsResponse {} +class _FakeGetPrecompiledObjectsResponse_0 extends _i1.SmartFake + implements _i2.GetPrecompiledObjectsResponse { + _FakeGetPrecompiledObjectsResponse_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeGetPrecompiledObjectCodeResponse_1 extends _i1.Fake - implements _i3.GetPrecompiledObjectCodeResponse {} +class _FakeGetPrecompiledObjectCodeResponse_1 extends _i1.SmartFake + implements _i3.GetPrecompiledObjectCodeResponse { + _FakeGetPrecompiledObjectCodeResponse_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeGetPrecompiledObjectResponse_2 extends _i1.Fake - implements _i4.GetPrecompiledObjectResponse {} +class _FakeGetPrecompiledObjectResponse_2 extends _i1.SmartFake + implements _i4.GetPrecompiledObjectResponse { + _FakeGetPrecompiledObjectResponse_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeOutputResponse_3 extends _i1.Fake implements _i5.OutputResponse {} +class _FakeOutputResponse_3 extends _i1.SmartFake + implements _i5.OutputResponse { + _FakeOutputResponse_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeGetSnippetResponse_4 extends _i1.Fake - implements _i6.GetSnippetResponse {} +class _FakeGetSnippetResponse_4 extends _i1.SmartFake + implements _i6.GetSnippetResponse { + _FakeGetSnippetResponse_4( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeSaveSnippetResponse_5 extends _i1.Fake - implements _i7.SaveSnippetResponse {} +class _FakeSaveSnippetResponse_5 extends _i1.SmartFake + implements _i7.SaveSnippetResponse { + _FakeSaveSnippetResponse_5( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} /// A class which mocks [ExampleClient]. /// @@ -74,8 +124,14 @@ class MockExampleClient extends _i1.Mock implements _i8.ExampleClient { #getPrecompiledObjects, [request], ), - returnValue: Future<_i2.GetPrecompiledObjectsResponse>.value( - _FakeGetPrecompiledObjectsResponse_0()), + returnValue: _i9.Future<_i2.GetPrecompiledObjectsResponse>.value( + _FakeGetPrecompiledObjectsResponse_0( + this, + Invocation.method( + #getPrecompiledObjects, + [request], + ), + )), ) as _i9.Future<_i2.GetPrecompiledObjectsResponse>); @override _i9.Future<_i3.GetPrecompiledObjectCodeResponse> getPrecompiledObjectCode( @@ -85,8 +141,14 @@ class MockExampleClient extends _i1.Mock implements _i8.ExampleClient { #getPrecompiledObjectCode, [request], ), - returnValue: Future<_i3.GetPrecompiledObjectCodeResponse>.value( - _FakeGetPrecompiledObjectCodeResponse_1()), + returnValue: _i9.Future<_i3.GetPrecompiledObjectCodeResponse>.value( + _FakeGetPrecompiledObjectCodeResponse_1( + this, + Invocation.method( + #getPrecompiledObjectCode, + [request], + ), + )), ) as _i9.Future<_i3.GetPrecompiledObjectCodeResponse>); @override _i9.Future<_i4.GetPrecompiledObjectResponse> getDefaultPrecompiledObject( @@ -96,8 +158,14 @@ class MockExampleClient extends _i1.Mock implements _i8.ExampleClient { #getDefaultPrecompiledObject, [request], ), - returnValue: Future<_i4.GetPrecompiledObjectResponse>.value( - _FakeGetPrecompiledObjectResponse_2()), + returnValue: _i9.Future<_i4.GetPrecompiledObjectResponse>.value( + _FakeGetPrecompiledObjectResponse_2( + this, + Invocation.method( + #getDefaultPrecompiledObject, + [request], + ), + )), ) as _i9.Future<_i4.GetPrecompiledObjectResponse>); @override _i9.Future<_i4.GetPrecompiledObjectResponse> getPrecompiledObject( @@ -107,8 +175,14 @@ class MockExampleClient extends _i1.Mock implements _i8.ExampleClient { #getPrecompiledObject, [request], ), - returnValue: Future<_i4.GetPrecompiledObjectResponse>.value( - _FakeGetPrecompiledObjectResponse_2()), + returnValue: _i9.Future<_i4.GetPrecompiledObjectResponse>.value( + _FakeGetPrecompiledObjectResponse_2( + this, + Invocation.method( + #getPrecompiledObject, + [request], + ), + )), ) as _i9.Future<_i4.GetPrecompiledObjectResponse>); @override _i9.Future<_i5.OutputResponse> getPrecompiledObjectOutput( @@ -118,7 +192,13 @@ class MockExampleClient extends _i1.Mock implements _i8.ExampleClient { #getPrecompiledObjectOutput, [request], ), - returnValue: Future<_i5.OutputResponse>.value(_FakeOutputResponse_3()), + returnValue: _i9.Future<_i5.OutputResponse>.value(_FakeOutputResponse_3( + this, + Invocation.method( + #getPrecompiledObjectOutput, + [request], + ), + )), ) as _i9.Future<_i5.OutputResponse>); @override _i9.Future<_i5.OutputResponse> getPrecompiledObjectLogs( @@ -128,7 +208,13 @@ class MockExampleClient extends _i1.Mock implements _i8.ExampleClient { #getPrecompiledObjectLogs, [request], ), - returnValue: Future<_i5.OutputResponse>.value(_FakeOutputResponse_3()), + returnValue: _i9.Future<_i5.OutputResponse>.value(_FakeOutputResponse_3( + this, + Invocation.method( + #getPrecompiledObjectLogs, + [request], + ), + )), ) as _i9.Future<_i5.OutputResponse>); @override _i9.Future<_i5.OutputResponse> getPrecompiledObjectGraph( @@ -138,7 +224,13 @@ class MockExampleClient extends _i1.Mock implements _i8.ExampleClient { #getPrecompiledObjectGraph, [request], ), - returnValue: Future<_i5.OutputResponse>.value(_FakeOutputResponse_3()), + returnValue: _i9.Future<_i5.OutputResponse>.value(_FakeOutputResponse_3( + this, + Invocation.method( + #getPrecompiledObjectGraph, + [request], + ), + )), ) as _i9.Future<_i5.OutputResponse>); @override _i9.Future<_i6.GetSnippetResponse> getSnippet( @@ -149,7 +241,13 @@ class MockExampleClient extends _i1.Mock implements _i8.ExampleClient { [request], ), returnValue: - Future<_i6.GetSnippetResponse>.value(_FakeGetSnippetResponse_4()), + _i9.Future<_i6.GetSnippetResponse>.value(_FakeGetSnippetResponse_4( + this, + Invocation.method( + #getSnippet, + [request], + ), + )), ) as _i9.Future<_i6.GetSnippetResponse>); @override _i9.Future<_i7.SaveSnippetResponse> saveSnippet( @@ -159,7 +257,13 @@ class MockExampleClient extends _i1.Mock implements _i8.ExampleClient { #saveSnippet, [request], ), - returnValue: - Future<_i7.SaveSnippetResponse>.value(_FakeSaveSnippetResponse_5()), + returnValue: _i9.Future<_i7.SaveSnippetResponse>.value( + _FakeSaveSnippetResponse_5( + this, + Invocation.method( + #saveSnippet, + [request], + ), + )), ) as _i9.Future<_i7.SaveSnippetResponse>); } diff --git a/playground/frontend/playground_components_dev/pubspec.yaml b/playground/frontend/playground_components_dev/pubspec.yaml index 0630dce0633e..b71b0f0f74eb 100644 --- a/playground/frontend/playground_components_dev/pubspec.yaml +++ b/playground/frontend/playground_components_dev/pubspec.yaml @@ -21,8 +21,8 @@ version: 0.0.1 publish_to: none environment: - sdk: '>=2.18.1 <4.0.0' - flutter: '>=3.3.2' + sdk: '>=2.19.2 <4.0.0' + flutter: '>=3.7.3' dependencies: flutter: { sdk: flutter } diff --git a/playground/frontend/pubspec.lock b/playground/frontend/pubspec.lock index 792d53e859b8..520ffa67c85f 100644 --- a/playground/frontend/pubspec.lock +++ b/playground/frontend/pubspec.lock @@ -5,282 +5,386 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - url: "https://pub.dartlang.org" + sha256: "0c80aeab9bc807ab10022cd3b2f4cf2ecdf231949dc1ddd9442406a003f19201" + url: "https://pub.dev" source: hosted - version: "40.0.0" + version: "52.0.0" akvelon_flutter_issue_106664_workaround: dependency: "direct main" description: name: akvelon_flutter_issue_106664_workaround - url: "https://pub.dartlang.org" + sha256: "1b8e38a65afa6058bc365b5e1762786a1e7618f549f1ea6215151d8d3b346207" + url: "https://pub.dev" source: hosted version: "0.1.2" aligned_dialog: dependency: "direct main" description: name: aligned_dialog - url: "https://pub.dartlang.org" + sha256: c6ce4f82a5ab35dde2c48caa436eab4da9d6a4238802f67312c878394caf055d + url: "https://pub.dev" source: hosted version: "0.0.6" analyzer: dependency: transitive description: name: analyzer - url: "https://pub.dartlang.org" + sha256: cd8ee83568a77f3ae6b913a36093a1c9b1264e7cb7f834d9ddd2311dade9c1f4 + url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "5.4.0" app_state: dependency: "direct main" description: name: app_state - url: "https://pub.dartlang.org" + sha256: "4824dc181bb5ba35595af6a9a78c9c96e7e4c58ede164e64cce34c75fabf244a" + url: "https://pub.dev" source: hosted - version: "0.8.4" + version: "0.9.2" archive: dependency: transitive description: name: archive - url: "https://pub.dartlang.org" + sha256: "80e5141fafcb3361653ce308776cfd7d45e6e9fbb429e14eec571382c0c5fecb" + url: "https://pub.dev" source: hosted - version: "3.3.0" + version: "3.3.2" args: dependency: transitive description: name: args - url: "https://pub.dartlang.org" + sha256: "4cab82a83ffef80b262ddedf47a0a8e56ee6fbf7fe21e6e768b02792034dd440" + url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.0" async: dependency: transitive description: name: async - url: "https://pub.dartlang.org" + sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + url: "https://pub.dev" source: hosted - version: "2.9.0" + version: "2.10.0" autotrie: dependency: transitive description: name: autotrie - url: "https://pub.dartlang.org" + sha256: "55da6faefb53cfcb0abb2f2ca8636123fb40e35286bb57440d2cf467568188f8" + url: "https://pub.dev" source: hosted version: "2.0.0" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.dartlang.org" + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" build: dependency: transitive description: name: build - url: "https://pub.dartlang.org" + sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" + url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.3.1" build_config: dependency: transitive description: name: build_config - url: "https://pub.dartlang.org" + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.1.1" build_daemon: dependency: transitive description: name: build_daemon - url: "https://pub.dartlang.org" + sha256: "6bc5544ea6ce4428266e7ea680e945c68806c4aae2da0eb5e9ccf38df8d6acbf" + url: "https://pub.dev" source: hosted version: "3.1.0" build_resolvers: dependency: transitive description: name: build_resolvers - url: "https://pub.dartlang.org" + sha256: "7c35a3a7868626257d8aee47b51c26b9dba11eaddf3431117ed2744951416aab" + url: "https://pub.dev" source: hosted - version: "2.0.9" + version: "2.1.0" build_runner: dependency: "direct dev" description: name: build_runner - url: "https://pub.dartlang.org" + sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727 + url: "https://pub.dev" source: hosted - version: "2.1.11" + version: "2.3.3" build_runner_core: dependency: transitive description: name: build_runner_core - url: "https://pub.dartlang.org" + sha256: "14febe0f5bac5ae474117a36099b4de6f1dbc52df6c5e55534b3da9591bf4292" + url: "https://pub.dev" source: hosted - version: "7.2.3" + version: "7.2.7" built_collection: dependency: transitive description: name: built_collection - url: "https://pub.dartlang.org" + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" source: hosted version: "5.1.1" built_value: dependency: transitive description: name: built_value - url: "https://pub.dartlang.org" + sha256: "169565c8ad06adb760c3645bf71f00bff161b00002cace266cad42c5d22a7725" + url: "https://pub.dev" source: hosted - version: "8.3.2" + version: "8.4.3" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" + sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + url: "https://pub.dev" source: hosted version: "1.2.1" charcode: dependency: transitive description: name: charcode - url: "https://pub.dartlang.org" + sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 + url: "https://pub.dev" source: hosted version: "1.3.1" checked_yaml: dependency: transitive description: name: checked_yaml - url: "https://pub.dartlang.org" + sha256: "3d1505d91afa809d177efd4eed5bb0eb65805097a1463abdd2add076effae311" + url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" clock: dependency: transitive description: name: clock - url: "https://pub.dartlang.org" + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" source: hosted version: "1.1.1" code_builder: dependency: transitive description: name: code_builder - url: "https://pub.dartlang.org" + sha256: "0d43dd1288fd145de1ecc9a3948ad4a6d5a82f0a14c4fdd0892260787d975cbe" + url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "4.4.0" collection: dependency: "direct main" description: name: collection - url: "https://pub.dartlang.org" + sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" + connectivity_plus: + dependency: transitive + description: + name: connectivity_plus + sha256: "3f8fe4e504c2d33696dac671a54909743bc6a902a9bb0902306f7a2aed7e528e" + url: "https://pub.dev" + source: hosted + version: "2.3.9" + connectivity_plus_linux: + dependency: transitive + description: + name: connectivity_plus_linux + sha256: "3caf859d001f10407b8e48134c761483e4495ae38094ffcca97193f6c271f5e2" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + connectivity_plus_macos: + dependency: transitive + description: + name: connectivity_plus_macos + sha256: "488d2de1e47e1224ad486e501b20b088686ba1f4ee9c4420ecbc3b9824f0b920" + url: "https://pub.dev" + source: hosted + version: "1.2.6" + connectivity_plus_platform_interface: + dependency: transitive + description: + name: connectivity_plus_platform_interface + sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a + url: "https://pub.dev" + source: hosted + version: "1.2.4" + connectivity_plus_web: + dependency: transitive + description: + name: connectivity_plus_web + sha256: "81332be1b4baf8898fed17bb4fdef27abb7c6fd990bf98c54fd978478adf2f1a" + url: "https://pub.dev" + source: hosted + version: "1.2.5" + connectivity_plus_windows: + dependency: transitive + description: + name: connectivity_plus_windows + sha256: "535b0404b4d5605c4dd8453d67e5d6d2ea0dd36e3b477f50f31af51b0aeab9dd" + url: "https://pub.dev" + source: hosted + version: "1.2.2" convert: dependency: transitive description: name: convert - url: "https://pub.dartlang.org" + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.1.1" + coverage: + dependency: transitive + description: + name: coverage + sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097" + url: "https://pub.dev" + source: hosted + version: "1.6.3" crypto: dependency: transitive description: name: crypto - url: "https://pub.dartlang.org" + sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 + url: "https://pub.dev" source: hosted version: "3.0.2" csslib: dependency: transitive description: name: csslib - url: "https://pub.dartlang.org" + sha256: b36c7f7e24c0bdf1bf9a3da461c837d1de64b9f8beb190c9011d8c72a3dfd745 + url: "https://pub.dev" source: hosted version: "0.17.2" csv: dependency: transitive description: name: csv - url: "https://pub.dartlang.org" + sha256: "18aef53ab72181a0b5384562d18c8cbd57e941e24cb8e54eb41409d3d8abdc6d" + url: "https://pub.dev" source: hosted version: "5.0.1" dart_style: dependency: transitive description: name: dart_style - url: "https://pub.dartlang.org" + sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4" + url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.2.4" + dbus: + dependency: transitive + description: + name: dbus + sha256: "6f07cba3f7b3448d42d015bfd3d53fe12e5b36da2423f23838efc1d5fb31a263" + url: "https://pub.dev" + source: hosted + version: "0.7.8" easy_localization: dependency: "direct main" description: name: easy_localization - url: "https://pub.dartlang.org" + sha256: "6a2e99fa0bfe5765bf4c6ca9b137d5de2c75593007178c5e4cd2ae985f870080" + url: "https://pub.dev" source: hosted version: "3.0.1" easy_localization_ext: dependency: "direct main" description: name: easy_localization_ext - url: "https://pub.dartlang.org" + sha256: "7a5ff2595436141f2e4873b69576ff33034fe23fef252fa592b2575be3fba33a" + url: "https://pub.dev" source: hosted version: "0.1.1" easy_localization_loader: dependency: "direct main" description: name: easy_localization_loader - url: "https://pub.dartlang.org" + sha256: "89e70b2516123d639e8e68b8dd08fc07c8b83b0cfee17c642165e7e8807ebc86" + url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.1+1" easy_logger: dependency: transitive description: name: easy_logger - url: "https://pub.dartlang.org" + sha256: c764a6e024846f33405a2342caf91c62e357c24b02c04dbc712ef232bf30ffb7 + url: "https://pub.dev" source: hosted version: "0.0.2" enum_map: dependency: transitive description: name: enum_map - url: "https://pub.dartlang.org" + sha256: "0dfe18306d2e9b0e9d381f5e11aac4c8255d5f5eddc68b0ab037f7d00aa36126" + url: "https://pub.dev" source: hosted version: "0.2.1" equatable: dependency: "direct main" description: name: equatable - url: "https://pub.dartlang.org" + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" source: hosted version: "2.0.5" expansion_widget: dependency: "direct main" description: name: expansion_widget - url: "https://pub.dartlang.org" + sha256: c1ab07f475c6a5381e6f068de1d6a8e6e736a07a664787267503c1cd085844f7 + url: "https://pub.dev" source: hosted version: "0.0.3" fake_async: dependency: "direct dev" description: name: fake_async - url: "https://pub.dartlang.org" + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" source: hosted version: "1.3.1" ffi: dependency: transitive description: name: ffi - url: "https://pub.dartlang.org" + sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978 + url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.0.1" file: dependency: transitive description: name: file - url: "https://pub.dartlang.org" + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + url: "https://pub.dev" source: hosted - version: "6.1.2" + version: "6.1.4" fixnum: dependency: transitive description: name: fixnum - url: "https://pub.dartlang.org" + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -290,9 +394,10 @@ packages: dependency: "direct dev" description: name: flutter_code_editor - url: "https://pub.dartlang.org" + sha256: a5d46c1740709997bbf6c2d21168b54db2a521606cbec7b1a01b79339ad4359e + url: "https://pub.dev" source: hosted - version: "0.2.5" + version: "0.2.10" flutter_driver: dependency: transitive description: flutter @@ -302,21 +407,24 @@ packages: dependency: transitive description: name: flutter_highlight - url: "https://pub.dartlang.org" + sha256: "7b96333867aa07e122e245c033b8ad622e4e3a42a1a2372cbb098a2541d8782c" + url: "https://pub.dev" source: hosted version: "0.7.0" flutter_issue_108697_workaround: dependency: transitive description: name: flutter_issue_108697_workaround - url: "https://pub.dartlang.org" + sha256: "71401a9196e1e8ed7a6463b8ab8ea6bf8d8cc719783c4be309553d64d8c353ec" + url: "https://pub.dev" source: hosted version: "0.1.2" flutter_lints: dependency: "direct dev" description: name: flutter_lints - url: "https://pub.dartlang.org" + sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + url: "https://pub.dev" source: hosted version: "2.0.1" flutter_localizations: @@ -328,16 +436,18 @@ packages: dependency: transitive description: name: flutter_markdown - url: "https://pub.dartlang.org" + sha256: "7b25c10de1fea883f3c4f9b8389506b54053cd00807beab69fd65c8653a2711f" + url: "https://pub.dev" source: hosted - version: "0.6.12" + version: "0.6.14" flutter_svg: dependency: "direct main" description: name: flutter_svg - url: "https://pub.dartlang.org" + sha256: b9be7260c1fdbe0090a11d9d356fc2c88e14cf33407fc0c1829d76ab13808035 + url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "2.0.1" flutter_test: dependency: "direct dev" description: flutter @@ -352,16 +462,18 @@ packages: dependency: transitive description: name: fluttertoast - url: "https://pub.dartlang.org" + sha256: "774fa28b07f3a82c93596bc137be33189fec578ed3447a93a5a11c93435de394" + url: "https://pub.dev" source: hosted - version: "8.1.1" + version: "8.1.3" frontend_server_client: dependency: transitive description: name: frontend_server_client - url: "https://pub.dartlang.org" + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "3.2.0" fuchsia_remote_debug_protocol: dependency: transitive description: flutter @@ -371,93 +483,106 @@ packages: dependency: "direct main" description: name: get_it - url: "https://pub.dartlang.org" + sha256: "290fde3a86072e4b37dbb03c07bec6126f0ecc28dad403c12ffe2e5a2d751ab7" + url: "https://pub.dev" source: hosted version: "7.2.0" glob: dependency: transitive description: name: glob - url: "https://pub.dartlang.org" + sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c" + url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.1.1" google_fonts: dependency: "direct main" description: name: google_fonts - url: "https://pub.dartlang.org" + sha256: "927573f2e8a8d65c17931e21918ad0ab0666b1b636537de7c4932bdb487b190f" + url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "4.0.3" googleapis_auth: dependency: transitive description: name: googleapis_auth - url: "https://pub.dartlang.org" + sha256: "127b1bbd32170ab8312f503bd57f1d654d8e4039ddfbc63c027d3f7ade0eff74" + url: "https://pub.dev" source: hosted version: "1.3.1" graphs: dependency: transitive description: name: graphs - url: "https://pub.dartlang.org" + sha256: f9e130f3259f52d26f0cfc0e964513796dafed572fa52e45d2f8d6ca14db39b2 + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.0" grpc: dependency: transitive description: name: grpc - url: "https://pub.dartlang.org" + sha256: a73c16e4f6a4a819be892bb2c73cc1d0b00e36095f69b0738cc91a733e3d27ba + url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.1.0" highlight: dependency: "direct main" description: name: highlight - url: "https://pub.dartlang.org" + sha256: "5353a83ffe3e3eca7df0abfb72dcf3fa66cc56b953728e7113ad4ad88497cf21" + url: "https://pub.dev" source: hosted version: "0.7.0" hive: dependency: transitive description: name: hive - url: "https://pub.dartlang.org" + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" source: hosted version: "2.2.3" html: dependency: transitive description: name: html - url: "https://pub.dartlang.org" + sha256: d9793e10dbe0e6c364f4c59bf3e01fb33a9b2a674bc7a1081693dba0614b6269 + url: "https://pub.dev" source: hosted - version: "0.15.0" + version: "0.15.1" http: dependency: transitive description: name: http - url: "https://pub.dartlang.org" + sha256: "6aa2946395183537c8b880962d935877325d6a09a2867c3970c05c0fed6ac482" + url: "https://pub.dev" source: hosted version: "0.13.5" http2: dependency: transitive description: name: http2 - url: "https://pub.dartlang.org" + sha256: "58805ebc6513eed3b98ee0a455a8357e61d187bf2e0fdc1e53120770f78de258" + url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.0.1" http_multi_server: dependency: transitive description: name: http_multi_server - url: "https://pub.dartlang.org" + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.1" http_parser: dependency: transitive description: name: http_parser - url: "https://pub.dartlang.org" + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" source: hosted - version: "4.0.1" + version: "4.0.2" integration_test: dependency: "direct dev" description: flutter @@ -467,210 +592,248 @@ packages: dependency: "direct main" description: name: intl - url: "https://pub.dartlang.org" + sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91" + url: "https://pub.dev" source: hosted version: "0.17.0" io: dependency: transitive description: name: io - url: "https://pub.dartlang.org" + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.4" js: dependency: transitive description: name: js - url: "https://pub.dartlang.org" + sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + url: "https://pub.dev" source: hosted - version: "0.6.4" + version: "0.6.5" json_annotation: dependency: transitive description: name: json_annotation - url: "https://pub.dartlang.org" + sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317 + url: "https://pub.dev" source: hosted - version: "4.7.0" + version: "4.8.0" keyed_collection_widgets: dependency: transitive description: name: keyed_collection_widgets - url: "https://pub.dartlang.org" + sha256: "9db2df4c4897c35fe167bdca82d307d81baa4161c3118da3f06ab4fd2d75291b" + url: "https://pub.dev" source: hosted version: "0.4.3" linked_scroll_controller: dependency: transitive description: name: linked_scroll_controller - url: "https://pub.dartlang.org" + sha256: e6020062bcf4ffc907ee7fd090fa971e65d8dfaac3c62baf601a3ced0b37986a + url: "https://pub.dev" source: hosted version: "0.2.0" lints: dependency: transitive description: name: lints - url: "https://pub.dartlang.org" + sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" + url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.0.1" logging: dependency: transitive description: name: logging - url: "https://pub.dartlang.org" + sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d" + url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.1.1" markdown: dependency: transitive description: name: markdown - url: "https://pub.dartlang.org" + sha256: "4ed544d2ce84975b2ab5cbd4268f2d31f47858553ae2295c92fdf5d6e431a927" + url: "https://pub.dev" source: hosted - version: "6.0.1" + version: "7.0.0" matcher: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + url: "https://pub.dev" source: hosted - version: "0.12.12" + version: "0.12.13" material_color_utilities: dependency: transitive description: name: material_color_utilities - url: "https://pub.dartlang.org" + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + url: "https://pub.dev" source: hosted - version: "0.1.5" + version: "0.2.0" meta: dependency: transitive description: name: meta - url: "https://pub.dartlang.org" + sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + url: "https://pub.dev" source: hosted version: "1.8.0" mime: dependency: transitive description: name: mime - url: "https://pub.dartlang.org" + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.0.4" mockito: dependency: "direct dev" description: name: mockito - url: "https://pub.dartlang.org" + sha256: "2a8a17b82b1bde04d514e75d90d634a0ac23f6cb4991f6098009dd56836aeafe" + url: "https://pub.dev" source: hosted - version: "5.2.0" + version: "5.3.2" + mocktail: + dependency: transitive + description: + name: mocktail + sha256: "80a996cd9a69284b3dc521ce185ffe9150cde69767c2d3a0720147d93c0cef53" + url: "https://pub.dev" + source: hosted + version: "0.3.0" nested: dependency: transitive description: name: nested - url: "https://pub.dartlang.org" + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" source: hosted version: "1.0.0" + nm: + dependency: transitive + description: + name: nm + sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "8ebdbaa3b96d5285d068f80772390d27c21e1fa10fb2df6627b1b9415043608d" + url: "https://pub.dev" + source: hosted + version: "2.0.1" onmessage: dependency: "direct main" description: name: onmessage - url: "https://pub.dartlang.org" + sha256: "262fee2df9a3cf361446ad743cb556ebf0552c2ae5c1220a041a781346f8a1a9" + url: "https://pub.dev" source: hosted version: "0.2.0" os_detect: dependency: transitive description: name: os_detect - url: "https://pub.dartlang.org" + sha256: faf3bcf39515e64da8ff76b2f2805b20a6ff47ae515393e535f8579ff91d6b7f + url: "https://pub.dev" source: hosted version: "2.0.1" package_config: dependency: transitive description: name: package_config - url: "https://pub.dartlang.org" + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.1.0" path: dependency: transitive description: name: path - url: "https://pub.dartlang.org" + sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + url: "https://pub.dev" source: hosted version: "1.8.2" - path_drawing: - dependency: transitive - description: - name: path_drawing - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" path_parsing: dependency: transitive description: name: path_parsing - url: "https://pub.dartlang.org" + sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf + url: "https://pub.dev" source: hosted version: "1.0.1" path_provider: dependency: transitive description: name: path_provider - url: "https://pub.dartlang.org" + sha256: dcea5feb97d8abf90cab9e9030b497fb7c3cbf26b7a1fe9e3ef7dcb0a1ddec95 + url: "https://pub.dev" source: hosted - version: "2.0.10" + version: "2.0.12" path_provider_android: dependency: transitive description: name: path_provider_android - url: "https://pub.dartlang.org" + sha256: a776c088d671b27f6e3aa8881d64b87b3e80201c64e8869b811325de7a76c15e + url: "https://pub.dev" source: hosted - version: "2.0.14" - path_provider_ios: + version: "2.0.22" + path_provider_foundation: dependency: transitive description: - name: path_provider_ios - url: "https://pub.dartlang.org" + name: path_provider_foundation + sha256: "62a68e7e1c6c459f9289859e2fae58290c981ce21d1697faf54910fe1faa4c74" + url: "https://pub.dev" source: hosted - version: "2.0.9" + version: "2.1.1" path_provider_linux: dependency: transitive description: name: path_provider_linux - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.7" - path_provider_macos: - dependency: transitive - description: - name: path_provider_macos - url: "https://pub.dartlang.org" + sha256: "2e32f1640f07caef0d3cb993680f181c79e54a3827b997d5ee221490d131fbd9" + url: "https://pub.dev" source: hosted - version: "2.0.6" + version: "2.1.8" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - url: "https://pub.dartlang.org" + sha256: f0abc8ebd7253741f05488b4813d936b4d07c6bae3e86148a09e342ee4b08e76 + url: "https://pub.dev" source: hosted - version: "2.0.4" + version: "2.0.5" path_provider_windows: dependency: transitive description: name: path_provider_windows - url: "https://pub.dartlang.org" + sha256: bcabbe399d4042b8ee687e17548d5d3f527255253b4a639f5f8d2094a9c2b45c + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.3" petitparser: dependency: transitive description: name: petitparser - url: "https://pub.dartlang.org" + sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4" + url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "5.1.0" platform: dependency: transitive description: name: platform - url: "https://pub.dartlang.org" + sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + url: "https://pub.dev" source: hosted version: "3.1.0" playground_components: @@ -691,135 +854,162 @@ packages: dependency: transitive description: name: plugin_platform_interface - url: "https://pub.dartlang.org" + sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" pool: dependency: transitive description: name: pool - url: "https://pub.dartlang.org" + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "1.5.1" process: dependency: transitive description: name: process - url: "https://pub.dartlang.org" + sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + url: "https://pub.dev" source: hosted version: "4.2.4" protobuf: dependency: transitive description: name: protobuf - url: "https://pub.dartlang.org" + sha256: "01dd9bd0fa02548bf2ceee13545d4a0ec6046459d847b6b061d8a27237108a08" + url: "https://pub.dev" source: hosted version: "2.1.0" provider: dependency: "direct main" description: name: provider - url: "https://pub.dartlang.org" + sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f + url: "https://pub.dev" source: hosted - version: "6.0.3" + version: "6.0.5" pub_semver: dependency: transitive description: name: pub_semver - url: "https://pub.dartlang.org" + sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17" + url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.3" pubspec_parse: dependency: transitive description: name: pubspec_parse - url: "https://pub.dartlang.org" + sha256: "75f6614d6dde2dc68948dffbaa4fe5dae32cd700eb9fb763fe11dfb45a3c4d0a" + url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" rxdart: dependency: transitive description: name: rxdart - url: "https://pub.dartlang.org" + sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + url: "https://pub.dev" source: hosted version: "0.27.7" scrollable_positioned_list: dependency: transitive description: name: scrollable_positioned_list - url: "https://pub.dartlang.org" + sha256: ca7fcaa743db712d4f7b1580526f494d0093c77a721a65705ee51fbeac7a2bd3 + url: "https://pub.dev" source: hosted version: "0.3.5" shared_preferences: dependency: "direct main" description: name: shared_preferences - url: "https://pub.dartlang.org" + sha256: "5949029e70abe87f75cfe59d17bf5c397619c4b74a099b10116baeb34786fad9" + url: "https://pub.dev" source: hosted - version: "2.0.15" + version: "2.0.17" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - url: "https://pub.dartlang.org" + sha256: "955e9736a12ba776bdd261cf030232b30eadfcd9c79b32a3250dd4a494e8c8f7" + url: "https://pub.dev" source: hosted - version: "2.0.12" - shared_preferences_ios: + version: "2.0.15" + shared_preferences_foundation: dependency: transitive description: - name: shared_preferences_ios - url: "https://pub.dartlang.org" + name: shared_preferences_foundation + sha256: "2b55c18636a4edc529fa5cd44c03d3f3100c00513f518c5127c951978efcccd0" + url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.3" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - url: "https://pub.dartlang.org" + sha256: f8ea038aa6da37090093974ebdcf4397010605fd2ff65c37a66f9d28394cb874 + url: "https://pub.dev" source: hosted - version: "2.1.1" - shared_preferences_macos: - dependency: transitive - description: - name: shared_preferences_macos - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.4" + version: "2.1.3" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - url: "https://pub.dartlang.org" + sha256: da9431745ede5ece47bc26d5d73a9d3c6936ef6945c101a5aca46f62e52c1cf3 + url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.1.0" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - url: "https://pub.dartlang.org" + sha256: a4b5bc37fe1b368bbc81f953197d55e12f49d0296e7e412dfe2d2d77d6929958 + url: "https://pub.dev" source: hosted version: "2.0.4" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - url: "https://pub.dartlang.org" + sha256: "5eaf05ae77658d3521d0e993ede1af962d4b326cd2153d312df716dc250f00c9" + url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.3" shelf: dependency: transitive description: name: shelf - url: "https://pub.dartlang.org" + sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c + url: "https://pub.dev" + source: hosted + version: "1.4.0" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: aef74dc9195746a384843102142ab65b6a4735bb3beea791e63527b88cc83306 + url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "3.0.1" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: e792b76b96a36d4a41b819da593aff4bdd413576b3ba6150df5d8d9996d2e74c + url: "https://pub.dev" + source: hosted + version: "1.1.1" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - url: "https://pub.dartlang.org" + sha256: a988c0e8d8ffbdb8a28aa7ec8e449c260f3deb808781fe1284d22c5bba7156e8 + url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.3" sky_engine: dependency: transitive description: flutter @@ -829,247 +1019,346 @@ packages: dependency: transitive description: name: source_gen - url: "https://pub.dartlang.org" + sha256: c2bea18c95cfa0276a366270afaa2850b09b4a76db95d546f3d003dcc7011298 + url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.2.7" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "490098075234dcedb83c5d949b4c93dad5e6b7702748de000be2b57b8e6b2427" + url: "https://pub.dev" + source: hosted + version: "0.10.11" source_span: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.dartlang.org" + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.0" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.dartlang.org" + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" stream_transform: dependency: transitive description: name: stream_transform - url: "https://pub.dartlang.org" + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.1.0" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" sync_http: dependency: transitive description: name: sync_http - url: "https://pub.dartlang.org" + sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" + url: "https://pub.dev" source: hosted version: "0.3.1" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" source: hosted version: "1.2.1" + test: + dependency: transitive + description: + name: test + sha256: a5fcd2d25eeadbb6589e80198a47d6a464ba3e2049da473943b8af9797900c2d + url: "https://pub.dev" + source: hosted + version: "1.22.0" test_api: dependency: transitive description: name: test_api - url: "https://pub.dartlang.org" + sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + url: "https://pub.dev" source: hosted - version: "0.4.12" + version: "0.4.16" + test_core: + dependency: transitive + description: + name: test_core + sha256: "0ef9755ec6d746951ba0aabe62f874b707690b5ede0fecc818b138fcc9b14888" + url: "https://pub.dev" + source: hosted + version: "0.4.20" timing: dependency: transitive description: name: timing - url: "https://pub.dartlang.org" + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.1" total_lints: dependency: transitive description: name: total_lints - url: "https://pub.dartlang.org" + sha256: "5424a55034e89a9c6198518356842dfdc33b6f0b4d557071f84d6095e5a9f8ea" + url: "https://pub.dev" source: hosted - version: "2.18.0" + version: "2.19.0" tuple: dependency: transitive description: name: tuple - url: "https://pub.dartlang.org" + sha256: "0ea99cd2f9352b2586583ab2ce6489d1f95a5f6de6fb9492faaf97ae2060f0aa" + url: "https://pub.dev" source: hosted version: "2.0.1" typed_data: dependency: transitive description: name: typed_data - url: "https://pub.dartlang.org" + sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + url: "https://pub.dev" source: hosted version: "1.3.1" universal_html: dependency: transitive description: name: universal_html - url: "https://pub.dartlang.org" + sha256: "5ff50b7c14d201421cf5230ec389a0591c4deb5c817c9d7ccca3b26fe5f31e34" + url: "https://pub.dev" source: hosted version: "2.0.8" universal_io: dependency: transitive description: name: universal_io - url: "https://pub.dartlang.org" + sha256: "79f78ddad839ee3aae3ec7c01eb4575faf0d5c860f8e5223bc9f9c17f7f03cef" + url: "https://pub.dev" source: hosted version: "2.0.4" url_launcher: dependency: "direct main" description: name: url_launcher - url: "https://pub.dartlang.org" + sha256: e8f2efc804810c0f2f5b485f49e7942179f56eabcfe81dce3387fec4bb55876b + url: "https://pub.dev" source: hosted - version: "6.1.2" + version: "6.1.9" url_launcher_android: dependency: transitive description: name: url_launcher_android - url: "https://pub.dartlang.org" + sha256: "3e2f6dfd2c7d9cd123296cab8ef66cfc2c1a13f5845f42c7a0f365690a8a7dd1" + url: "https://pub.dev" source: hosted - version: "6.0.17" + version: "6.0.23" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - url: "https://pub.dartlang.org" + sha256: "0a5af0aefdd8cf820dd739886efb1637f1f24489900204f50984634c07a54815" + url: "https://pub.dev" source: hosted - version: "6.0.17" + version: "6.1.0" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - url: "https://pub.dartlang.org" + sha256: "318c42cba924e18180c029be69caf0a1a710191b9ec49bb42b5998fdcccee3cc" + url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - url: "https://pub.dartlang.org" + sha256: "41988b55570df53b3dd2a7fc90c76756a963de6a8c5f8e113330cb35992e2094" + url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - url: "https://pub.dartlang.org" + sha256: "4eae912628763eb48fc214522e58e942fd16ce195407dbf45638239523c759a6" + url: "https://pub.dev" source: hosted - version: "2.0.5" + version: "2.1.1" url_launcher_web: dependency: transitive description: name: url_launcher_web - url: "https://pub.dartlang.org" + sha256: "44d79408ce9f07052095ef1f9a693c258d6373dc3944249374e30eff7219ccb0" + url: "https://pub.dev" source: hosted - version: "2.0.11" + version: "2.0.14" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - url: "https://pub.dartlang.org" + sha256: b6217370f8eb1fd85c8890c539f5a639a01ab209a36db82c921ebeacefc7a615 + url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.3" url_strategy: dependency: "direct main" description: name: url_strategy - url: "https://pub.dartlang.org" + sha256: "42b68b42a9864c4d710401add17ad06e28f1c1d5500c93b98c431f6b0ea4ab87" + url: "https://pub.dev" source: hosted version: "0.2.0" usage: dependency: "direct main" description: name: usage - url: "https://pub.dartlang.org" + sha256: ccc861ca619157046d961a1d7fa21a364476c574bb8f3e90219e41b9103adee0 + url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.0" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: "09562ef5f47aa84f6567495adb6b9cb2a3192b82c352623b8bd00b300d62603b" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "886e57742644ebed024dc3ade29712e37eea1b03d294fb314c0a3386243fe5a6" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: "5d9010c4a292766c55395b2288532579a85673f8148460d1e233d98ffe10d24e" + url: "https://pub.dev" + source: hosted + version: "1.0.1" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" vm_service: dependency: transitive description: name: vm_service - url: "https://pub.dartlang.org" + sha256: e7fb6c2282f7631712b69c19d1bff82f3767eea33a2321c14fa59ad67ea391c7 + url: "https://pub.dev" source: hosted - version: "9.0.0" + version: "9.4.0" watcher: dependency: transitive description: name: watcher - url: "https://pub.dartlang.org" + sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" + url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.2" web_browser_detect: dependency: transitive description: name: web_browser_detect - url: "https://pub.dartlang.org" + sha256: "78ba66860b61a993030788a3a4586fb21cb7d9cef966cfd24faa9ad487c3fd8b" + url: "https://pub.dev" source: hosted version: "2.0.3" web_socket_channel: dependency: transitive description: name: web_socket_channel - url: "https://pub.dartlang.org" + sha256: ca49c0bc209c687b887f30527fb6a9d80040b072cc2990f34b9bec3e7663101b + url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.0" webdriver: dependency: transitive description: name: webdriver - url: "https://pub.dartlang.org" + sha256: ef67178f0cc7e32c1494645b11639dd1335f1d18814aa8435113a92e9ef9d841 + url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.0.1" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "67d3a8b6c79e1987d19d848b0892e582dbb0c66c57cc1fef58a177dd2aa2823d" + url: "https://pub.dev" + source: hosted + version: "1.2.0" win32: dependency: transitive description: name: win32 - url: "https://pub.dartlang.org" + sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46 + url: "https://pub.dev" source: hosted - version: "2.7.0" + version: "3.1.3" xdg_directories: dependency: transitive description: name: xdg_directories - url: "https://pub.dartlang.org" + sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1 + url: "https://pub.dev" source: hosted - version: "0.2.0+1" + version: "1.0.0" xml: dependency: transitive description: name: xml - url: "https://pub.dartlang.org" + sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5" + url: "https://pub.dev" source: hosted - version: "5.4.1" + version: "6.2.2" yaml: dependency: transitive description: name: yaml - url: "https://pub.dartlang.org" + sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370" + url: "https://pub.dev" source: hosted version: "3.1.1" sdks: - dart: ">=2.18.1 <3.0.0" - flutter: ">=3.3.2" + dart: ">=2.19.2 <3.0.0" + flutter: ">=3.7.3" diff --git a/playground/frontend/pubspec.yaml b/playground/frontend/pubspec.yaml index 3c65a5aab129..a01f8fb182b4 100644 --- a/playground/frontend/pubspec.yaml +++ b/playground/frontend/pubspec.yaml @@ -21,13 +21,13 @@ publish_to: 'none' version: 1.0.0+1 environment: - sdk: ">=2.18.1 <3.0.0" - flutter: ">=3.3.2" + sdk: ">=2.19.2 <4.0.0" + flutter: ">=3.7.3" dependencies: akvelon_flutter_issue_106664_workaround: ^0.1.2 aligned_dialog: ^0.0.6 - app_state: ^0.8.4 + app_state: ^0.9.2 collection: ^1.15.0 easy_localization: ^3.0.1 easy_localization_ext: ^0.1.1 @@ -36,10 +36,10 @@ dependencies: expansion_widget: ^0.0.2 flutter: { sdk: flutter } flutter_localizations: { sdk: flutter } - flutter_svg: ^1.0.3 + flutter_svg: ^2.0.1 flutter_web_plugins: { sdk: flutter } get_it: ^7.2.0 - google_fonts: ^3.0.1 + google_fonts: ^4.0.3 highlight: ^0.7.0 intl: ^0.17.0 onmessage: ^0.2.0 diff --git a/playground/frontend/test/pages/playground/states/example_selector_state_test.mocks.dart b/playground/frontend/test/pages/playground/states/example_selector_state_test.mocks.dart index 2117f7fccb4e..d341e3c0566c 100644 --- a/playground/frontend/test/pages/playground/states/example_selector_state_test.mocks.dart +++ b/playground/frontend/test/pages/playground/states/example_selector_state_test.mocks.dart @@ -1,7 +1,8 @@ -// Mocks generated by Mockito 5.2.0 from annotations +// Mocks generated by Mockito 5.3.2 from annotations // in playground/test/pages/playground/states/example_selector_state_test.dart. // Do not manually edit this file. +// ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i5; import 'package:mockito/mockito.dart' as _i1; @@ -24,9 +25,18 @@ import 'package:playground_components/src/models/sdk.dart' as _i7; // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class -class _FakeExampleLoaderFactory_0 extends _i1.Fake - implements _i2.ExampleLoaderFactory {} +class _FakeExampleLoaderFactory_0 extends _i1.SmartFake + implements _i2.ExampleLoaderFactory { + _FakeExampleLoaderFactory_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} /// A class which mocks [ExamplesLoader]. /// @@ -38,20 +48,38 @@ class MockExamplesLoader extends _i1.Mock implements _i3.ExamplesLoader { @override _i2.ExampleLoaderFactory get defaultFactory => (super.noSuchMethod( - Invocation.getter(#defaultFactory), - returnValue: _FakeExampleLoaderFactory_0()) as _i2.ExampleLoaderFactory); + Invocation.getter(#defaultFactory), + returnValue: _FakeExampleLoaderFactory_0( + this, + Invocation.getter(#defaultFactory), + ), + ) as _i2.ExampleLoaderFactory); @override void setPlaygroundController(_i4.PlaygroundController? value) => - super.noSuchMethod(Invocation.method(#setPlaygroundController, [value]), - returnValueForMissingStub: null); + super.noSuchMethod( + Invocation.method( + #setPlaygroundController, + [value], + ), + returnValueForMissingStub: null, + ); @override _i5.Future load(_i6.ExamplesLoadingDescriptor? descriptor) => - (super.noSuchMethod(Invocation.method(#load, [descriptor]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + (super.noSuchMethod( + Invocation.method( + #load, + [descriptor], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); @override - _i5.Future loadDefaultIfAny(_i7.Sdk? sdk) => - (super.noSuchMethod(Invocation.method(#loadDefaultIfAny, [sdk]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + _i5.Future loadDefaultIfAny(_i7.Sdk? sdk) => (super.noSuchMethod( + Invocation.method( + #loadDefaultIfAny, + [sdk], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); } From a90fbbf2318581cad3f79044dabc5e74a342794a Mon Sep 17 00:00:00 2001 From: Alexey Inkin Date: Tue, 14 Feb 2023 10:58:18 +0400 Subject: [PATCH 31/52] Fix integration test (#24730) --- .../standalone_change_example_sdk_run_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playground/frontend/integration_test/standalone_change_example_sdk_run_test.dart b/playground/frontend/integration_test/standalone_change_example_sdk_run_test.dart index f7b601c877ea..c0ffdf52135c 100644 --- a/playground/frontend/integration_test/standalone_change_example_sdk_run_test.dart +++ b/playground/frontend/integration_test/standalone_change_example_sdk_run_test.dart @@ -26,7 +26,7 @@ import 'package:playground_components_dev/playground_components_dev.dart'; import 'common/common.dart'; import 'common/common_finders.dart'; -const _outputPrefix = 'The processing has started\n'; +const _outputPrefix = 'The processing has been started\n'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); From 916a96627b099dfb5073c9859e083930c1b1ddee Mon Sep 17 00:00:00 2001 From: malarg Date: Tue, 14 Feb 2023 13:25:01 +0400 Subject: [PATCH 32/52] fix cors issue and added mouse scroll to tags --- .../examples/components/filter/filter.dart | 24 +++++--- .../components/web_scroll_converter.dart | 55 +++++++++++++++++++ .../assets/translations/en.yaml | 7 +++ .../lib/src/controllers/code_runner.dart | 15 +++-- .../controllers/playground_controller.dart | 11 +--- .../code_client/grpc_code_client.dart | 10 ++-- .../repositories/models/run_code_result.dart | 12 ++-- .../lib/src/util/connectivity_result.dart | 7 +-- .../playground_components/pubspec.yaml | 2 +- .../playground_controller_test.dart | 2 +- 10 files changed, 104 insertions(+), 41 deletions(-) create mode 100644 playground/frontend/lib/modules/examples/components/web_scroll_converter.dart diff --git a/playground/frontend/lib/modules/examples/components/filter/filter.dart b/playground/frontend/lib/modules/examples/components/filter/filter.dart index 8907550922d7..200eff034e04 100644 --- a/playground/frontend/lib/modules/examples/components/filter/filter.dart +++ b/playground/frontend/lib/modules/examples/components/filter/filter.dart @@ -24,6 +24,7 @@ import 'package:provider/provider.dart'; import '../../../../constants/sizes.dart'; import '../../../../pages/standalone_playground/notifiers/example_selector_state.dart'; import '../examples_components.dart'; +import '../web_scroll_converter.dart'; class ExamplesFilter extends StatelessWidget { const ExamplesFilter({super.key}); @@ -37,8 +38,8 @@ class ExamplesFilter extends StatelessWidget { ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, - children: const [ - _Types(), + children: [ + const _Types(), _Tags(), ], ), @@ -77,19 +78,24 @@ class _Types extends StatelessWidget { } class _Tags extends StatelessWidget { - const _Tags(); + final ScrollController scrollController = ScrollController(); + _Tags(); @override Widget build(BuildContext context) { return Consumer( builder: (context, state, child) => Padding( padding: const EdgeInsets.symmetric(vertical: kMdSpacing), - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: state.tags - .map((tag) => TagBubble(name: tag)) - .toList(growable: false), + child: WebScrollConverterWidget( + scrollController: scrollController, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + controller: scrollController, + child: Wrap( + children: state.tags + .map((tag) => TagBubble(name: tag)) + .toList(growable: false), + ), ), ), ), diff --git a/playground/frontend/lib/modules/examples/components/web_scroll_converter.dart b/playground/frontend/lib/modules/examples/components/web_scroll_converter.dart new file mode 100644 index 000000000000..50dd5c78de9d --- /dev/null +++ b/playground/frontend/lib/modules/examples/components/web_scroll_converter.dart @@ -0,0 +1,55 @@ +/* + * 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/gestures.dart'; +import 'package:flutter/widgets.dart'; + +/// For web, listens for mouse scroll events and adds dy +/// to the given controller. +/// +/// Use this to scroll horizontal lists that would otherwise not scroll +/// because mouse wheel only scrolls vertically. +/// +/// This widget appears to has no effect on swipe scrolling on mobile platforms. +class WebScrollConverterWidget extends StatelessWidget { + final ScrollController scrollController; + final Widget child; + + const WebScrollConverterWidget({ + required this.scrollController, + required this.child, + }); + + @override + Widget build(BuildContext context) { + return Listener( + onPointerSignal: (pointerSignal) { + if (pointerSignal is PointerScrollEvent) { + final delta = pointerSignal.scrollDelta.dy; + final overscroll = scrollController.position.animateTo( + scrollController.position.pixels + delta, + duration: const Duration(milliseconds: 100), + curve: Curves.linear, + ); + print(overscroll); + } + }, + child: child, + ); + } +} diff --git a/playground/frontend/playground_components/assets/translations/en.yaml b/playground/frontend/playground_components/assets/translations/en.yaml index 3c6b720c6941..283150c678ad 100644 --- a/playground/frontend/playground_components/assets/translations/en.yaml +++ b/playground/frontend/playground_components/assets/translations/en.yaml @@ -17,12 +17,16 @@ errors: error: 'Error' + failedParseOptions: "Failed to parse pipeline options, please check the format (example: --key1 value1 --key2 value2), only alphanumeric and \",*,/,-,:,;,',. symbols are allowed" + internetUnavailable: 'Internet connection is unavailable' loading: 'Error while loading.' loadingCatalog: 'Cannot load the example catalog.' loadingExample: 'Cannot load the example.' savingSnippet: 'Cannot create a shareable link.' + unknownError: 'Unknown error. Potential internet connection problem or CORS issue' examples: + defaultTitle: 'Catalog' userSharedName: 'User Shared Code' ui: @@ -46,6 +50,9 @@ widgets: output: 'Output' filterTitle: 'Display at this tab' graph: 'Graph' + messages: + cachedResult: 'The results of this example are taken from the Apache Beam Playground cache' + pipelineCancelled: 'Pipeline cancelled' result: 'Result' resetButton: diff --git a/playground/frontend/playground_components/lib/src/controllers/code_runner.dart b/playground/frontend/playground_components/lib/src/controllers/code_runner.dart index c9a2e17936d5..ce87b5435e85 100644 --- a/playground/frontend/playground_components/lib/src/controllers/code_runner.dart +++ b/playground/frontend/playground_components/lib/src/controllers/code_runner.dart @@ -19,6 +19,7 @@ import 'dart:async'; import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import '../../playground_components.dart'; @@ -83,9 +84,9 @@ class CodeRunner extends ChangeNotifier { final parsedPipelineOptions = parsePipelineOptions(snippetEditingController!.pipelineOptions); if (parsedPipelineOptions == null) { - _result = const RunCodeResult( + _result = RunCodeResult( status: RunCodeStatus.compileError, - errorMessage: kPipelineOptionsParseError, + errorMessage: 'errors.failedParseOptions'.tr(), ); _runStopDate = DateTime.now(); notifyListeners(); @@ -140,7 +141,7 @@ class CodeRunner extends ChangeNotifier { status: _result?.status ?? RunCodeStatus.unspecified, output: _result?.output, log: _result?.log ?? '', - errorMessage: kInternetConnectionUnavailable, + errorMessage: 'errors.internetUnavailable'.tr(), graph: _result?.graph, ); notifyListeners(); @@ -158,7 +159,10 @@ class CodeRunner extends ChangeNotifier { _result = RunCodeResult( status: RunCodeStatus.finished, output: _result?.output, - log: (_result?.log ?? '') + kExecutionCancelledText, + // ignore: prefer_interpolation_to_compose_strings + log: (_result?.log ?? '') + + '\n' + + 'widgets.output.messages.pipelineCancelled'.tr(), graph: _result?.graph, ); @@ -180,7 +184,8 @@ class CodeRunner extends ChangeNotifier { _result = RunCodeResult( status: RunCodeStatus.finished, output: selectedExample.outputs, - log: kCachedResultsLog + logs, + // ignore: prefer_interpolation_to_compose_strings + log: 'widgets.output.messages.cachedResult'.tr() + '\n$logs', graph: selectedExample.graph, ); diff --git a/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart index e7e73b9c2b7a..6024189a9a12 100644 --- a/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart +++ b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart @@ -19,6 +19,7 @@ import 'dart:async'; import 'dart:math'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get_it/get_it.dart'; @@ -45,13 +46,6 @@ import 'snippet_editing_controller.dart'; const kTitleLength = 25; const kExecutionTimeUpdate = 100; const kPrecompiledDelay = Duration(seconds: 1); -const kTitle = 'Catalog'; -const kExecutionCancelledText = '\nPipeline cancelled'; -const kPipelineOptionsParseError = - 'Failed to parse pipeline options, please check the format (example: --key1 value1 --key2 value2), only alphanumeric and ",*,/,-,:,;,\',. symbols are allowed'; -const kInternetConnectionUnavailable = 'Internet connection unavailable'; -const kCachedResultsLog = - 'The results of this example are taken from the Apache Beam Playground cache.\n'; /// The main state object for the code and its running. class PlaygroundController with ChangeNotifier { @@ -102,7 +96,8 @@ class PlaygroundController with ChangeNotifier { // TODO(alexeyinkin): Return full, then shorten, https://github.com/apache/beam/issues/23250 String get examplesTitle { - final name = snippetEditingController?.example?.name ?? kTitle; + final name = + snippetEditingController?.example?.name ?? 'examples.defaultTitle'.tr(); return name.substring(0, min(kTitleLength, name.length)); } diff --git a/playground/frontend/playground_components/lib/src/repositories/code_client/grpc_code_client.dart b/playground/frontend/playground_components/lib/src/repositories/code_client/grpc_code_client.dart index c8163608f3a0..4eb2158d5b76 100644 --- a/playground/frontend/playground_components/lib/src/repositories/code_client/grpc_code_client.dart +++ b/playground/frontend/playground_components/lib/src/repositories/code_client/grpc_code_client.dart @@ -16,11 +16,13 @@ * limitations under the License. */ +import 'package:easy_localization/easy_localization.dart'; import 'package:grpc/grpc.dart'; -import '../../../playground_components.dart'; import '../../api/iis_workaround_channel.dart'; import '../../api/v1/api.pbgrpc.dart' as grpc; +import '../../models/sdk.dart'; +import '../../util/pipeline_options.dart'; import '../dataset_grpc_extension.dart'; import '../models/check_status_response.dart'; import '../models/output_response.dart'; @@ -194,10 +196,8 @@ class GrpcCodeClient implements CodeClient { try { return await invoke(); } on GrpcError catch (error) { - //TODO(Malarg): check if internet connection available here using connectivity_plus - //throw internet unavailable exception, if not - if (error.message?.contains('CORS') ?? false) { - throw const RunCodeError(message: kInternetConnectionUnavailable); + if (error.code == StatusCode.unknown) { + throw RunCodeError(message: 'errors.unknownError'.tr()); } throw RunCodeError(message: error.message); } on Exception catch (_) { diff --git a/playground/frontend/playground_components/lib/src/repositories/models/run_code_result.dart b/playground/frontend/playground_components/lib/src/repositories/models/run_code_result.dart index dacfa1fa59ee..771a287f5fe5 100644 --- a/playground/frontend/playground_components/lib/src/repositories/models/run_code_result.dart +++ b/playground/frontend/playground_components/lib/src/repositories/models/run_code_result.dart @@ -19,17 +19,17 @@ import 'package:equatable/equatable.dart'; enum RunCodeStatus { - compileError, - compiling, - executing, - finished, + unspecified, preparation, preparationError, + validationError, + compiling, + compileError, + executing, runError, + finished, timeout, unknownError, - unspecified, - validationError, } const kFinishedStatuses = [ diff --git a/playground/frontend/playground_components/lib/src/util/connectivity_result.dart b/playground/frontend/playground_components/lib/src/util/connectivity_result.dart index e602cbe9d1ae..188f911f78aa 100644 --- a/playground/frontend/playground_components/lib/src/util/connectivity_result.dart +++ b/playground/frontend/playground_components/lib/src/util/connectivity_result.dart @@ -19,10 +19,5 @@ import 'package:connectivity_plus/connectivity_plus.dart'; extension ConnectivityResultExtension on ConnectivityResult { - bool get isConnected => [ - ConnectivityResult.ethernet, - ConnectivityResult.mobile, - ConnectivityResult.vpn, - ConnectivityResult.wifi, - ].contains(this); + bool get isConnected => this != ConnectivityResult.none; } diff --git a/playground/frontend/playground_components/pubspec.yaml b/playground/frontend/playground_components/pubspec.yaml index 63eeb9624bd5..a8488bea79d4 100644 --- a/playground/frontend/playground_components/pubspec.yaml +++ b/playground/frontend/playground_components/pubspec.yaml @@ -41,7 +41,7 @@ dependencies: fluttertoast: ^8.1.1 get_it: ^7.2.0 google_fonts: ^3.0.1 - grpc: ^3.0.2 + grpc: 3.1.0 highlight: ^0.7.0 http: ^0.13.5 json_annotation: ^4.7.0 diff --git a/playground/frontend/playground_components/test/src/controllers/playground_controller_test.dart b/playground/frontend/playground_components/test/src/controllers/playground_controller_test.dart index 628b1cc9443a..b6075a5ea4d4 100644 --- a/playground/frontend/playground_components/test/src/controllers/playground_controller_test.dart +++ b/playground/frontend/playground_components/test/src/controllers/playground_controller_test.dart @@ -50,7 +50,7 @@ Future main() async { }); test('Initial value of examplesTitle should be equal to kTitle', () { - expect(controller.examplesTitle, kTitle); + expect(controller.examplesTitle, 'Catalog'); }); test('Initial value of isCodeRunning should be false', () { From cd83c1fa909b3d8bf003005a252c258cfa37fad7 Mon Sep 17 00:00:00 2001 From: Alexey Inkin Date: Tue, 14 Feb 2023 14:43:27 +0400 Subject: [PATCH 33/52] Upgrade Flutter in Dockerfile (#24720) --- playground/frontend/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/playground/frontend/Dockerfile b/playground/frontend/Dockerfile index fc4c034a3c80..7800159255ec 100644 --- a/playground/frontend/Dockerfile +++ b/playground/frontend/Dockerfile @@ -17,7 +17,7 @@ ############################################################################### FROM debian:11 as build -ARG FLUTTER_VERSION=3.3.2 +ARG FLUTTER_VERSION=3.7.3 # ------------------------------------------------ # Setting up. @@ -28,6 +28,7 @@ RUN apt-get update && apt-get install -y wget xz-utils git RUN wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_$FLUTTER_VERSION-stable.tar.xz &&\ tar -xf flutter_linux_$FLUTTER_VERSION-stable.tar.xz &&\ mv flutter /opt/ &&\ + git config --global --add safe.directory /opt/flutter &&\ ln -s /opt/flutter/bin/flutter /usr/bin/flutter &&\ ln -s /opt/flutter/bin/dart /usr/bin/dart &&\ dart pub global activate protoc_plugin &&\ From ffa95e7f2a093f38e26eac07f514ad2afde241e8 Mon Sep 17 00:00:00 2001 From: malarg Date: Wed, 15 Feb 2023 10:06:56 +0400 Subject: [PATCH 34/52] sorting moved to model --- .../examples/components/filter/filter.dart | 39 ++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/playground/frontend/lib/modules/examples/components/filter/filter.dart b/playground/frontend/lib/modules/examples/components/filter/filter.dart index 200eff034e04..f5a31011093f 100644 --- a/playground/frontend/lib/modules/examples/components/filter/filter.dart +++ b/playground/frontend/lib/modules/examples/components/filter/filter.dart @@ -84,21 +84,34 @@ class _Tags extends StatelessWidget { @override Widget build(BuildContext context) { return Consumer( - builder: (context, state, child) => Padding( - padding: const EdgeInsets.symmetric(vertical: kMdSpacing), - child: WebScrollConverterWidget( - scrollController: scrollController, - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - controller: scrollController, - child: Wrap( - children: state.tags - .map((tag) => TagBubble(name: tag)) - .toList(growable: false), + builder: (context, state, child) { + state.tags.sort((a, b) { + if (state.selectedTags.contains(a) && + !state.selectedTags.contains(b)) { + return -1; + } else if (!state.selectedTags.contains(a) && + state.selectedTags.contains(b)) { + return 1; + } else { + return 0; + } + }); + return Padding( + padding: const EdgeInsets.symmetric(vertical: kMdSpacing), + child: WebScrollConverterWidget( + scrollController: scrollController, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + controller: scrollController, + child: Wrap( + children: state.tags + .map((tag) => TagBubble(name: tag)) + .toList(growable: false), + ), ), ), - ), - ), + ); + }, ); } } From eb337950c864def2ccf5bbfca3ac4fd16fe24c29 Mon Sep 17 00:00:00 2001 From: malarg Date: Wed, 15 Feb 2023 10:07:46 +0400 Subject: [PATCH 35/52] sorting moved to model --- .../notifiers/example_selector_state.dart | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/playground/frontend/lib/pages/standalone_playground/notifiers/example_selector_state.dart b/playground/frontend/lib/pages/standalone_playground/notifiers/example_selector_state.dart index 7ce9064e1b9f..dde09e0d55e1 100644 --- a/playground/frontend/lib/pages/standalone_playground/notifiers/example_selector_state.dart +++ b/playground/frontend/lib/pages/standalone_playground/notifiers/example_selector_state.dart @@ -47,14 +47,28 @@ class ExampleSelectorState with ChangeNotifier { void addSelectedTag(String tag) { selectedTags.add(tag); + _sortTagsBySelected(); notifyListeners(); } void removeSelectedTag(String tag) { selectedTags.remove(tag); + _sortTagsBySelected(); notifyListeners(); } + void _sortTagsBySelected() { + tags.sort((a, b) { + if (selectedTags.contains(a) && !selectedTags.contains(b)) { + return -1; + } else if (!selectedTags.contains(a) && selectedTags.contains(b)) { + return 1; + } else { + return 0; + } + }); + } + List _getTagsSortedByExampleCount( List categories, ) { From 399a80ae1adb8b647306adcbd224547cfafddf51 Mon Sep 17 00:00:00 2001 From: malarg Date: Wed, 15 Feb 2023 10:09:04 +0400 Subject: [PATCH 36/52] sorting moved to model --- .../modules/examples/components/filter/filter.dart | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/playground/frontend/lib/modules/examples/components/filter/filter.dart b/playground/frontend/lib/modules/examples/components/filter/filter.dart index f5a31011093f..d47a2221fba5 100644 --- a/playground/frontend/lib/modules/examples/components/filter/filter.dart +++ b/playground/frontend/lib/modules/examples/components/filter/filter.dart @@ -85,17 +85,6 @@ class _Tags extends StatelessWidget { Widget build(BuildContext context) { return Consumer( builder: (context, state, child) { - state.tags.sort((a, b) { - if (state.selectedTags.contains(a) && - !state.selectedTags.contains(b)) { - return -1; - } else if (!state.selectedTags.contains(a) && - state.selectedTags.contains(b)) { - return 1; - } else { - return 0; - } - }); return Padding( padding: const EdgeInsets.symmetric(vertical: kMdSpacing), child: WebScrollConverterWidget( From 6080a4b184fcbd742797d6d3c206450ecf0b937f Mon Sep 17 00:00:00 2001 From: malarg Date: Thu, 16 Feb 2023 15:56:20 +0400 Subject: [PATCH 37/52] bugs fix --- .../example_list/example_item_actions.dart | 13 +++-- .../examples/components/filter/filter.dart | 6 +-- .../components/web_scroll_converter.dart | 9 ++-- .../notifiers/example_selector_state.dart | 53 ++++++++++++++----- .../pages/standalone_playground/screen.dart | 5 ++ .../assets/translations/en.yaml | 2 +- .../lib/src/controllers/code_runner.dart | 2 + .../repositories/models/run_code_result.dart | 8 +-- .../lib/src/widgets/complexity.dart | 13 +++-- 9 files changed, 76 insertions(+), 35 deletions(-) diff --git a/playground/frontend/lib/modules/examples/components/example_list/example_item_actions.dart b/playground/frontend/lib/modules/examples/components/example_list/example_item_actions.dart index 03f9d2732d4a..746f5d0d29d9 100644 --- a/playground/frontend/lib/modules/examples/components/example_list/example_item_actions.dart +++ b/playground/frontend/lib/modules/examples/components/example_list/example_item_actions.dart @@ -67,11 +67,14 @@ class _EmulatedDataIcon extends StatelessWidget { @override Widget build(BuildContext context) { - return Tooltip( - message: 'intents.playground.usesEmulatedData'.tr(), - child: SvgPicture.asset( - Assets.streaming, - color: Theme.of(context).extension()?.iconColor, + return GestureDetector( + onTap: () {}, + child: Tooltip( + message: 'intents.playground.usesEmulatedData'.tr(), + child: SvgPicture.asset( + Assets.streaming, + color: Theme.of(context).extension()?.iconColor, + ), ), ); } diff --git a/playground/frontend/lib/modules/examples/components/filter/filter.dart b/playground/frontend/lib/modules/examples/components/filter/filter.dart index d47a2221fba5..fdf54b460450 100644 --- a/playground/frontend/lib/modules/examples/components/filter/filter.dart +++ b/playground/frontend/lib/modules/examples/components/filter/filter.dart @@ -78,7 +78,7 @@ class _Types extends StatelessWidget { } class _Tags extends StatelessWidget { - final ScrollController scrollController = ScrollController(); + final ScrollController _scrollController = ScrollController(); _Tags(); @override @@ -88,10 +88,10 @@ class _Tags extends StatelessWidget { return Padding( padding: const EdgeInsets.symmetric(vertical: kMdSpacing), child: WebScrollConverterWidget( - scrollController: scrollController, + scrollController: _scrollController, child: SingleChildScrollView( scrollDirection: Axis.horizontal, - controller: scrollController, + controller: _scrollController, child: Wrap( children: state.tags .map((tag) => TagBubble(name: tag)) diff --git a/playground/frontend/lib/modules/examples/components/web_scroll_converter.dart b/playground/frontend/lib/modules/examples/components/web_scroll_converter.dart index 50dd5c78de9d..a64261db1edf 100644 --- a/playground/frontend/lib/modules/examples/components/web_scroll_converter.dart +++ b/playground/frontend/lib/modules/examples/components/web_scroll_converter.dart @@ -24,15 +24,13 @@ import 'package:flutter/widgets.dart'; /// /// Use this to scroll horizontal lists that would otherwise not scroll /// because mouse wheel only scrolls vertically. -/// -/// This widget appears to has no effect on swipe scrolling on mobile platforms. class WebScrollConverterWidget extends StatelessWidget { - final ScrollController scrollController; final Widget child; + final ScrollController scrollController; const WebScrollConverterWidget({ - required this.scrollController, required this.child, + required this.scrollController, }); @override @@ -41,12 +39,11 @@ class WebScrollConverterWidget extends StatelessWidget { onPointerSignal: (pointerSignal) { if (pointerSignal is PointerScrollEvent) { final delta = pointerSignal.scrollDelta.dy; - final overscroll = scrollController.position.animateTo( + scrollController.position.animateTo( scrollController.position.pixels + delta, duration: const Duration(milliseconds: 100), curve: Curves.linear, ); - print(overscroll); } }, child: child, diff --git a/playground/frontend/lib/pages/standalone_playground/notifiers/example_selector_state.dart b/playground/frontend/lib/pages/standalone_playground/notifiers/example_selector_state.dart index dde09e0d55e1..f2e447e9fdc5 100644 --- a/playground/frontend/lib/pages/standalone_playground/notifiers/example_selector_state.dart +++ b/playground/frontend/lib/pages/standalone_playground/notifiers/example_selector_state.dart @@ -25,6 +25,7 @@ class ExampleSelectorState with ChangeNotifier { String _searchText; List categories; List tags = []; + late final Map tagsFrequencyMap; List selectedTags = []; ExampleSelectorState( @@ -34,6 +35,7 @@ class ExampleSelectorState with ChangeNotifier { this._searchText = '', ]) { tags = _getTagsSortedByExampleCount(categories); + tagsFrequencyMap = _buildTagsFrequencyMap(_getAllCategories().toList()); } ExampleType get selectedFilterType => _selectedFilterType; @@ -64,7 +66,15 @@ class ExampleSelectorState with ChangeNotifier { } else if (!selectedTags.contains(a) && selectedTags.contains(b)) { return 1; } else { - return 0; + final aFreq = tagsFrequencyMap[a] ?? -1; + final bFreq = tagsFrequencyMap[b] ?? -1; + if (aFreq > bFreq) { + return -1; + } else if (aFreq < bFreq) { + return 1; + } else { + return a.compareTo(b); + } } }); } @@ -72,17 +82,28 @@ class ExampleSelectorState with ChangeNotifier { List _getTagsSortedByExampleCount( List categories, ) { - Map exampleCountByTag = {}; + Map tagsFrequencyMap = _buildTagsFrequencyMap(categories); + final tagEntries = tagsFrequencyMap.entries.toList() + ..sort( + (entry1, entry2) => entry2.value.compareTo(entry1.value) != 0 + ? entry2.value.compareTo(entry1.value) + : entry2.key.compareTo(entry1.key), + ); + return tagEntries.map((entry) => entry.key).toList(); + } + + Map _buildTagsFrequencyMap( + List categories, + ) { + Map result = {}; for (final category in categories) { for (final example in category.examples) { for (final tag in example.tags) { - exampleCountByTag[tag] = (exampleCountByTag[tag] ?? 0) + 1; + result[tag] = (result[tag] ?? 0) + 1; } } } - final tagEntries = exampleCountByTag.entries.toList() - ..sort((entry1, entry2) => entry2.value.compareTo(entry1.value)); - return tagEntries.map((entry) => entry.key).toList(); + return result; } void setSearchText(String text) { @@ -96,18 +117,24 @@ class ExampleSelectorState with ChangeNotifier { } void filterCategoriesWithExamples() { - final categories = _playgroundController.exampleCache.getCategories( - _playgroundController.sdk, - ); - final filteredCategories = categories - .map((category) => CategoryWithExamples( - title: category.title, - examples: _filterExamples(category.examples))) + final filteredCategories = _getAllCategories() .where((category) => category.examples.isNotEmpty) .toList(); setCategories(filteredCategories); } + Iterable _getAllCategories() { + final categories = _playgroundController.exampleCache.getCategories( + _playgroundController.sdk, + ); + return categories.map( + (category) => CategoryWithExamples( + title: category.title, + examples: _filterExamples(category.examples), + ), + ); + } + List _filterExamples(List examples) { final byType = filterExamplesByType(examples, selectedFilterType); final byTags = filterExamplesByTags(byType); diff --git a/playground/frontend/lib/pages/standalone_playground/screen.dart b/playground/frontend/lib/pages/standalone_playground/screen.dart index 1aace1bfb940..b6ddff4e36f1 100644 --- a/playground/frontend/lib/pages/standalone_playground/screen.dart +++ b/playground/frontend/lib/pages/standalone_playground/screen.dart @@ -37,6 +37,7 @@ import 'widgets/playground_page_footer.dart'; import 'widgets/playground_page_providers.dart'; class StandalonePlaygroundScreen extends StatelessWidget { + static const kAppBarButtonsWidth = 1105; final StandalonePlaygroundNotifier notifier; const StandalonePlaygroundScreen(this.notifier); @@ -56,6 +57,10 @@ class StandalonePlaygroundScreen extends StatelessWidget { return Scaffold( appBar: AppBar( + toolbarHeight: + MediaQuery.of(context).size.width > kAppBarButtonsWidth + ? kToolbarHeight + : 2 * kToolbarHeight, automaticallyImplyLeading: false, title: Wrap( crossAxisAlignment: WrapCrossAlignment.center, diff --git a/playground/frontend/playground_components/assets/translations/en.yaml b/playground/frontend/playground_components/assets/translations/en.yaml index 283150c678ad..74565ff62753 100644 --- a/playground/frontend/playground_components/assets/translations/en.yaml +++ b/playground/frontend/playground_components/assets/translations/en.yaml @@ -23,7 +23,7 @@ errors: loadingCatalog: 'Cannot load the example catalog.' loadingExample: 'Cannot load the example.' savingSnippet: 'Cannot create a shareable link.' - unknownError: 'Unknown error. Potential internet connection problem or CORS issue' + unknownError: 'Unknown error. Possibly internet connection problem or CORS issue' examples: defaultTitle: 'Catalog' diff --git a/playground/frontend/playground_components/lib/src/controllers/code_runner.dart b/playground/frontend/playground_components/lib/src/controllers/code_runner.dart index ce87b5435e85..b7d30d48f8ba 100644 --- a/playground/frontend/playground_components/lib/src/controllers/code_runner.dart +++ b/playground/frontend/playground_components/lib/src/controllers/code_runner.dart @@ -149,6 +149,8 @@ class CodeRunner extends ChangeNotifier { } snippetEditingController = null; + // Awaited cancelling subscription here blocks futrher method execution. + // TODO: Figure out the reason: https://github.com/apache/beam/issues/25509 unawaited(_runSubscription?.cancel()); final pipelineUuid = _result?.pipelineUuid ?? ''; diff --git a/playground/frontend/playground_components/lib/src/repositories/models/run_code_result.dart b/playground/frontend/playground_components/lib/src/repositories/models/run_code_result.dart index 771a287f5fe5..700db498750e 100644 --- a/playground/frontend/playground_components/lib/src/repositories/models/run_code_result.dart +++ b/playground/frontend/playground_components/lib/src/repositories/models/run_code_result.dart @@ -33,13 +33,13 @@ enum RunCodeStatus { } const kFinishedStatuses = [ + RunCodeStatus.unknownError, + RunCodeStatus.timeout, RunCodeStatus.compileError, - RunCodeStatus.finished, - RunCodeStatus.preparationError, RunCodeStatus.runError, - RunCodeStatus.timeout, - RunCodeStatus.unknownError, RunCodeStatus.validationError, + RunCodeStatus.preparationError, + RunCodeStatus.finished, ]; class RunCodeResult with EquatableMixin { diff --git a/playground/frontend/playground_components/lib/src/widgets/complexity.dart b/playground/frontend/playground_components/lib/src/widgets/complexity.dart index ce8c7e087cfd..93eaa758537e 100644 --- a/playground/frontend/playground_components/lib/src/widgets/complexity.dart +++ b/playground/frontend/playground_components/lib/src/widgets/complexity.dart @@ -29,9 +29,16 @@ class ComplexityWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return Row( - mainAxisSize: MainAxisSize.min, - children: _dots[complexity]!, + return GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () {}, + child: SizedBox.square( + dimension: 24, + child: Row( + mainAxisSize: MainAxisSize.min, + children: _dots[complexity]!, + ), + ), ); } From b7b14b4baa920a0f70ba9fa354d41cdb428a47c7 Mon Sep 17 00:00:00 2001 From: malarg Date: Thu, 16 Feb 2023 19:15:32 +0400 Subject: [PATCH 38/52] issue 25278 --- .../lib/src/repositories/code_repository.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/playground/frontend/playground_components/lib/src/repositories/code_repository.dart b/playground/frontend/playground_components/lib/src/repositories/code_repository.dart index 94872888f1c2..855511e4cafd 100644 --- a/playground/frontend/playground_components/lib/src/repositories/code_repository.dart +++ b/playground/frontend/playground_components/lib/src/repositories/code_repository.dart @@ -47,10 +47,11 @@ class CodeRepository { try { final log = request.pipelineOptions.isEmpty ? kProcessingStartedText + // ignore: prefer_interpolation_to_compose_strings : kProcessingStartedOptionsText + request.pipelineOptions.entries .map((e) => '--${e.key} ${e.value}') - .join(' '); + .join(' ') + '\n'; final initResult = RunCodeResult( status: RunCodeStatus.preparation, log: log, From 2ca302e1cbc2b9ac5d7c43d6de87ec96962d6bb2 Mon Sep 17 00:00:00 2001 From: malarg Date: Fri, 17 Feb 2023 11:37:21 +0400 Subject: [PATCH 39/52] fix pr --- .../notifiers/example_selector_state.dart | 42 ++++++++----------- .../pages/standalone_playground/screen.dart | 4 ++ .../assets/translations/en.yaml | 2 +- .../lib/src/controllers/code_runner.dart | 2 +- .../code_client/grpc_code_client.dart | 2 + .../states/example_selector_state_test.dart | 18 ++++++-- 6 files changed, 40 insertions(+), 30 deletions(-) diff --git a/playground/frontend/lib/pages/standalone_playground/notifiers/example_selector_state.dart b/playground/frontend/lib/pages/standalone_playground/notifiers/example_selector_state.dart index f2e447e9fdc5..9be2b4797452 100644 --- a/playground/frontend/lib/pages/standalone_playground/notifiers/example_selector_state.dart +++ b/playground/frontend/lib/pages/standalone_playground/notifiers/example_selector_state.dart @@ -34,8 +34,8 @@ class ExampleSelectorState with ChangeNotifier { this._selectedFilterType = ExampleType.all, this._searchText = '', ]) { + tagsFrequencyMap = _buildTagsFrequencyMap(categories); tags = _getTagsSortedByExampleCount(categories); - tagsFrequencyMap = _buildTagsFrequencyMap(_getAllCategories().toList()); } ExampleType get selectedFilterType => _selectedFilterType; @@ -49,53 +49,47 @@ class ExampleSelectorState with ChangeNotifier { void addSelectedTag(String tag) { selectedTags.add(tag); - _sortTagsBySelected(); + tags.sort(_compareTags); notifyListeners(); } void removeSelectedTag(String tag) { selectedTags.remove(tag); - _sortTagsBySelected(); + tags.sort(_compareTags); notifyListeners(); } - void _sortTagsBySelected() { - tags.sort((a, b) { - if (selectedTags.contains(a) && !selectedTags.contains(b)) { + /// First selected, then most frequent, alphabetically for equal frequency. + int _compareTags(String a, String b) { + if (selectedTags.contains(a) && !selectedTags.contains(b)) { + return -1; + } else if (!selectedTags.contains(a) && selectedTags.contains(b)) { + return 1; + } else { + final aFreq = tagsFrequencyMap[a] ?? -1; + final bFreq = tagsFrequencyMap[b] ?? -1; + if (aFreq > bFreq) { return -1; - } else if (!selectedTags.contains(a) && selectedTags.contains(b)) { + } else if (aFreq < bFreq) { return 1; } else { - final aFreq = tagsFrequencyMap[a] ?? -1; - final bFreq = tagsFrequencyMap[b] ?? -1; - if (aFreq > bFreq) { - return -1; - } else if (aFreq < bFreq) { - return 1; - } else { - return a.compareTo(b); - } + return a.compareTo(b); } - }); + } } List _getTagsSortedByExampleCount( List categories, ) { - Map tagsFrequencyMap = _buildTagsFrequencyMap(categories); final tagEntries = tagsFrequencyMap.entries.toList() - ..sort( - (entry1, entry2) => entry2.value.compareTo(entry1.value) != 0 - ? entry2.value.compareTo(entry1.value) - : entry2.key.compareTo(entry1.key), - ); + ..sort((entry1, entry2) => _compareTags(entry1.key, entry2.key)); return tagEntries.map((entry) => entry.key).toList(); } Map _buildTagsFrequencyMap( List categories, ) { - Map result = {}; + final result = {}; for (final category in categories) { for (final example in category.examples) { for (final tag in example.tags) { diff --git a/playground/frontend/lib/pages/standalone_playground/screen.dart b/playground/frontend/lib/pages/standalone_playground/screen.dart index b6ddff4e36f1..cf68286c903f 100644 --- a/playground/frontend/lib/pages/standalone_playground/screen.dart +++ b/playground/frontend/lib/pages/standalone_playground/screen.dart @@ -37,6 +37,10 @@ import 'widgets/playground_page_footer.dart'; import 'widgets/playground_page_providers.dart'; class StandalonePlaygroundScreen extends StatelessWidget { + // TODO: calculate sum of widths of all app bar buttons at the first frame. + // https://github.com/apache/beam/issues/25524 + // To get value manually print window width and check when app bar buttons + // will span 2 lines static const kAppBarButtonsWidth = 1105; final StandalonePlaygroundNotifier notifier; diff --git a/playground/frontend/playground_components/assets/translations/en.yaml b/playground/frontend/playground_components/assets/translations/en.yaml index 74565ff62753..5f193ac17e6c 100644 --- a/playground/frontend/playground_components/assets/translations/en.yaml +++ b/playground/frontend/playground_components/assets/translations/en.yaml @@ -17,7 +17,7 @@ errors: error: 'Error' - failedParseOptions: "Failed to parse pipeline options, please check the format (example: --key1 value1 --key2 value2), only alphanumeric and \",*,/,-,:,;,',. symbols are allowed" + failedParseOptions: 'Failed to parse pipeline options, please check the format (example: --key1 value1 --key2 value2), only alphanumeric and \",*,/,-,:,;,'',. symbols are allowed' internetUnavailable: 'Internet connection is unavailable' loading: 'Error while loading.' loadingCatalog: 'Cannot load the example catalog.' diff --git a/playground/frontend/playground_components/lib/src/controllers/code_runner.dart b/playground/frontend/playground_components/lib/src/controllers/code_runner.dart index b7d30d48f8ba..c6e65e06b8d3 100644 --- a/playground/frontend/playground_components/lib/src/controllers/code_runner.dart +++ b/playground/frontend/playground_components/lib/src/controllers/code_runner.dart @@ -149,7 +149,7 @@ class CodeRunner extends ChangeNotifier { } snippetEditingController = null; - // Awaited cancelling subscription here blocks futrher method execution. + // Awaited cancelling subscription here blocks further method execution. // TODO: Figure out the reason: https://github.com/apache/beam/issues/25509 unawaited(_runSubscription?.cancel()); final pipelineUuid = _result?.pipelineUuid ?? ''; diff --git a/playground/frontend/playground_components/lib/src/repositories/code_client/grpc_code_client.dart b/playground/frontend/playground_components/lib/src/repositories/code_client/grpc_code_client.dart index 4eb2158d5b76..6e461d30b446 100644 --- a/playground/frontend/playground_components/lib/src/repositories/code_client/grpc_code_client.dart +++ b/playground/frontend/playground_components/lib/src/repositories/code_client/grpc_code_client.dart @@ -196,6 +196,8 @@ class GrpcCodeClient implements CodeClient { try { return await invoke(); } on GrpcError catch (error) { + // Internet unavailable issue also returns unknown code error, + // so message was overwritten. if (error.code == StatusCode.unknown) { throw RunCodeError(message: 'errors.unknownError'.tr()); } 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 index a58a7feff5bc..009e5eb86911 100644 --- a/playground/frontend/test/pages/playground/states/example_selector_state_test.dart +++ b/playground/frontend/test/pages/playground/states/example_selector_state_test.dart @@ -109,7 +109,7 @@ void main() { '- notify all listeners,' 'but should NOT:' '- affect Example state categories', () { - final state = ExampleSelectorState( + state = ExampleSelectorState( playgroundController, categoriesMock, ); @@ -126,7 +126,7 @@ void main() { '- notify all listeners,' 'but should NOT:' '- affect Example state categories', () { - final state = ExampleSelectorState( + state = ExampleSelectorState( playgroundController, categoriesMock, ); @@ -145,7 +145,7 @@ void main() { '- wait for full name of example,' '- be sensitive for register,' '- affect Example state categories', () { - final state = ExampleSelectorState( + state = ExampleSelectorState( playgroundController, categoriesMock, ); @@ -157,11 +157,21 @@ void main() { }); test('ExampleSelectorState sorts tags by example count', () { - final state = ExampleSelectorState( + state = ExampleSelectorState( playgroundController, categoriesMock, ); const popularTag = 'tag2'; expect(state.tags.first == popularTag, true); }); + + test('ExampleSelectorState sorts first selected tags ', () { + state = ExampleSelectorState( + playgroundController, + categoriesMock, + ); + const selectedTag = 'tag1'; + state.addSelectedTag(selectedTag); + expect(state.tags.first == selectedTag, true); + }); } From 781de4f0d8b82144683ccd02a6dbe9714023cc83 Mon Sep 17 00:00:00 2001 From: malarg Date: Fri, 17 Feb 2023 14:56:36 +0400 Subject: [PATCH 40/52] quites fix in en.yaml --- .../frontend/playground_components/assets/translations/en.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/playground/frontend/playground_components/assets/translations/en.yaml b/playground/frontend/playground_components/assets/translations/en.yaml index 5f193ac17e6c..441570837ff8 100644 --- a/playground/frontend/playground_components/assets/translations/en.yaml +++ b/playground/frontend/playground_components/assets/translations/en.yaml @@ -17,7 +17,8 @@ errors: error: 'Error' - failedParseOptions: 'Failed to parse pipeline options, please check the format (example: --key1 value1 --key2 value2), only alphanumeric and \",*,/,-,:,;,'',. symbols are allowed' + failedParseOptions: > + Failed to parse pipeline options, please check the format (example: --key1 value1 --key2 value2), only alphanumeric and ",*,/,-,:,;,',. symbols are allowed internetUnavailable: 'Internet connection is unavailable' loading: 'Error while loading.' loadingCatalog: 'Cannot load the example catalog.' From 694e22ea394c065cf8e02c81fe82004f77482541 Mon Sep 17 00:00:00 2001 From: Alexey Inkin Date: Fri, 17 Feb 2023 16:59:20 +0400 Subject: [PATCH 41/52] Fix not loading default example (#25528) --- .../lib/src/cache/example_cache.dart | 3 -- .../example_loaders/examples_loader.dart | 32 +++++++++++++------ 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/playground/frontend/playground_components/lib/src/cache/example_cache.dart b/playground/frontend/playground_components/lib/src/cache/example_cache.dart index 19a362952de8..68bdaaf01df6 100644 --- a/playground/frontend/playground_components/lib/src/cache/example_cache.dart +++ b/playground/frontend/playground_components/lib/src/cache/example_cache.dart @@ -239,9 +239,6 @@ class ExampleCache extends ChangeNotifier { // As long as any of the examples is loaded, continue. print(ex); // TODO: Log. - - notifyListeners(); - throw ExampleLoadingException(ex); } notifyListeners(); diff --git a/playground/frontend/playground_components/lib/src/controllers/example_loaders/examples_loader.dart b/playground/frontend/playground_components/lib/src/controllers/example_loaders/examples_loader.dart index d68eea8a6e14..ecfc9a77d4ee 100644 --- a/playground/frontend/playground_components/lib/src/controllers/example_loaders/examples_loader.dart +++ b/playground/frontend/playground_components/lib/src/controllers/example_loaders/examples_loader.dart @@ -17,11 +17,14 @@ */ import 'package:collection/collection.dart'; +import 'package:get_it/get_it.dart'; +import '../../../playground_components.dart'; import '../../exceptions/example_loading_exception.dart'; import '../../models/example_loading_descriptors/example_loading_descriptor.dart'; import '../../models/example_loading_descriptors/examples_loading_descriptor.dart'; import '../../models/sdk.dart'; +import '../../services/toast_notifier.dart'; import '../playground_controller.dart'; import 'catalog_default_example_loader.dart'; import 'content_example_loader.dart'; @@ -108,18 +111,29 @@ class ExamplesLoader { } Future loadDefaultIfAny(Sdk sdk) async { - final one = _descriptor?.lazyLoadDescriptors[sdk]?.firstOrNull; + try { + final one = _descriptor?.lazyLoadDescriptors[sdk]?.firstOrNull; - if (_descriptor == null || one == null) { - return; - } + if (_descriptor == null || one == null) { + return; + } - final loader = _createLoader(one); - if (loader == null) { - return; - } + final loader = _createLoader(one); + if (loader == null) { + return; + } - await _loadOne(loader); + await _loadOne(loader); + } on Exception catch (ex) { + GetIt.instance.get().addException(ex); + await _loadOne( + EmptyExampleLoader( + descriptor: EmptyExampleLoadingDescriptor(sdk: sdk), + exampleCache: _playgroundController!.exampleCache, + ), + ); + rethrow; + } } Future _loadOne(ExampleLoader loader) async { From 6e4581bb9bdc236db7644f63daafd43805b7e3aa Mon Sep 17 00:00:00 2001 From: malarg Date: Fri, 17 Feb 2023 18:32:09 +0400 Subject: [PATCH 42/52] fix compile error --- .../standalone_cancel_running_example_test.dart | 3 ++- .../standalone_change_pipeline_options_and_run_test.dart | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/playground/frontend/integration_test/standalone_cancel_running_example_test.dart b/playground/frontend/integration_test/standalone_cancel_running_example_test.dart index 3d429b1b77fa..eb0969381a93 100644 --- a/playground/frontend/integration_test/standalone_cancel_running_example_test.dart +++ b/playground/frontend/integration_test/standalone_cancel_running_example_test.dart @@ -18,11 +18,12 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; -import 'package:playground_components/playground_components.dart'; import 'package:playground_components_dev/playground_components_dev.dart'; import 'common/common.dart'; +const kExecutionCancelledText = 'Pipeline cancelled'; + void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); diff --git a/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart b/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart index 940bf1d9375c..2cca9ec56dbf 100644 --- a/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart +++ b/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart @@ -23,12 +23,12 @@ import 'package:playground/modules/editor/components/pipeline_options_dropdown/p import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_input.dart'; import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_options_row.dart'; import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_options_text_field.dart'; -import 'package:playground_components/playground_components.dart'; import 'package:playground_components_dev/playground_components_dev.dart'; import 'common/common.dart'; import 'common/common_finders.dart'; import 'common/finder.dart'; +import 'standalone_cancel_running_example_test.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); From 3aa9fec33cb6fc44ecd5e9c5b2a7e822f6a00bf5 Mon Sep 17 00:00:00 2001 From: alexeyinkin Date: Tue, 21 Feb 2023 14:08:11 +0400 Subject: [PATCH 43/52] Refactor output tabs, test embedded playground (#25136) (#439) * Refactor output tabs, test embedded playground (#25136) * Clean up (#25136) --- playground/frontend/README.md | 2 +- playground/frontend/build.gradle | 23 ++- .../integration_test/common/finder.dart | 73 -------- .../integration_test/embedded_run_test.dart | 42 +++++ .../toggle_brightness_mode_test.dart | 20 +-- ...tandalone_change_example_sdk_run_test.dart | 21 +-- ..._change_pipeline_options_and_run_test.dart | 1 - .../standalone_default_examples_test.dart | 35 +--- .../standalone_editing_test.dart | 1 - .../lib/playground_components.dart | 4 +- .../lib/src/controllers/code_runner.dart | 111 ++++++++---- .../example_loaders/examples_loader.dart | 2 +- .../controllers/playground_controller.dart | 7 +- ...ler.dart => result_filter_controller.dart} | 10 +- .../src/controllers/unread_controller.dart | 58 +++++++ .../lib/src/enums/output_tab.dart | 27 +++ .../lib/src/enums/output_tab.g.dart | 164 ++++++++++++++++++ .../lib/src/enums/result_filter.dart | 23 +++ .../lib/src/enums/unread_entry.dart | 23 +++ .../lib/src/models/outputs.dart | 6 - .../lib/src/repositories/code_repository.dart | 92 +++++----- .../repositories/models/run_code_result.dart | 26 +-- .../lib/src/widgets/output/graph/graph.dart | 105 ----------- .../lib/src/widgets/output/graph/painter.dart | 38 ++++ .../lib/src/widgets/output/graph_tab.dart | 47 +++++ .../src/widgets/output/graph_tab_content.dart | 110 ++++++++++++ .../lib/src/widgets/output/output.dart | 121 ++++++------- .../lib/src/widgets/output/output_area.dart | 81 --------- .../lib/src/widgets/output/output_result.dart | 69 -------- .../lib/src/widgets/output/output_tab.dart | 91 +++------- .../widgets/output/result_filter_bubble.dart | 24 +-- ...ut_tabs.dart => result_filter_button.dart} | 53 +++--- .../widgets/output/result_filter_popover.dart | 43 +++-- .../lib/src/widgets/output/result_tab.dart | 49 ++++++ .../widgets/output/result_tab_content.dart | 85 +++++++++ .../lib/src/widgets/tabs/tab_bar.dart | 19 +- .../lib/src/widgets/unread/builder.dart | 46 +++++ .../lib/src/widgets/unread/clearer.dart | 58 +++++++ .../lib/src/widgets/unread/marker.dart | 37 ++++ .../playground_components/pubspec.yaml | 5 +- .../examples_loader_test.mocks.dart | 46 +++-- .../playground_controller_test.dart | 4 +- .../controllers/unread_controller_test.dart | 77 ++++++++ .../repositories/code_repository_test.dart | 129 ++++++++------ .../lib/src/common_finders.dart | 11 +- .../lib/src/expect.dart | 15 +- .../lib/src/finder.dart | 69 ++++++++ .../lib/src/widget_tester.dart | 75 ++++++-- .../playground_components_dev/pubspec.yaml | 5 +- playground/frontend/pubspec.lock | 16 +- playground/frontend/pubspec.yaml | 4 +- 51 files changed, 1497 insertions(+), 806 deletions(-) delete mode 100644 playground/frontend/integration_test/common/finder.dart create mode 100644 playground/frontend/integration_test/embedded_run_test.dart rename playground/frontend/playground_components/lib/src/controllers/{output_filter_type_controller.dart => result_filter_controller.dart} (79%) create mode 100644 playground/frontend/playground_components/lib/src/controllers/unread_controller.dart create mode 100644 playground/frontend/playground_components/lib/src/enums/output_tab.dart create mode 100644 playground/frontend/playground_components/lib/src/enums/output_tab.g.dart create mode 100644 playground/frontend/playground_components/lib/src/enums/result_filter.dart create mode 100644 playground/frontend/playground_components/lib/src/enums/unread_entry.dart delete mode 100644 playground/frontend/playground_components/lib/src/widgets/output/graph/graph.dart create mode 100644 playground/frontend/playground_components/lib/src/widgets/output/graph/painter.dart create mode 100644 playground/frontend/playground_components/lib/src/widgets/output/graph_tab.dart create mode 100644 playground/frontend/playground_components/lib/src/widgets/output/graph_tab_content.dart delete mode 100644 playground/frontend/playground_components/lib/src/widgets/output/output_area.dart delete mode 100644 playground/frontend/playground_components/lib/src/widgets/output/output_result.dart rename playground/frontend/playground_components/lib/src/widgets/output/{output_tabs.dart => result_filter_button.dart} (55%) create mode 100644 playground/frontend/playground_components/lib/src/widgets/output/result_tab.dart create mode 100644 playground/frontend/playground_components/lib/src/widgets/output/result_tab_content.dart create mode 100644 playground/frontend/playground_components/lib/src/widgets/unread/builder.dart create mode 100644 playground/frontend/playground_components/lib/src/widgets/unread/clearer.dart create mode 100644 playground/frontend/playground_components/lib/src/widgets/unread/marker.dart create mode 100644 playground/frontend/playground_components/test/src/controllers/unread_controller_test.dart diff --git a/playground/frontend/README.md b/playground/frontend/README.md index 705e96b9d843..2c11bf73093c 100644 --- a/playground/frontend/README.md +++ b/playground/frontend/README.md @@ -146,7 +146,7 @@ flutter format ./lib To delete all generated files and re-generate them again and then run tests: ```bash -./gradlew :playground:frontend:playground_components::test +./gradlew :playground:frontend:playground_components:test ./gradlew :playground:frontend:test ``` diff --git a/playground/frontend/build.gradle b/playground/frontend/build.gradle index f5f45f0a76f2..9e1141f11874 100644 --- a/playground/frontend/build.gradle +++ b/playground/frontend/build.gradle @@ -181,20 +181,21 @@ ext.deleteFilesByRegExp = { re -> } tasks.register("integrationTest") { + dependsOn("integrationTest_embedded_run") dependsOn("integrationTest_initial_urls") - dependsOn("integrationTest_standalone_change_example_sdk_run") - dependsOn("integrationTest_standalone_default_examples") dependsOn("integrationTest_standalone_cancel_running_example") + dependsOn("integrationTest_standalone_change_example_sdk_run") dependsOn("integrationTest_standalone_change_pipeline_options_and_run") + dependsOn("integrationTest_standalone_default_examples") dependsOn("integrationTest_standalone_editing") dependsOn("integrationTest_standalone_example_selector") dependsOn("integrationTest_standalone_miscellaneous_ui") dependsOn("integrationTest_standalone_run_shortcuts") } -tasks.register("integrationTest_standalone_cancel_running_example") { +tasks.register("integrationTest_embedded_run") { doLast { - runIntegrationTest("standalone_cancel_running_example", "/") + runIntegrationTest("embedded_run", "/") } } @@ -204,15 +205,15 @@ tasks.register("integrationTest_initial_urls") { } } -tasks.register("integrationTest_standalone_change_example_sdk_run") { +tasks.register("integrationTest_standalone_cancel_running_example") { doLast { - runIntegrationTest("standalone_change_example_sdk_run", "/") + runIntegrationTest("standalone_cancel_running_example", "/") } } -tasks.register("integrationTest_standalone_default_examples") { +tasks.register("integrationTest_standalone_change_example_sdk_run") { doLast { - runIntegrationTest("standalone_default_examples", "/") + runIntegrationTest("standalone_change_example_sdk_run", "/") } } @@ -222,6 +223,12 @@ tasks.register("integrationTest_standalone_change_pipeline_options_and_run") { } } +tasks.register("integrationTest_standalone_default_examples") { + doLast { + runIntegrationTest("standalone_default_examples", "/") + } +} + tasks.register("integrationTest_standalone_editing") { doLast { runIntegrationTest("standalone_editing", "/") diff --git a/playground/frontend/integration_test/common/finder.dart b/playground/frontend/integration_test/common/finder.dart deleted file mode 100644 index a9037f715eae..000000000000 --- a/playground/frontend/integration_test/common/finder.dart +++ /dev/null @@ -1,73 +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/painting.dart'; -import 'package:flutter_test/flutter_test.dart'; - -extension FinderExtension on Finder { - Finder getChildrenByType(Type childType) { - final finders = evaluate(); - final childElements = finders - .map((e) => collectAllElementsFrom(e, skipOffstage: true)) - .expand((e) => e) - .where((e) => e.widget.runtimeType == childType); - - return find.byElementPredicate( - (element) => childElements.contains(element), - ); - } - - Finder horizontallyAt(int index, WidgetTester wt) => - _atIndexOnAxis(index, Axis.horizontal, wt); - - Finder verticallyAt(int index, WidgetTester wt) => - _atIndexOnAxis(index, Axis.vertical, wt); - - Finder _atIndexOnAxis(int index, Axis axis, WidgetTester wt) { - final finders = evaluate(); - - if (index > finders.length - 1) { - throw IndexError(index, finders); - } - - final offsets = <_IndexAndOffset>[]; - - for (int i = 0; i < finders.length; i++) { - offsets.add(_IndexAndOffset(i, wt.getCenter(at(i)))); - } - - offsets.sort( - (a, b) => axis == Axis.vertical - ? _compareDoubles(a.offset.dy, b.offset.dy) - : _compareDoubles(a.offset.dx, b.offset.dx), - ); - - return at(offsets[index].index); - } - - int _compareDoubles(double a, double b) { - return (a - b).sign.toInt(); - } -} - -class _IndexAndOffset { - final int index; - final Offset offset; - - _IndexAndOffset(this.index, this.offset); -} diff --git a/playground/frontend/integration_test/embedded_run_test.dart b/playground/frontend/integration_test/embedded_run_test.dart new file mode 100644 index 000000000000..c4da338b1130 --- /dev/null +++ b/playground/frontend/integration_test/embedded_run_test.dart @@ -0,0 +1,42 @@ +/* + * 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:integration_test/integration_test.dart'; +import 'package:playground_components_dev/playground_components_dev.dart'; + +import 'common/common.dart'; +import 'miscellaneous_ui/toggle_brightness_mode_test.dart'; + +final _url = '/embedded?path=${javaMinimalWordCount.dbPath}&sdk=java'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + testWidgets('Embedded run', (WidgetTester wt) async { + await init(wt); + + await _openJavaMinimalWordCount(wt); + await wt.runExpectCached(javaMinimalWordCount); + await wt.modifyRunExpectReal(javaMinimalWordCount); + await checkToggleBrightnessMode(wt); + }); +} + +Future _openJavaMinimalWordCount(WidgetTester wt) async { + await wt.navigateAndSettle(_url); +} diff --git a/playground/frontend/integration_test/miscellaneous_ui/toggle_brightness_mode_test.dart b/playground/frontend/integration_test/miscellaneous_ui/toggle_brightness_mode_test.dart index 16bb9a3cc33d..7cc291db9398 100644 --- a/playground/frontend/integration_test/miscellaneous_ui/toggle_brightness_mode_test.dart +++ b/playground/frontend/integration_test/miscellaneous_ui/toggle_brightness_mode_test.dart @@ -21,21 +21,13 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:playground_components_dev/playground_components_dev.dart'; Future checkToggleBrightnessMode(WidgetTester wt) async { - Brightness getBrightness() { - return Theme.of(wt.element(find.toggleThemeButton())).brightness; - } - - Future toggleTheme() async { - await wt.tap(find.toggleThemeButton()); - await wt.pumpAndSettle(); - } - - final startBrightness = getBrightness(); + final startBrightness = wt.getBrightness(); final invertedBrightness = startBrightness == Brightness.light ? Brightness.dark : Brightness.light; - await toggleTheme(); - expect(getBrightness(), invertedBrightness); - await toggleTheme(); - expect(getBrightness(), startBrightness); + await wt.toggleTheme(); + expect(wt.getBrightness(), invertedBrightness); + + await wt.toggleTheme(); + expect(wt.getBrightness(), startBrightness); } diff --git a/playground/frontend/integration_test/standalone_change_example_sdk_run_test.dart b/playground/frontend/integration_test/standalone_change_example_sdk_run_test.dart index 90065419abce..2312cf292220 100644 --- a/playground/frontend/integration_test/standalone_change_example_sdk_run_test.dart +++ b/playground/frontend/integration_test/standalone_change_example_sdk_run_test.dart @@ -45,11 +45,6 @@ void main() { ); } - Future runExpectJavaAggregationMax(WidgetTester wt) async { - await wt.runExpectCached(); - expectOutputEndsWith(javaAggregationMax.outputTail, wt); - } - Future runCustomJava(WidgetTester wt) async { const text = 'OK'; const code = ''' @@ -66,7 +61,7 @@ public class MyClass { await wt.tap(find.runOrCancelButton()); await wt.pumpAndSettle(); - expectOutput('$_outputPrefix$text', wt); + expectOutputEquals('$_outputPrefix$text', wt); } Future switchToPython(WidgetTester wt) async { @@ -100,14 +95,6 @@ public class MyClass { // ); } - Future runExpectPythonAggregationMean(WidgetTester wt) async { - await wt.runExpectCached(); - - for (final str in pythonAggregationMean.outputContains!) { - expectOutputContains(str, wt); - } - } - Future runCustomPython(WidgetTester wt) async { const text = 'OK'; const code = 'print("$text", end="")'; @@ -118,19 +105,19 @@ public class MyClass { await wt.tap(find.runOrCancelButton()); await wt.pumpAndSettle(); - expectOutput('$_outputPrefix$text', wt); + expectOutputEquals('$_outputPrefix$text', wt); } testWidgets('Change example, change SDK, run', (WidgetTester wt) async { await init(wt); await changeToJavaAggregationMax(wt); - await runExpectJavaAggregationMax(wt); + await wt.runExpectCached(javaAggregationMax); await runCustomJava(wt); await switchToPython(wt); await changeToPythonAggregationMean(wt); - await runExpectPythonAggregationMean(wt); + await wt.runExpectCached(pythonAggregationMean); await runCustomPython(wt); }); } diff --git a/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart b/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart index 2cca9ec56dbf..373d98df9461 100644 --- a/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart +++ b/playground/frontend/integration_test/standalone_change_pipeline_options_and_run_test.dart @@ -27,7 +27,6 @@ import 'package:playground_components_dev/playground_components_dev.dart'; import 'common/common.dart'; import 'common/common_finders.dart'; -import 'common/finder.dart'; import 'standalone_cancel_running_example_test.dart'; void main() { diff --git a/playground/frontend/integration_test/standalone_default_examples_test.dart b/playground/frontend/integration_test/standalone_default_examples_test.dart index 2e3ddcf6b651..197e27868da6 100644 --- a/playground/frontend/integration_test/standalone_default_examples_test.dart +++ b/playground/frontend/integration_test/standalone_default_examples_test.dart @@ -18,6 +18,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:playground_components_dev/playground_components_dev.dart'; import 'common/common.dart'; @@ -42,8 +43,8 @@ void main() { } await _expectExample(example, wt); - await _runCached(example, wt); - await _runReal(example, wt); + await wt.runExpectCached(example); + await wt.modifyRunExpectReal(example); } }); } @@ -63,33 +64,5 @@ Future _expectExample(ExampleDescriptor example, WidgetTester wt) async { } expect(find.resultTab(), findsOneWidget); - expect(wt.findOutputTabController().index, 0); -} - -Future _runCached(ExampleDescriptor example, WidgetTester wt) async { - await wt.runExpectCached(); - _expectOutput(example, wt); -} - -Future _runReal(ExampleDescriptor example, WidgetTester wt) async { - // Add a character into the first comment. - // This relies on that the position 10 is inside a license comment. - final controller = wt.findOneCodeController(); - final text = controller.fullText; - controller.fullText = text.substring(0, 10) + '+' + text.substring(10); - - await wt.runExpectReal(); - _expectOutput(example, wt); -} - -void _expectOutput(ExampleDescriptor example, WidgetTester wt) { - if (example.outputTail != null) { - expectOutputEndsWith(example.outputTail, wt); - } else if (example.outputContains != null) { - for (final str in example.outputContains!) { - expectOutputContains(str, wt); - } - } else { - throw AssertionError('No pattern to check example output: ${example.path}'); - } + expect(wt.findOutputTabController().currentKey, OutputTabEnum.result); } diff --git a/playground/frontend/integration_test/standalone_editing_test.dart b/playground/frontend/integration_test/standalone_editing_test.dart index 35bb35f2f32d..83a4d433fd33 100644 --- a/playground/frontend/integration_test/standalone_editing_test.dart +++ b/playground/frontend/integration_test/standalone_editing_test.dart @@ -23,7 +23,6 @@ import 'package:playground_components_dev/playground_components_dev.dart'; import 'common/common.dart'; import 'common/common_finders.dart'; -import 'common/finder.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); diff --git a/playground/frontend/playground_components/lib/playground_components.dart b/playground/frontend/playground_components/lib/playground_components.dart index 3dfb270c4219..1278cbc61c68 100644 --- a/playground/frontend/playground_components/lib/playground_components.dart +++ b/playground/frontend/playground_components/lib/playground_components.dart @@ -24,6 +24,7 @@ export 'src/controllers/example_loaders/examples_loader.dart'; export 'src/controllers/playground_controller.dart'; export 'src/controllers/public_notifier.dart'; export 'src/enums/complexity.dart'; +export 'src/enums/output_tab.dart'; export 'src/models/category_with_examples.dart'; export 'src/models/dataset.dart'; export 'src/models/example.dart'; @@ -67,9 +68,7 @@ export 'src/widgets/loading_error.dart'; export 'src/widgets/loading_indicator.dart'; export 'src/widgets/logo.dart'; export 'src/widgets/output/output.dart'; -export 'src/widgets/output/output_area.dart'; export 'src/widgets/output/output_tab.dart'; -export 'src/widgets/output/output_tabs.dart'; export 'src/widgets/overlay/body.dart'; export 'src/widgets/overlay/dismissible.dart'; export 'src/widgets/overlay/opener.dart'; @@ -80,6 +79,7 @@ export 'src/widgets/shortcuts_manager.dart'; export 'src/widgets/snippet_editor.dart'; export 'src/widgets/split_view.dart'; export 'src/widgets/tab_header.dart'; +export 'src/widgets/tabs/tab_bar.dart'; export 'src/widgets/toasts/toast_listener.dart'; export 'src/widgets/toggle_theme_button.dart'; export 'src/widgets/toggle_theme_icon_button.dart'; diff --git a/playground/frontend/playground_components/lib/src/controllers/code_runner.dart b/playground/frontend/playground_components/lib/src/controllers/code_runner.dart index 343a085a4865..3836a5c434f3 100644 --- a/playground/frontend/playground_components/lib/src/controllers/code_runner.dart +++ b/playground/frontend/playground_components/lib/src/controllers/code_runner.dart @@ -23,15 +23,18 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import '../../playground_components.dart'; +import '../enums/unread_entry.dart'; import '../repositories/models/run_code_request.dart'; import '../repositories/models/run_code_result.dart'; import '../util/connectivity_result.dart'; import 'snippet_editing_controller.dart'; +import 'unread_controller.dart'; class CodeRunner extends ChangeNotifier { final CodeRepository? _codeRepository; final ValueGetter _snippetEditingControllerGetter; SnippetEditingController? snippetEditingController; + final unreadController = UnreadController(); CodeRunner({ required ValueGetter @@ -61,7 +64,7 @@ class CodeRunner extends ChangeNotifier { } void clearResult() { - _result = null; + _setResult(null); notifyListeners(); } @@ -71,7 +74,7 @@ class CodeRunner extends ChangeNotifier { } _runStartDate = null; _runStopDate = null; - _result = null; + _setResult(null); notifyListeners(); } @@ -80,13 +83,17 @@ class CodeRunner extends ChangeNotifier { _runStopDate = null; notifyListeners(); snippetEditingController = _snippetEditingControllerGetter(); + final sdk = snippetEditingController!.sdk; final parsedPipelineOptions = parsePipelineOptions(snippetEditingController!.pipelineOptions); if (parsedPipelineOptions == null) { - _result = RunCodeResult( - status: RunCodeStatus.compileError, - errorMessage: 'errors.failedParseOptions'.tr(), + _setResult( + RunCodeResult( + errorMessage: 'errors.failedParseOptions'.tr(), + sdk: sdk, + status: RunCodeStatus.compileError, + ), ); _runStopDate = DateTime.now(); notifyListeners(); @@ -104,7 +111,7 @@ class CodeRunner extends ChangeNotifier { pipelineOptions: parsedPipelineOptions, ); _runSubscription = _codeRepository?.runCode(request).listen((event) { - _result = event; + _setResult(event); notifyListeners(); if (event.isFinished) { @@ -127,22 +134,35 @@ class CodeRunner extends ChangeNotifier { if (_result == null) { return; } - _result = RunCodeResult( - status: _result!.status, - output: _result!.output, + + _setResult( + RunCodeResult( + output: _result!.output, + sdk: _result!.sdk, + status: _result!.status, + ), ); + notifyListeners(); } Future cancelRun() async { + final sdk = _result?.sdk; + if (sdk == null) { + return; + } + final hasInternet = (await Connectivity().checkConnectivity()).isConnected; if (!hasInternet) { - _result = RunCodeResult( - status: _result?.status ?? RunCodeStatus.unspecified, - output: _result?.output, - log: _result?.log ?? '', - errorMessage: 'errors.internetUnavailable'.tr(), - graph: _result?.graph, + _setResult( + RunCodeResult( + errorMessage: 'errors.internetUnavailable'.tr(), + graph: _result?.graph, + log: _result?.log ?? '', + output: _result?.output, + sdk: sdk, + status: _result?.status ?? RunCodeStatus.unspecified, + ), ); notifyListeners(); return; @@ -158,14 +178,17 @@ class CodeRunner extends ChangeNotifier { await _codeRepository?.cancelExecution(pipelineUuid); } - _result = RunCodeResult( - status: RunCodeStatus.finished, - output: _result?.output, - // ignore: prefer_interpolation_to_compose_strings - log: (_result?.log ?? '') + - '\n' + - 'widgets.output.messages.pipelineCancelled'.tr(), - graph: _result?.graph, + _setResult( + RunCodeResult( + graph: _result?.graph, + // ignore: prefer_interpolation_to_compose_strings + log: (_result?.log ?? '') + + '\n' + + 'widgets.output.messages.pipelineCancelled'.tr(), + output: _result?.output, + sdk: sdk, + status: RunCodeStatus.finished, + ), ); _runStopDate = DateTime.now(); @@ -173,11 +196,15 @@ class CodeRunner extends ChangeNotifier { } Future _showPrecompiledResult() async { - _result = const RunCodeResult( - status: RunCodeStatus.preparation, - ); final selectedExample = snippetEditingController!.example!; + _setResult( + RunCodeResult( + sdk: selectedExample.sdk, + status: RunCodeStatus.preparation, + ), + ); + notifyListeners(); // add a little delay to improve user experience await Future.delayed(kPrecompiledDelay); @@ -187,15 +214,35 @@ class CodeRunner extends ChangeNotifier { } final String logs = selectedExample.logs ?? ''; - _result = RunCodeResult( - status: RunCodeStatus.finished, - output: selectedExample.outputs, - // ignore: prefer_interpolation_to_compose_strings - log: 'widgets.output.messages.cachedResult'.tr() + '\n$logs', - graph: selectedExample.graph, + _setResult( + RunCodeResult( + graph: selectedExample.graph, + // ignore: prefer_interpolation_to_compose_strings + log: kCachedResultsLog + logs, + output: selectedExample.outputs, + sdk: selectedExample.sdk, + status: RunCodeStatus.finished, + ), ); _runStopDate = DateTime.now(); notifyListeners(); } + + void _setResult(RunCodeResult? newValue) { + _result = newValue; + + if (newValue == null) { + unreadController.markAllRead(); + } else { + unreadController.setValue( + UnreadEntryEnum.result, + (newValue.output ?? '') + (newValue.log ?? ''), + ); + unreadController.setValue( + UnreadEntryEnum.graph, + newValue.graph ?? '', + ); + } + } } diff --git a/playground/frontend/playground_components/lib/src/controllers/example_loaders/examples_loader.dart b/playground/frontend/playground_components/lib/src/controllers/example_loaders/examples_loader.dart index ecfc9a77d4ee..75342c3ec0fe 100644 --- a/playground/frontend/playground_components/lib/src/controllers/example_loaders/examples_loader.dart +++ b/playground/frontend/playground_components/lib/src/controllers/example_loaders/examples_loader.dart @@ -19,8 +19,8 @@ import 'package:collection/collection.dart'; import 'package:get_it/get_it.dart'; -import '../../../playground_components.dart'; import '../../exceptions/example_loading_exception.dart'; +import '../../models/example_loading_descriptors/empty_example_loading_descriptor.dart'; import '../../models/example_loading_descriptors/example_loading_descriptor.dart'; import '../../models/example_loading_descriptors/examples_loading_descriptor.dart'; import '../../models/sdk.dart'; diff --git a/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart index 9cf04ff503c4..f380af4cc02d 100644 --- a/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart +++ b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart @@ -41,19 +41,20 @@ import '../services/symbols/symbols_notifier.dart'; import '../util/logical_keyboard_key.dart'; import 'code_runner.dart'; import 'example_loaders/examples_loader.dart'; -import 'output_filter_type_controller.dart'; +import 'result_filter_controller.dart'; import 'snippet_editing_controller.dart'; const kTitleLength = 25; const kExecutionTimeUpdate = 100; const kPrecompiledDelay = Duration(seconds: 1); +const kCachedResultsLog = + 'The results of this example are taken from the Apache Beam Playground cache.\n'; /// The main state object for the code and its running. class PlaygroundController with ChangeNotifier { final ExampleCache exampleCache; final ExamplesLoader examplesLoader; - final OutputFilterTypeController outputTypeController = - OutputFilterTypeController(); + final resultFilterController = ResultFilterController(); late final CodeRunner codeRunner; diff --git a/playground/frontend/playground_components/lib/src/controllers/output_filter_type_controller.dart b/playground/frontend/playground_components/lib/src/controllers/result_filter_controller.dart similarity index 79% rename from playground/frontend/playground_components/lib/src/controllers/output_filter_type_controller.dart rename to playground/frontend/playground_components/lib/src/controllers/result_filter_controller.dart index d245e800b016..fc8703846fb2 100644 --- a/playground/frontend/playground_components/lib/src/controllers/output_filter_type_controller.dart +++ b/playground/frontend/playground_components/lib/src/controllers/result_filter_controller.dart @@ -18,13 +18,13 @@ import 'package:flutter/material.dart'; -import '../../playground_components.dart'; +import '../enums/result_filter.dart'; -class OutputFilterTypeController extends ChangeNotifier { - OutputType outputFilterType = OutputType.all; +class ResultFilterController extends ChangeNotifier { + ResultFilterEnum value = ResultFilterEnum.all; - void setOutputFilterType(OutputType type) { - outputFilterType = type; + void setValue(ResultFilterEnum newValue) { + value = newValue; notifyListeners(); } } diff --git a/playground/frontend/playground_components/lib/src/controllers/unread_controller.dart b/playground/frontend/playground_components/lib/src/controllers/unread_controller.dart new file mode 100644 index 000000000000..1b6457247dab --- /dev/null +++ b/playground/frontend/playground_components/lib/src/controllers/unread_controller.dart @@ -0,0 +1,58 @@ +/* + * 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/foundation.dart'; + +/// Tracks the unread status of arbitrary data. +class UnreadController extends ChangeNotifier { + final _values = {}; + final _unreadKeys = {}; + + /// Marks [key] as unread if [value] differs from the last call. + void setValue(K key, dynamic value) { + if (_values.containsKey(key) && _values[key] == value) { + return; + } + + _values[key] = value; + _unreadKeys.add(key); + notifyListeners(); + } + + bool isUnread(K key) { + return _unreadKeys.contains(key); + } + + void markRead(K key) { + if (!_unreadKeys.contains(key)) { + return; + } + + _unreadKeys.remove(key); + notifyListeners(); + } + + void markAllRead() { + if (_unreadKeys.isEmpty) { + return; + } + + _unreadKeys.clear(); + notifyListeners(); + } +} diff --git a/playground/frontend/playground_components/lib/src/enums/output_tab.dart b/playground/frontend/playground_components/lib/src/enums/output_tab.dart new file mode 100644 index 000000000000..0d3b70b072f4 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/enums/output_tab.dart @@ -0,0 +1,27 @@ +/* + * 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:enum_map/enum_map.dart'; + +part 'output_tab.g.dart'; + +@unmodifiableEnumMap +enum OutputTabEnum { + result, + graph, +} diff --git a/playground/frontend/playground_components/lib/src/enums/output_tab.g.dart b/playground/frontend/playground_components/lib/src/enums/output_tab.g.dart new file mode 100644 index 000000000000..e7d801fb8415 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/enums/output_tab.g.dart @@ -0,0 +1,164 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'output_tab.dart'; + +// ************************************************************************** +// UnmodifiableEnumMapGenerator +// ************************************************************************** + +class UnmodifiableOutputTabEnumMap + extends UnmodifiableEnumMap { + final V result; + final V graph; + + const UnmodifiableOutputTabEnumMap({ + required this.result, + required this.graph, + }); + + @override + Map cast() { + return Map.castFrom(this); + } + + @override + bool containsValue(Object? value) { + if (this.result == value) return true; + if (this.graph == value) return true; + return false; + } + + @override + bool containsKey(Object? key) { + return key.runtimeType == OutputTabEnum; + } + + @override + V? operator [](Object? key) { + switch (key) { + case OutputTabEnum.result: + return this.result; + case OutputTabEnum.graph: + return this.graph; + } + + return null; + } + + @override + void operator []=(OutputTabEnum key, V value) { + throw Exception("Cannot modify this map."); + } + + @override + Iterable> get entries { + return [ + MapEntry(OutputTabEnum.result, this.result), + MapEntry(OutputTabEnum.graph, this.graph), + ]; + } + + @override + Map map( + MapEntry transform(OutputTabEnum key, V value)) { + final result = transform(OutputTabEnum.result, this.result); + final graph = transform(OutputTabEnum.graph, this.graph); + return { + result.key: result.value, + graph.key: graph.value, + }; + } + + @override + void addEntries(Iterable> newEntries) { + throw Exception("Cannot modify this map."); + } + + @override + V update(OutputTabEnum key, V update(V value), {V Function()? ifAbsent}) { + throw Exception("Cannot modify this map."); + } + + @override + void updateAll(V update(OutputTabEnum key, V value)) { + throw Exception("Cannot modify this map."); + } + + @override + void removeWhere(bool test(OutputTabEnum key, V value)) { + throw Exception("Objects in this map cannot be removed."); + } + + @override + V putIfAbsent(OutputTabEnum key, V ifAbsent()) { + return this.get(key); + } + + @override + void addAll(Map other) { + throw Exception("Cannot modify this map."); + } + + @override + V? remove(Object? key) { + throw Exception("Objects in this map cannot be removed."); + } + + @override + void clear() { + throw Exception("Objects in this map cannot be removed."); + } + + @override + void forEach(void action(OutputTabEnum key, V value)) { + action(OutputTabEnum.result, this.result); + action(OutputTabEnum.graph, this.graph); + } + + @override + Iterable get keys { + return OutputTabEnum.values; + } + + @override + Iterable get values { + return [ + this.result, + this.graph, + ]; + } + + @override + int get length { + return 2; + } + + @override + bool get isEmpty { + return false; + } + + @override + bool get isNotEmpty { + return true; + } + + V get(OutputTabEnum key) { + switch (key) { + case OutputTabEnum.result: + return this.result; + case OutputTabEnum.graph: + return this.graph; + } + } + + @override + String toString() { + final buffer = StringBuffer("{"); + buffer.write("OutputTabEnum.result: ${this.result}"); + buffer.write(", "); + buffer.write("OutputTabEnum.graph: ${this.graph}"); + buffer.write("}"); + return buffer.toString(); + } +} diff --git a/playground/frontend/playground_components/lib/src/enums/result_filter.dart b/playground/frontend/playground_components/lib/src/enums/result_filter.dart new file mode 100644 index 000000000000..5dd2a526c33f --- /dev/null +++ b/playground/frontend/playground_components/lib/src/enums/result_filter.dart @@ -0,0 +1,23 @@ +/* + * 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. + */ + +enum ResultFilterEnum { + all, + log, + output, +} diff --git a/playground/frontend/playground_components/lib/src/enums/unread_entry.dart b/playground/frontend/playground_components/lib/src/enums/unread_entry.dart new file mode 100644 index 000000000000..f4bbc9943603 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/enums/unread_entry.dart @@ -0,0 +1,23 @@ +/* + * 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. + */ + +/// Keys to track the unread status for. +enum UnreadEntryEnum { + result, + graph, +} diff --git a/playground/frontend/playground_components/lib/src/models/outputs.dart b/playground/frontend/playground_components/lib/src/models/outputs.dart index 4d56e19b2178..9260089c60bd 100644 --- a/playground/frontend/playground_components/lib/src/models/outputs.dart +++ b/playground/frontend/playground_components/lib/src/models/outputs.dart @@ -16,12 +16,6 @@ * limitations under the License. */ -enum OutputType { - all, - log, - output, -} - class Outputs { final String output; final String graph; diff --git a/playground/frontend/playground_components/lib/src/repositories/code_repository.dart b/playground/frontend/playground_components/lib/src/repositories/code_repository.dart index 855511e4cafd..da630e6c1329 100644 --- a/playground/frontend/playground_components/lib/src/repositories/code_repository.dart +++ b/playground/frontend/playground_components/lib/src/repositories/code_repository.dart @@ -53,8 +53,9 @@ class CodeRepository { .map((e) => '--${e.key} ${e.value}') .join(' ') + '\n'; final initResult = RunCodeResult( - status: RunCodeStatus.preparation, log: log, + sdk: request.sdk, + status: RunCodeStatus.preparation, ); yield initResult; @@ -67,9 +68,10 @@ class CodeRepository { ); } on RunCodeError catch (error) { yield RunCodeResult( - status: RunCodeStatus.unknownError, errorMessage: error.message ?? kUnknownErrorText, output: error.message ?? kUnknownErrorText, + sdk: request.sdk, + status: RunCodeStatus.unknownError, ); } } @@ -80,7 +82,7 @@ class CodeRepository { Stream _checkPipelineExecution( String pipelineUuid, { - RunCodeResult? prevResult, + required RunCodeResult prevResult, }) async* { try { final statusResponse = await runWithRetry( @@ -101,10 +103,11 @@ class CodeRepository { } } on RunCodeError catch (error) { yield RunCodeResult( - pipelineUuid: prevResult?.pipelineUuid, - status: RunCodeStatus.unknownError, errorMessage: error.message ?? kUnknownErrorText, output: error.message ?? kUnknownErrorText, + pipelineUuid: prevResult.pipelineUuid, + sdk: prevResult.sdk, + status: RunCodeStatus.unknownError, ); } } @@ -112,69 +115,77 @@ class CodeRepository { Future _getPipelineResult( String pipelineUuid, RunCodeStatus status, - RunCodeResult? prevResult, + RunCodeResult prevResult, ) async { - final prevOutput = prevResult?.output ?? ''; - final prevLog = prevResult?.log ?? ''; - final prevGraph = prevResult?.graph ?? ''; + final prevOutput = prevResult.output ?? ''; + final prevLog = prevResult.log ?? ''; + final prevGraph = prevResult.graph ?? ''; switch (status) { case RunCodeStatus.compileError: final compileOutput = await _client.getCompileOutput(pipelineUuid); return RunCodeResult( + graph: prevGraph, + log: prevLog, + output: compileOutput.output, pipelineUuid: pipelineUuid, + sdk: prevResult.sdk, status: status, - output: compileOutput.output, - log: prevLog, - graph: prevGraph, ); case RunCodeStatus.timeout: return RunCodeResult( - pipelineUuid: pipelineUuid, - status: status, errorMessage: kTimeoutErrorText, - output: kTimeoutErrorText, - log: prevLog, graph: prevGraph, + log: prevLog, + output: kTimeoutErrorText, + pipelineUuid: pipelineUuid, + sdk: prevResult.sdk, + status: status, ); case RunCodeStatus.runError: final output = await _client.getRunErrorOutput(pipelineUuid); return RunCodeResult( + graph: prevGraph, + log: prevLog, + output: output.output, pipelineUuid: pipelineUuid, + sdk: prevResult.sdk, status: status, - output: output.output, - log: prevLog, - graph: prevGraph, ); case RunCodeStatus.validationError: - final output = await _client.getValidationErrorOutput(pipelineUuid); + final output = + await _client.getValidationErrorOutput(pipelineUuid); return RunCodeResult( - status: status, - output: output.output, - log: prevLog, graph: prevGraph, + log: prevLog, + output: output.output, + sdk: prevResult.sdk, + status: status, ); case RunCodeStatus.preparationError: - final output = await _client.getPreparationErrorOutput(pipelineUuid); + final output = + await _client.getPreparationErrorOutput(pipelineUuid); return RunCodeResult( - status: status, - output: output.output, - log: prevLog, graph: prevGraph, + log: prevLog, + output: output.output, + sdk: prevResult.sdk, + status: status, ); case RunCodeStatus.unknownError: return RunCodeResult( - pipelineUuid: pipelineUuid, - status: status, errorMessage: kUnknownErrorText, - output: kUnknownErrorText, - log: prevLog, graph: prevGraph, + log: prevLog, + output: kUnknownErrorText, + pipelineUuid: pipelineUuid, + sdk: prevResult.sdk, + status: status, ); case RunCodeStatus.executing: @@ -189,11 +200,12 @@ class CodeRepository { final log = responses[1]; final graph = responses[2]; return RunCodeResult( + graph: graph.output, + log: prevLog + log.output, + output: prevOutput + output.output, pipelineUuid: pipelineUuid, + sdk: prevResult.sdk, status: status, - output: prevOutput + output.output, - log: prevLog + log.output, - graph: graph.output, ); case RunCodeStatus.finished: @@ -210,19 +222,21 @@ class CodeRepository { final error = responses[2]; final graph = responses[3]; return RunCodeResult( + graph: graph.output, + log: prevLog + log.output, + output: prevOutput + output.output + error.output, pipelineUuid: pipelineUuid, + sdk: prevResult.sdk, status: status, - output: prevOutput + output.output + error.output, - log: prevLog + log.output, - graph: graph.output, ); default: return RunCodeResult( - pipelineUuid: pipelineUuid, + graph: prevGraph, log: prevLog, + pipelineUuid: pipelineUuid, + sdk: prevResult.sdk, status: status, - graph: prevGraph, ); } } diff --git a/playground/frontend/playground_components/lib/src/repositories/models/run_code_result.dart b/playground/frontend/playground_components/lib/src/repositories/models/run_code_result.dart index 700db498750e..506fe54f633a 100644 --- a/playground/frontend/playground_components/lib/src/repositories/models/run_code_result.dart +++ b/playground/frontend/playground_components/lib/src/repositories/models/run_code_result.dart @@ -18,6 +18,8 @@ import 'package:equatable/equatable.dart'; +import '../../models/sdk.dart'; + enum RunCodeStatus { unspecified, preparation, @@ -43,20 +45,22 @@ const kFinishedStatuses = [ ]; class RunCodeResult with EquatableMixin { - final RunCodeStatus status; - final String? pipelineUuid; - final String? output; - final String? log; - final String? graph; final String? errorMessage; + final String? graph; + final String? log; + final String? output; + final String? pipelineUuid; + final Sdk sdk; + final RunCodeStatus status; const RunCodeResult({ + required this.sdk, required this.status, - this.pipelineUuid, - this.output, - this.log, this.errorMessage, this.graph, + this.log, + this.output, + this.pipelineUuid, }); bool get isFinished { @@ -70,11 +74,7 @@ class RunCodeResult with EquatableMixin { log, output, pipelineUuid, + sdk, status, ]; - - @override - String toString() { - return 'RunCodeResult{pipelineId: $pipelineUuid, status: $status, output: $output, log: $log, errorMessage: $errorMessage}'; - } } diff --git a/playground/frontend/playground_components/lib/src/widgets/output/graph/graph.dart b/playground/frontend/playground_components/lib/src/widgets/output/graph/graph.dart deleted file mode 100644 index 6f5e95c01d24..000000000000 --- a/playground/frontend/playground_components/lib/src/widgets/output/graph/graph.dart +++ /dev/null @@ -1,105 +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'; -import 'package:playground_components/playground_components.dart'; - -import 'graph_builder/canvas_drawer.dart'; -import 'graph_builder/graph_builder.dart'; -import 'graph_builder/painters/graph_painter.dart'; - -class GraphCustomPainter extends CustomPainter { - final GraphPainter graph; - - GraphCustomPainter({required this.graph}); - - @override - void paint(Canvas canvas, Size size) { - graph.paint(CanvasDrawer(canvas)); - } - - @override - bool shouldRepaint(covariant CustomPainter oldDelegate) { - return true; - } -} - -class GraphTab extends StatefulWidget { - final String graph; - final Sdk sdk; - final Axis direction; - - const GraphTab({ - super.key, - required this.graph, - required this.sdk, - required this.direction, - }); - - @override - State createState() => _GraphTabState(); -} - -class _GraphTabState extends State { - GraphPainter? graphPainter; - - @override - void initState() { - if (widget.graph.isNotEmpty) { - graphPainter = GraphBuilder.parseDot(widget.graph, widget.sdk) - ?.getPainter(widget.direction); - } - super.initState(); - } - - @override - void didUpdateWidget(GraphTab oldWidget) { - final graphChanged = - widget.graph.isNotEmpty && oldWidget.graph != widget.graph; - final directionChanged = widget.direction != oldWidget.direction; - if (graphChanged || directionChanged) { - graphPainter = GraphBuilder.parseDot(widget.graph, widget.sdk) - ?.getPainter(widget.direction); - } - if (widget.graph.isEmpty) { - graphPainter = null; - } - super.didUpdateWidget(oldWidget); - } - - @override - Widget build(BuildContext context) { - if (graphPainter == null) { - return Container(); - } - return Padding( - padding: const EdgeInsets.all(BeamSizes.size16), - child: SingleChildScrollView( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: ClipRRect( - child: CustomPaint( - painter: GraphCustomPainter(graph: graphPainter!), - size: graphPainter?.getSize(), - ), - ), - ), - ), - ); - } -} diff --git a/playground/frontend/playground_components/lib/src/widgets/output/graph/painter.dart b/playground/frontend/playground_components/lib/src/widgets/output/graph/painter.dart new file mode 100644 index 000000000000..f612153c5901 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/widgets/output/graph/painter.dart @@ -0,0 +1,38 @@ +/* + * 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 'graph_builder/canvas_drawer.dart'; +import 'graph_builder/painters/graph_painter.dart'; + +class GraphCustomPainter extends CustomPainter { + final GraphPainter graph; + + GraphCustomPainter({required this.graph}); + + @override + void paint(Canvas canvas, Size size) { + graph.paint(CanvasDrawer(canvas)); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return true; + } +} diff --git a/playground/frontend/playground_components/lib/src/widgets/output/graph_tab.dart b/playground/frontend/playground_components/lib/src/widgets/output/graph_tab.dart new file mode 100644 index 000000000000..3edac92d0b77 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/widgets/output/graph_tab.dart @@ -0,0 +1,47 @@ +/* + * 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:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; + +import '../../controllers/playground_controller.dart'; +import '../../enums/unread_entry.dart'; +import '../unread/builder.dart'; +import 'output_tab.dart'; + +class GraphTab extends StatelessWidget { + const GraphTab({ + required this.playgroundController, + }); + + final PlaygroundController playgroundController; + + @override + Widget build(BuildContext context) { + return UnreadBuilder( + controller: playgroundController.codeRunner.unreadController, + unreadKey: UnreadEntryEnum.graph, + builder: (context, isUnread) { + return OutputTab( + isUnread: isUnread, + title: 'widgets.output.graph'.tr(), + ); + }, + ); + } +} diff --git a/playground/frontend/playground_components/lib/src/widgets/output/graph_tab_content.dart b/playground/frontend/playground_components/lib/src/widgets/output/graph_tab_content.dart new file mode 100644 index 000000000000..913437133d6d --- /dev/null +++ b/playground/frontend/playground_components/lib/src/widgets/output/graph_tab_content.dart @@ -0,0 +1,110 @@ +/* + * 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 '../../constants/sizes.dart'; +import '../../controllers/playground_controller.dart'; +import '../../enums/unread_entry.dart'; +import '../unread/clearer.dart'; +import 'graph/graph_builder/graph_builder.dart'; +import 'graph/graph_builder/painters/graph_painter.dart'; +import 'graph/painter.dart'; + +class GraphTabContent extends StatefulWidget { + const GraphTabContent({ + super.key, + required this.direction, + required this.playgroundController, + }); + + final Axis direction; + final PlaygroundController playgroundController; + + @override + State createState() => _GraphTabContentState(); +} + +class _GraphTabContentState extends State { + GraphPainter? _graphPainter; + String _lastGraph = ''; + + @override + void initState() { + super.initState(); + widget.playgroundController.addListener(_updateGraphPainterIfNeed); + _updateGraphPainter(); + } + + void _updateGraphPainterIfNeed() { + final graph = widget.playgroundController.codeRunner.result?.graph ?? ''; + if (graph == _lastGraph) { + return; + } + + _updateGraphPainter(); + } + + void _updateGraphPainter() { + final codeRunner = widget.playgroundController.codeRunner; + final result = codeRunner.result; + final graph = result?.graph ?? ''; + final sdk = result?.sdk; + + _lastGraph = graph; + _graphPainter = graph.isEmpty || sdk == null + ? null + : GraphBuilder.parseDot(graph, sdk)?.getPainter(widget.direction); + } + + @override + void dispose() { + widget.playgroundController.removeListener(_updateGraphPainterIfNeed); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final graphPainter = _graphPainter; + if (graphPainter == null) { + return Container(); + } + + return UnreadClearer( + controller: widget.playgroundController.codeRunner.unreadController, + unreadKey: UnreadEntryEnum.graph, + child: AnimatedBuilder( + animation: widget.playgroundController.codeRunner, + builder: (context, child) => Padding( + padding: const EdgeInsets.all(BeamSizes.size16), + child: SingleChildScrollView( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: ClipRRect( + child: CustomPaint( + painter: GraphCustomPainter(graph: graphPainter), + size: graphPainter.getSize(), + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/playground/frontend/playground_components/lib/src/widgets/output/output.dart b/playground/frontend/playground_components/lib/src/widgets/output/output.dart index 81a42f2a794e..e7ae35a74416 100644 --- a/playground/frontend/playground_components/lib/src/widgets/output/output.dart +++ b/playground/frontend/playground_components/lib/src/widgets/output/output.dart @@ -17,88 +17,79 @@ */ import 'package:flutter/material.dart'; +import 'package:keyed_collection_widgets/keyed_collection_widgets.dart'; import '../../controllers/playground_controller.dart'; -import '../tab_header.dart'; -import 'output_area.dart'; -import 'output_tabs.dart'; +import '../../enums/output_tab.dart'; +import '../tabs/tab_bar.dart'; +import 'graph_tab.dart'; +import 'graph_tab_content.dart'; +import 'result_tab.dart'; +import 'result_tab_content.dart'; -const kTabsCount = 2; - -class OutputWidget extends StatefulWidget { +class OutputWidget extends StatelessWidget { final PlaygroundController playgroundController; final Widget? trailing; final Axis graphDirection; - OutputWidget({ + const OutputWidget({ required this.playgroundController, required this.graphDirection, this.trailing, - }) : super( - key: ValueKey( - '${playgroundController.sdk}_${playgroundController.selectedExample?.path}', - ), - ); + }); @override - State createState() => _OutputWidgetState(); -} - -class _OutputWidgetState extends State - with SingleTickerProviderStateMixin { - late final TabController tabController; - int selectedTab = 0; - - @override - void initState() { - final tabsCount = widget.playgroundController.graphAvailable - ? kTabsCount - : kTabsCount - 1; - tabController = TabController(vsync: this, length: tabsCount); - tabController.addListener(_onTabChange); - super.initState(); - } - - @override - void dispose() { - tabController.removeListener(_onTabChange); - tabController.dispose(); - super.dispose(); - } + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: playgroundController, + builder: (context, child) { + final keys = [...OutputTabEnum.values]; - void _onTabChange() { - setState(() { - selectedTab = tabController.index; - }); - } + if (!playgroundController.graphAvailable) { + keys.remove(OutputTabEnum.graph); + } - @override - Widget build(BuildContext context) { - return Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: TabHeader( - tabController: tabController, - tabsWidget: OutputTabs( - playgroundController: widget.playgroundController, - tabController: tabController, + return DefaultKeyedTabController.fromKeys( + keys: keys, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: BeamTabBar( + hasPadding: true, + tabs: UnmodifiableOutputTabEnumMap( + result: ResultTab( + playgroundController: playgroundController, + ), + graph: GraphTab( + playgroundController: playgroundController, + ), + ), + ), + ), + if (trailing != null) trailing!, + ], + ), + Expanded( + child: KeyedTabBarView.withDefaultController( + children: UnmodifiableOutputTabEnumMap( + result: ResultTabContent( + playgroundController: playgroundController, + ), + graph: GraphTabContent( + direction: graphDirection, + playgroundController: playgroundController, + ), + ), ), ), - ), - if (widget.trailing != null) widget.trailing!, - ], - ), - Expanded( - child: OutputArea( - playgroundController: widget.playgroundController, - tabController: tabController, - graphDirection: widget.graphDirection, + ], ), - ), - ], + ); + }, ); } } diff --git a/playground/frontend/playground_components/lib/src/widgets/output/output_area.dart b/playground/frontend/playground_components/lib/src/widgets/output/output_area.dart deleted file mode 100644 index d0ac762f858a..000000000000 --- a/playground/frontend/playground_components/lib/src/widgets/output/output_area.dart +++ /dev/null @@ -1,81 +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'; - -import '../../controllers/playground_controller.dart'; -import '../../models/outputs.dart'; -import 'graph/graph.dart'; -import 'output_result.dart'; - -class OutputArea extends StatelessWidget { - final PlaygroundController playgroundController; - final TabController tabController; - final Axis graphDirection; - - const OutputArea({ - super.key, - required this.playgroundController, - required this.tabController, - required this.graphDirection, - }); - - String _getResultOutput() { - final outputType = - playgroundController.outputTypeController.outputFilterType; - switch (outputType) { - case OutputType.log: - return playgroundController.codeRunner.resultLog; - case OutputType.output: - return playgroundController.codeRunner.resultOutput; - case OutputType.all: - return playgroundController.codeRunner.resultLogOutput; - } - } - - @override - Widget build(BuildContext context) { - final sdk = playgroundController.sdk; - - return AnimatedBuilder( - animation: playgroundController.outputTypeController, - builder: (context, child) => ColoredBox( - color: Theme.of(context).backgroundColor, - child: TabBarView( - controller: tabController, - physics: const NeverScrollableScrollPhysics(), - children: [ - OutputResult( - text: _getResultOutput(), - isSelected: tabController.index == 0, - ), - if (playgroundController.graphAvailable) - sdk == null - ? Container() - : GraphTab( - graph: - playgroundController.codeRunner.result?.graph ?? '', - sdk: sdk, - direction: graphDirection, - ), - ], - ), - ), - ); - } -} diff --git a/playground/frontend/playground_components/lib/src/widgets/output/output_result.dart b/playground/frontend/playground_components/lib/src/widgets/output/output_result.dart deleted file mode 100644 index 236a4856ff02..000000000000 --- a/playground/frontend/playground_components/lib/src/widgets/output/output_result.dart +++ /dev/null @@ -1,69 +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'; - -import '../../constants/sizes.dart'; -import '../../theme/theme.dart'; - -class OutputResult extends StatefulWidget { - final String text; - final bool isSelected; - - const OutputResult({Key? key, required this.text, required this.isSelected}) - : super(key: key); - - @override - State createState() => _OutputResultState(); -} - -class _OutputResultState extends State { - final ScrollController _scrollController = ScrollController(); - - @override - void didUpdateWidget(OutputResult oldWidget) { - WidgetsBinding.instance.addPostFrameCallback((_) { - if (_scrollController.hasClients && - !widget.isSelected && - oldWidget.text != widget.text) { - _scrollController.jumpTo(_scrollController.position.maxScrollExtent); - } - }); - super.didUpdateWidget(oldWidget); - } - - @override - Widget build(BuildContext context) { - final ext = Theme.of(context).extension()!; - return SingleChildScrollView( - controller: _scrollController, - child: Scrollbar( - thumbVisibility: true, - trackVisibility: true, - controller: _scrollController, - child: Padding( - padding: const EdgeInsets.all(BeamSizes.size16), - child: SelectableText( - widget.text, - style: ext.codeRootStyle, - ), - ), - ), - ); - } -} diff --git a/playground/frontend/playground_components/lib/src/widgets/output/output_tab.dart b/playground/frontend/playground_components/lib/src/widgets/output/output_tab.dart index 2fb40e9ecd1a..1dc3ab1d565d 100644 --- a/playground/frontend/playground_components/lib/src/widgets/output/output_tab.dart +++ b/playground/frontend/playground_components/lib/src/widgets/output/output_tab.dart @@ -16,94 +16,43 @@ * limitations under the License. */ -import 'package:aligned_dialog/aligned_dialog.dart'; import 'package:flutter/material.dart'; import '../../constants/sizes.dart'; -import '../../controllers/playground_controller.dart'; -import 'result_filter_popover.dart'; +import '../unread/marker.dart'; -class OutputTab extends StatefulWidget { - final PlaygroundController playgroundController; - final String name; - final bool isSelected; - - /// Used to check if an update marker should be added on the tab - final String maxPossibleContent; - final bool hasFilter; +const _horizontalPadding = BeamSizes.size8; +class OutputTab extends StatelessWidget { const OutputTab({ - super.key, - required this.playgroundController, - required this.name, - required this.isSelected, - required this.maxPossibleContent, - this.hasFilter = false, + required this.isUnread, + required this.title, + this.trailing, }); - @override - State createState() => _OutputTabState(); -} - -class _OutputTabState extends State { - bool hasNewContent = false; - - @override - void didUpdateWidget(OutputTab oldWidget) { - if (widget.isSelected && hasNewContent) { - setState(() { - hasNewContent = false; - }); - } else if (!widget.isSelected && - widget.maxPossibleContent.isNotEmpty && - oldWidget.maxPossibleContent != widget.maxPossibleContent) { - setState(() { - hasNewContent = true; - }); - } - super.didUpdateWidget(oldWidget); - } + final bool isUnread; + final String title; + final Widget? trailing; @override Widget build(BuildContext context) { - final themeData = Theme.of(context); - return Tab( child: Wrap( - direction: Axis.horizontal, alignment: WrapAlignment.center, spacing: BeamSizes.size8, children: [ - Text(widget.name), - widget.hasFilter - ? GestureDetector( - onTap: () { - showAlignedDialog( - context: context, - builder: (dialogContext) => ResultFilterPopover( - playgroundController: widget.playgroundController, - ), - followerAnchor: Alignment.topLeft, - targetAnchor: Alignment.topLeft, - barrierColor: Colors.transparent, - ); - }, - child: Icon( - Icons.filter_alt_outlined, - size: BeamIconSizes.small, - color: themeData.primaryColor, - ), - ) - : const SizedBox(), - if (hasNewContent) - Container( - width: BeamIconSizes.xs, - height: BeamIconSizes.xs, - decoration: BoxDecoration( - color: themeData.primaryColor, - shape: BoxShape.circle, - ), + const SizedBox(width: _horizontalPadding), + Text(title), + if (trailing != null) trailing!, + SizedBox( + width: _horizontalPadding, + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 500), + child: isUnread + ? const UnreadMarkerWidget() + : const Opacity(opacity: 0, child: UnreadMarkerWidget()), ), + ), ], ), ); diff --git a/playground/frontend/playground_components/lib/src/widgets/output/result_filter_bubble.dart b/playground/frontend/playground_components/lib/src/widgets/output/result_filter_bubble.dart index 2a93919e0e5c..68c557d5b19b 100644 --- a/playground/frontend/playground_components/lib/src/widgets/output/result_filter_bubble.dart +++ b/playground/frontend/playground_components/lib/src/widgets/output/result_filter_bubble.dart @@ -18,35 +18,35 @@ import 'package:flutter/material.dart'; -import '../../controllers/playground_controller.dart'; -import '../../models/outputs.dart'; +import '../../enums/result_filter.dart'; import '../bubble.dart'; class ResultFilterBubble extends StatelessWidget { - final PlaygroundController playgroundController; - final OutputType type; - final String name; + final ResultFilterEnum groupValue; + final ValueChanged onChanged; + final String title; + final ResultFilterEnum value; const ResultFilterBubble({ super.key, - required this.playgroundController, - required this.type, - required this.name, + required this.groupValue, + required this.onChanged, + required this.title, + required this.value, }); @override Widget build(BuildContext context) { - final isSelected = - type == playgroundController.outputTypeController.outputFilterType; + final isSelected = value == groupValue; return BubbleWidget( isSelected: isSelected, onTap: () { if (!isSelected) { - playgroundController.outputTypeController.setOutputFilterType(type); + onChanged(value); } }, - title: name, + title: title, ); } } diff --git a/playground/frontend/playground_components/lib/src/widgets/output/output_tabs.dart b/playground/frontend/playground_components/lib/src/widgets/output/result_filter_button.dart similarity index 55% rename from playground/frontend/playground_components/lib/src/widgets/output/output_tabs.dart rename to playground/frontend/playground_components/lib/src/widgets/output/result_filter_button.dart index f36a6389fc23..5ac2e9e5559f 100644 --- a/playground/frontend/playground_components/lib/src/widgets/output/output_tabs.dart +++ b/playground/frontend/playground_components/lib/src/widgets/output/result_filter_button.dart @@ -16,45 +16,42 @@ * limitations under the License. */ -import 'package:easy_localization/easy_localization.dart'; +import 'dart:async'; + +import 'package:aligned_dialog/aligned_dialog.dart'; import 'package:flutter/material.dart'; +import '../../constants/sizes.dart'; import '../../controllers/playground_controller.dart'; -import 'output_tab.dart'; - -class OutputTabs extends StatelessWidget { - final PlaygroundController playgroundController; - final TabController tabController; +import 'result_filter_popover.dart'; - const OutputTabs({ - super.key, +class ResultFilterButton extends StatelessWidget { + const ResultFilterButton({ required this.playgroundController, - required this.tabController, }); + final PlaygroundController playgroundController; + @override Widget build(BuildContext context) { - return SizedBox( - width: 300, - child: TabBar( - controller: tabController, - tabs: [ - OutputTab( - playgroundController: playgroundController, - name: 'widgets.output.result'.tr(), - isSelected: tabController.index == 0, - maxPossibleContent: playgroundController.codeRunner.resultLogOutput, - hasFilter: true, - ), - if (playgroundController.graphAvailable) - OutputTab( + return GestureDetector( + onTap: () { + unawaited( + showAlignedDialog( + context: context, + builder: (dialogContext) => ResultFilterPopover( playgroundController: playgroundController, - name: 'widgets.output.graph'.tr(), - isSelected: tabController.index == 2, - maxPossibleContent: - playgroundController.codeRunner.result?.graph ?? '', ), - ], + followerAnchor: Alignment.topLeft, + targetAnchor: Alignment.topLeft, + barrierColor: Colors.transparent, + ), + ); + }, + child: Icon( + Icons.filter_alt_outlined, + size: BeamIconSizes.small, + color: Theme.of(context).primaryColor, ), ); } diff --git a/playground/frontend/playground_components/lib/src/widgets/output/result_filter_popover.dart b/playground/frontend/playground_components/lib/src/widgets/output/result_filter_popover.dart index 671f7c566139..51948aac622c 100644 --- a/playground/frontend/playground_components/lib/src/widgets/output/result_filter_popover.dart +++ b/playground/frontend/playground_components/lib/src/widgets/output/result_filter_popover.dart @@ -19,8 +19,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:playground_components/playground_components.dart'; - +import '../../constants/sizes.dart'; +import '../../controllers/playground_controller.dart'; +import '../../enums/result_filter.dart'; import 'result_filter_bubble.dart'; const kPopoverWidth = 240.0; @@ -52,26 +53,24 @@ class ResultFilterPopover extends StatelessWidget { vertical: BeamSizes.size4, ), child: AnimatedBuilder( - animation: playgroundController.outputTypeController, - builder: (context, child) => Row( - children: [ - ResultFilterBubble( - playgroundController: playgroundController, - type: OutputType.all, - name: 'widgets.output.filter.all'.tr(), - ), - ResultFilterBubble( - playgroundController: playgroundController, - type: OutputType.log, - name: 'widgets.output.filter.log'.tr(), - ), - ResultFilterBubble( - playgroundController: playgroundController, - type: OutputType.output, - name: 'widgets.output.filter.output'.tr(), - ), - ], - ), + animation: playgroundController.resultFilterController, + builder: (context, child) { + final groupValue = playgroundController + .resultFilterController.value; + + return Row( + children: [ + for (final value in ResultFilterEnum.values) + ResultFilterBubble( + groupValue: groupValue, + onChanged: playgroundController + .resultFilterController.setValue, + title: 'widgets.output.filter.${value.name}'.tr(), + value: value, + ), + ], + ); + }, ), ), ], diff --git a/playground/frontend/playground_components/lib/src/widgets/output/result_tab.dart b/playground/frontend/playground_components/lib/src/widgets/output/result_tab.dart new file mode 100644 index 000000000000..e27f09968966 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/widgets/output/result_tab.dart @@ -0,0 +1,49 @@ +/* + * 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:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; + +import '../../controllers/playground_controller.dart'; +import '../../enums/unread_entry.dart'; +import '../unread/builder.dart'; +import 'output_tab.dart'; +import 'result_filter_button.dart'; + +class ResultTab extends StatelessWidget { + const ResultTab({ + required this.playgroundController, + }); + + final PlaygroundController playgroundController; + + @override + Widget build(BuildContext context) { + return UnreadBuilder( + controller: playgroundController.codeRunner.unreadController, + unreadKey: UnreadEntryEnum.result, + builder: (context, isUnread) => OutputTab( + isUnread: isUnread, + title: 'widgets.output.result'.tr(), + trailing: ResultFilterButton( + playgroundController: playgroundController, + ), + ), + ); + } +} diff --git a/playground/frontend/playground_components/lib/src/widgets/output/result_tab_content.dart b/playground/frontend/playground_components/lib/src/widgets/output/result_tab_content.dart new file mode 100644 index 000000000000..4da643d6d12a --- /dev/null +++ b/playground/frontend/playground_components/lib/src/widgets/output/result_tab_content.dart @@ -0,0 +1,85 @@ +/* + * 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 '../../constants/sizes.dart'; +import '../../controllers/playground_controller.dart'; +import '../../enums/result_filter.dart'; +import '../../enums/unread_entry.dart'; +import '../../theme/theme.dart'; +import '../unread/clearer.dart'; + +class ResultTabContent extends StatefulWidget { + const ResultTabContent({ + required this.playgroundController, + }); + + final PlaygroundController playgroundController; + + @override + State createState() => _ResultTabContentState(); +} + +class _ResultTabContentState extends State { + final ScrollController _scrollController = ScrollController(); + + @override + Widget build(BuildContext context) { + final ext = Theme.of(context).extension()!; + + return UnreadClearer( + controller: widget.playgroundController.codeRunner.unreadController, + unreadKey: UnreadEntryEnum.result, + child: AnimatedBuilder( + animation: widget.playgroundController.codeRunner, + builder: (context, child) => SingleChildScrollView( + controller: _scrollController, + child: Scrollbar( + thumbVisibility: true, + trackVisibility: true, + controller: _scrollController, + child: Padding( + padding: const EdgeInsets.all(BeamSizes.size16), + child: AnimatedBuilder( + animation: widget.playgroundController.resultFilterController, + builder: (context, child) => SelectableText( + _getText(), + style: ext.codeRootStyle, + ), + ), + ), + ), + ), + ), + ); + } + + String _getText() { + final filter = widget.playgroundController.resultFilterController.value; + + switch (filter) { + case ResultFilterEnum.log: + return widget.playgroundController.codeRunner.resultLog; + case ResultFilterEnum.output: + return widget.playgroundController.codeRunner.resultOutput; + case ResultFilterEnum.all: + return widget.playgroundController.codeRunner.resultLogOutput; + } + } +} diff --git a/playground/frontend/playground_components/lib/src/widgets/tabs/tab_bar.dart b/playground/frontend/playground_components/lib/src/widgets/tabs/tab_bar.dart index 7c4a864f7b33..19419c6693cc 100644 --- a/playground/frontend/playground_components/lib/src/widgets/tabs/tab_bar.dart +++ b/playground/frontend/playground_components/lib/src/widgets/tabs/tab_bar.dart @@ -25,17 +25,26 @@ class BeamTabBar extends StatelessWidget { const BeamTabBar({ super.key, required this.tabs, + this.hasPadding = false, }); + final bool hasPadding; final Map tabs; @override Widget build(BuildContext context) { - return SizedBox( - height: BeamSizes.tabBarHeight, - child: KeyedTabBar.withDefaultController( - isScrollable: true, - tabs: {for (final key in tabs.keys) key: Tab(child: tabs[key])}, + return Padding( + padding: hasPadding + ? const EdgeInsets.symmetric(horizontal: BeamSizes.size16) + : EdgeInsets.zero, + child: SizedBox( + height: BeamSizes.tabBarHeight, + child: KeyedTabBar.withDefaultController( + isScrollable: true, + tabs: { + for (final key in tabs.keys) key: Tab(child: tabs[key]), + }, + ), ), ); } diff --git a/playground/frontend/playground_components/lib/src/widgets/unread/builder.dart b/playground/frontend/playground_components/lib/src/widgets/unread/builder.dart new file mode 100644 index 000000000000..f3647a7dc850 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/widgets/unread/builder.dart @@ -0,0 +1,46 @@ +/* + * 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/widgets.dart'; + +import '../../controllers/unread_controller.dart'; + +/// Calls [builder] when [controller] changes and passes the unread status +/// of [unreadKey]. +class UnreadBuilder extends StatelessWidget { + const UnreadBuilder({ + super.key, + required this.builder, + required this.controller, + required this.unreadKey, + }); + + final Widget Function(BuildContext context, bool isUnread) builder; + final UnreadController controller; + final Object unreadKey; + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: controller, + builder: (context, child) { + return builder(context, controller.isUnread(unreadKey)); + }, + ); + } +} diff --git a/playground/frontend/playground_components/lib/src/widgets/unread/clearer.dart b/playground/frontend/playground_components/lib/src/widgets/unread/clearer.dart new file mode 100644 index 000000000000..011e0b458890 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/widgets/unread/clearer.dart @@ -0,0 +1,58 @@ +/* + * 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/widgets.dart'; +import 'package:visibility_detector/visibility_detector.dart'; + +import '../../controllers/unread_controller.dart'; + +/// Clears the unread status of [unreadKey] when built and at least +/// partially visible. +class UnreadClearer extends StatelessWidget { + const UnreadClearer({ + super.key, + required this.child, + required this.controller, + required this.unreadKey, + }); + + final Widget child; + final UnreadController controller; + final T unreadKey; + + @override + Widget build(BuildContext context) { + return VisibilityDetector( + key: Key(unreadKey.toString()), + onVisibilityChanged: (info) { + if (info.visibleFraction > 0) { + controller.markRead(unreadKey); + } + }, + child: AnimatedBuilder( + animation: controller, + builder: (context, _) { + WidgetsBinding.instance.addPostFrameCallback((_) { + controller.markRead(unreadKey); + }); + return child; + }, + ), + ); + } +} diff --git a/playground/frontend/playground_components/lib/src/widgets/unread/marker.dart b/playground/frontend/playground_components/lib/src/widgets/unread/marker.dart new file mode 100644 index 000000000000..977299f03fe8 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/widgets/unread/marker.dart @@ -0,0 +1,37 @@ +/* + * 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 '../../constants/sizes.dart'; + +class UnreadMarkerWidget extends StatelessWidget { + const UnreadMarkerWidget(); + + @override + Widget build(BuildContext context) { + return Container( + width: BeamIconSizes.xs, + height: BeamIconSizes.xs, + decoration: BoxDecoration( + color: Theme.of(context).primaryColor, + shape: BoxShape.circle, + ), + ); + } +} diff --git a/playground/frontend/playground_components/pubspec.yaml b/playground/frontend/playground_components/pubspec.yaml index 34c9c08b563a..a7c35d9ffa15 100644 --- a/playground/frontend/playground_components/pubspec.yaml +++ b/playground/frontend/playground_components/pubspec.yaml @@ -26,7 +26,7 @@ environment: dependencies: aligned_dialog: ^0.0.6 - app_state: ^0.9.2 + app_state: ^0.9.3 collection: ^1.16.0 connectivity_plus: ^2.3.9 easy_localization: ^3.0.1 @@ -35,7 +35,7 @@ dependencies: enum_map: ^0.2.1 equatable: ^2.0.5 flutter: { sdk: flutter } - flutter_code_editor: ^0.2.9 + flutter_code_editor: ^0.2.12 flutter_markdown: ^0.6.12 flutter_svg: ^2.0.1 fluttertoast: ^8.1.1 @@ -52,6 +52,7 @@ dependencies: provider: ^6.0.3 rxdart: ^0.27.7 shared_preferences: ^2.0.15 + visibility_detector: ^0.3.3 yaml: ^3.1.1 dev_dependencies: diff --git a/playground/frontend/playground_components/test/src/controllers/example_loaders/examples_loader_test.mocks.dart b/playground/frontend/playground_components/test/src/controllers/example_loaders/examples_loader_test.mocks.dart index 982c97d7f512..3c5fa2b5c74b 100644 --- a/playground/frontend/playground_components/test/src/controllers/example_loaders/examples_loader_test.mocks.dart +++ b/playground/frontend/playground_components/test/src/controllers/example_loaders/examples_loader_test.mocks.dart @@ -11,10 +11,10 @@ import 'package:playground_components/src/cache/example_cache.dart' as _i2; import 'package:playground_components/src/controllers/code_runner.dart' as _i5; import 'package:playground_components/src/controllers/example_loaders/examples_loader.dart' as _i3; -import 'package:playground_components/src/controllers/output_filter_type_controller.dart' - as _i4; import 'package:playground_components/src/controllers/playground_controller.dart' as _i12; +import 'package:playground_components/src/controllers/result_filter_controller.dart' + as _i4; import 'package:playground_components/src/controllers/snippet_editing_controller.dart' as _i7; import 'package:playground_components/src/models/category_with_examples.dart' @@ -66,9 +66,9 @@ class _FakeExamplesLoader_1 extends _i1.SmartFake ); } -class _FakeOutputFilterTypeController_2 extends _i1.SmartFake - implements _i4.OutputFilterTypeController { - _FakeOutputFilterTypeController_2( +class _FakeResultFilterController_2 extends _i1.SmartFake + implements _i4.ResultFilterController { + _FakeResultFilterController_2( Object parent, Invocation parentInvocation, ) : super( @@ -176,14 +176,13 @@ class MockPlaygroundController extends _i1.Mock ), ) as _i3.ExamplesLoader); @override - _i4.OutputFilterTypeController get outputTypeController => - (super.noSuchMethod( - Invocation.getter(#outputTypeController), - returnValue: _FakeOutputFilterTypeController_2( + _i4.ResultFilterController get resultFilterController => (super.noSuchMethod( + Invocation.getter(#resultFilterController), + returnValue: _FakeResultFilterController_2( this, - Invocation.getter(#outputTypeController), + Invocation.getter(#resultFilterController), ), - ) as _i4.OutputFilterTypeController); + ) as _i4.ResultFilterController); @override _i5.CodeRunner get codeRunner => (super.noSuchMethod( Invocation.getter(#codeRunner), @@ -233,6 +232,23 @@ class MockPlaygroundController extends _i1.Mock returnValueForMissingStub: null, ); @override + _i6.BeamShortcut get showSuggestionsShortcut => (super.noSuchMethod( + Invocation.getter(#showSuggestionsShortcut), + returnValue: _FakeBeamShortcut_4( + this, + Invocation.getter(#showSuggestionsShortcut), + ), + ) as _i6.BeamShortcut); + @override + set showSuggestionsShortcut(_i6.BeamShortcut? _showSuggestionsShortcut) => + super.noSuchMethod( + Invocation.setter( + #showSuggestionsShortcut, + _showSuggestionsShortcut, + ), + returnValueForMissingStub: null, + ); + @override String get examplesTitle => (super.noSuchMethod( Invocation.getter(#examplesTitle), returnValue: '', @@ -338,6 +354,14 @@ class MockPlaygroundController extends _i1.Mock returnValueForMissingStub: _i14.Future.value(), ) as _i14.Future); @override + void showSuggestions() => super.noSuchMethod( + Invocation.method( + #showSuggestions, + [], + ), + returnValueForMissingStub: null, + ); + @override void resetErrorMessageText() => super.noSuchMethod( Invocation.method( #resetErrorMessageText, diff --git a/playground/frontend/playground_components/test/src/controllers/playground_controller_test.dart b/playground/frontend/playground_components/test/src/controllers/playground_controller_test.dart index b6075a5ea4d4..44d1a3bb05eb 100644 --- a/playground/frontend/playground_components/test/src/controllers/playground_controller_test.dart +++ b/playground/frontend/playground_components/test/src/controllers/playground_controller_test.dart @@ -49,8 +49,8 @@ Future main() async { expect(controller.sdk, Sdk.go); }); - test('Initial value of examplesTitle should be equal to kTitle', () { - expect(controller.examplesTitle, 'Catalog'); + test('Initial value of examplesTitle', () { + expect(controller.examplesTitle, 'examples.defaultTitle'); }); test('Initial value of isCodeRunning should be false', () { diff --git a/playground/frontend/playground_components/test/src/controllers/unread_controller_test.dart b/playground/frontend/playground_components/test/src/controllers/unread_controller_test.dart new file mode 100644 index 000000000000..d703d2ddba04 --- /dev/null +++ b/playground/frontend/playground_components/test/src/controllers/unread_controller_test.dart @@ -0,0 +1,77 @@ +/* + * 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_components/src/controllers/unread_controller.dart'; + +void main() { + UnreadController controller = UnreadController(); + int notified = 0; + + setUp(() { + controller = UnreadController(); + notified = 0; + + controller.addListener(() { + notified++; + }); + }); + + group('UnreadController', () { + test('setValue, isUnread, clearKey, clear', () { + controller.setValue('a', 1); + expect(controller.isUnread('a'), true); + expect(controller.isUnread('b'), false); + expect(notified, 1); + + controller.setValue('a', 1); + expect(controller.isUnread('a'), true); + expect(controller.isUnread('b'), false); + expect(notified, 1); + + controller.setValue('a', 2); + expect(controller.isUnread('a'), true); + expect(controller.isUnread('b'), false); + expect(notified, 2); + + controller.setValue('b', 1); + expect(controller.isUnread('a'), true); + expect(controller.isUnread('b'), true); + expect(notified, 3); + + controller.markRead('b'); + expect(controller.isUnread('a'), true); + expect(controller.isUnread('b'), false); + expect(notified, 4); + + controller.setValue('b', 1); + expect(controller.isUnread('a'), true); + expect(controller.isUnread('b'), false); + expect(notified, 4); + + controller.setValue('c', 1); + controller.markAllRead(); + expect(controller.isUnread('a'), false); + expect(controller.isUnread('c'), false); + expect(notified, 6); + + controller.markAllRead(); + expect(notified, 6); + }); + }); +} diff --git a/playground/frontend/playground_components/test/src/repositories/code_repository_test.dart b/playground/frontend/playground_components/test/src/repositories/code_repository_test.dart index 6dfd362282f3..2b07ea9d2ebb 100644 --- a/playground/frontend/playground_components/test/src/repositories/code_repository_test.dart +++ b/playground/frontend/playground_components/test/src/repositories/code_repository_test.dart @@ -48,24 +48,39 @@ const kPreparationErrorOutput = 'PreparationErrorOutput'; const kValidationErrorOutput = 'ValidationErrorOutput'; const kRunCodeResponse = RunCodeResponse(pipelineUuid: kPipelineUuid); -const kFinishedStatusResponse = CheckStatusResponse(status: RunCodeStatus.finished,); -const kErrorStatusResponse = CheckStatusResponse(status: RunCodeStatus.unknownError,); -const kRunErrorStatusResponse = CheckStatusResponse(status: RunCodeStatus.runError,); -const kExecutingStatusResponse = CheckStatusResponse(status: RunCodeStatus.executing,); -const kCompileErrorStatusResponse = - CheckStatusResponse(status: RunCodeStatus.compileError,); -const kValidationErrorStatusResponse = - CheckStatusResponse(status: RunCodeStatus.validationError,); -const kPreparationErrorStatusResponse = - CheckStatusResponse(status: RunCodeStatus.preparationError,); +const kFinishedStatusResponse = CheckStatusResponse( + status: RunCodeStatus.finished, +); +const kErrorStatusResponse = CheckStatusResponse( + status: RunCodeStatus.unknownError, +); +const kRunErrorStatusResponse = CheckStatusResponse( + status: RunCodeStatus.runError, +); +const kExecutingStatusResponse = CheckStatusResponse( + status: RunCodeStatus.executing, +); +const kCompileErrorStatusResponse = CheckStatusResponse( + status: RunCodeStatus.compileError, +); +const kValidationErrorStatusResponse = CheckStatusResponse( + status: RunCodeStatus.validationError, +); +const kPreparationErrorStatusResponse = CheckStatusResponse( + status: RunCodeStatus.preparationError, +); const kRunOutputResponse = OutputResponse(output: kRunOutput); const kLogOutputResponse = OutputResponse(output: kLogOutput); const kCompileOutputResponse = OutputResponse(output: kCompileOutput); const kRunErrorOutputResponse = OutputResponse(output: kRunErrorOutput); const kGraphResponse = OutputResponse(output: kGraphOutput); -const kValidationErrorOutputResponse = OutputResponse(output: kValidationErrorOutput); -const kPreparationErrorOutputResponse = OutputResponse(output: kPreparationErrorOutput); + +const kValidationErrorOutputResponse = + OutputResponse(output: kValidationErrorOutput); + +const kPreparationErrorOutputResponse = + OutputResponse(output: kPreparationErrorOutput); @GenerateMocks([CodeClient]) void main() { @@ -104,15 +119,17 @@ void main() { stream, emitsInOrder([ RunCodeResult( - status: RunCodeStatus.preparation, log: kProcessingStartedText, + sdk: kRequestMock.sdk, + status: RunCodeStatus.preparation, ), RunCodeResult( + graph: kGraphOutput, + log: kProcessingStartedText + kLogOutput, + output: kRunOutput + kRunErrorOutput, pipelineUuid: kPipelineUuid, + sdk: kRequestMock.sdk, status: RunCodeStatus.finished, - output: kRunOutput + kRunErrorOutput, - log: kProcessingStartedText + kLogOutput, - graph: kGraphOutput ), ]), ); @@ -139,7 +156,7 @@ void main() { (_) async => kLogOutputResponse, ); when(client.getGraphOutput(kPipelineUuid)).thenAnswer( - (_) async => kGraphResponse, + (_) async => kGraphResponse, ); // test variables @@ -151,15 +168,17 @@ void main() { stream, emitsInOrder([ RunCodeResult( - status: RunCodeStatus.preparation, log: kProcessingStartedText, + sdk: kRequestMock.sdk, + status: RunCodeStatus.preparation, ), RunCodeResult( + graph: '', + log: kProcessingStartedText, + output: kCompileOutput, pipelineUuid: kPipelineUuid, + sdk: kRequestMock.sdk, status: RunCodeStatus.compileError, - output: kCompileOutput, - log: kProcessingStartedText, - graph: '', ), ]), ); @@ -175,12 +194,11 @@ void main() { when(client.checkStatus(kPipelineUuid)).thenAnswer( (_) async => kValidationErrorStatusResponse, ); - when(client.getValidationErrorOutput(kPipelineUuid)) - .thenAnswer( + when(client.getValidationErrorOutput(kPipelineUuid)).thenAnswer( (_) async => kValidationErrorOutputResponse, ); when(client.getGraphOutput(kPipelineUuid)).thenAnswer( - (_) async => kGraphResponse, + (_) async => kGraphResponse, ); // test variables @@ -192,14 +210,16 @@ void main() { stream, emitsInOrder([ RunCodeResult( - status: RunCodeStatus.preparation, log: kProcessingStartedText, + sdk: kRequestMock.sdk, + status: RunCodeStatus.preparation, ), RunCodeResult( - status: RunCodeStatus.validationError, - output: kValidationErrorOutput, - log: kProcessingStartedText, graph: '', + log: kProcessingStartedText, + output: kValidationErrorOutput, + sdk: kRequestMock.sdk, + status: RunCodeStatus.validationError, ), ]), ); @@ -215,12 +235,11 @@ void main() { when(client.checkStatus(kPipelineUuid)).thenAnswer( (_) async => kPreparationErrorStatusResponse, ); - when(client.getPreparationErrorOutput(kPipelineUuid)) - .thenAnswer( + when(client.getPreparationErrorOutput(kPipelineUuid)).thenAnswer( (_) async => kPreparationErrorOutputResponse, ); when(client.getGraphOutput(kPipelineUuid)).thenAnswer( - (_) async => kGraphResponse, + (_) async => kGraphResponse, ); // test variables @@ -232,14 +251,16 @@ void main() { stream, emitsInOrder([ RunCodeResult( - status: RunCodeStatus.preparation, + sdk: kRequestMock.sdk, log: kProcessingStartedText, + status: RunCodeStatus.preparation, ), RunCodeResult( - status: RunCodeStatus.preparationError, - output: kPreparationErrorOutput, - log: kProcessingStartedText, graph: '', + log: kProcessingStartedText, + output: kPreparationErrorOutput, + sdk: kRequestMock.sdk, + status: RunCodeStatus.preparationError, ), ]), ); @@ -268,7 +289,7 @@ void main() { (_) async => kLogOutputResponse, ); when(client.getGraphOutput(kPipelineUuid)).thenAnswer( - (_) async => kGraphResponse, + (_) async => kGraphResponse, ); // test variables @@ -280,15 +301,17 @@ void main() { stream, emitsInOrder([ RunCodeResult( - status: RunCodeStatus.preparation, log: kProcessingStartedText, + sdk: kRequestMock.sdk, + status: RunCodeStatus.preparation, ), RunCodeResult( + graph: '', + log: kProcessingStartedText, + output: kRunErrorOutput, pipelineUuid: kPipelineUuid, + sdk: kRequestMock.sdk, status: RunCodeStatus.runError, - output: kRunErrorOutput, - log: kProcessingStartedText, - graph: '', ), ]), ); @@ -320,7 +343,7 @@ void main() { (_) async => kLogOutputResponse, ); when(client.getGraphOutput(kPipelineUuid)).thenAnswer( - (_) async => kGraphResponse, + (_) async => kGraphResponse, ); // test variables @@ -332,29 +355,33 @@ void main() { stream, emitsInOrder([ RunCodeResult( - status: RunCodeStatus.preparation, log: kProcessingStartedText, + sdk: kRequestMock.sdk, + status: RunCodeStatus.preparation, ), RunCodeResult( + graph: kGraphOutput, + log: kProcessingStartedText + kLogOutput, + output: kRunOutput, pipelineUuid: kPipelineUuid, + sdk: kRequestMock.sdk, status: RunCodeStatus.executing, - output: kRunOutput, - log: kProcessingStartedText + kLogOutput, - graph: kGraphOutput, ), RunCodeResult( + graph: kGraphOutput, + log: kProcessingStartedText + kLogOutput * 2, + output: kRunOutput * 2, pipelineUuid: kPipelineUuid, + sdk: kRequestMock.sdk, status: RunCodeStatus.executing, - output: kRunOutput * 2, - log: kProcessingStartedText + kLogOutput * 2, - graph: kGraphOutput, ), RunCodeResult( + graph: kGraphOutput, + log: kProcessingStartedText + kLogOutput * 3, + output: kRunOutput * 3 + kRunErrorOutput, pipelineUuid: kPipelineUuid, + sdk: kRequestMock.sdk, status: RunCodeStatus.finished, - output: kRunOutput * 3 + kRunErrorOutput, - log: kProcessingStartedText + kLogOutput * 3, - graph: kGraphOutput, ), ]), ); diff --git a/playground/frontend/playground_components_dev/lib/src/common_finders.dart b/playground/frontend/playground_components_dev/lib/src/common_finders.dart index 4180ae02a0d1..0b36bd79714f 100644 --- a/playground/frontend/playground_components_dev/lib/src/common_finders.dart +++ b/playground/frontend/playground_components_dev/lib/src/common_finders.dart @@ -21,6 +21,8 @@ import 'package:flutter_code_editor/flutter_code_editor.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:playground_components/playground_components.dart'; +import 'finder.dart'; + extension CommonFindersExtension on CommonFinders { Finder codeField() { return byType(CodeField); @@ -31,14 +33,9 @@ extension CommonFindersExtension on CommonFinders { return widgetWithText(OutputTab, 'Graph'); } - Finder outputArea() { - return byType(OutputArea); - } - Finder outputSelectableText() { - final outputArea = find.outputArea(); return find.descendant( - of: outputArea, + of: find.outputWidget(), matching: find.byType(SelectableText), ); } @@ -61,6 +58,6 @@ extension CommonFindersExtension on CommonFinders { } Finder toggleThemeButton() { - return byType(ToggleThemeButton); + return byType(ToggleThemeButton).or(byType(ToggleThemeIconButton)); } } diff --git a/playground/frontend/playground_components_dev/lib/src/expect.dart b/playground/frontend/playground_components_dev/lib/src/expect.dart index 369fc675d9f7..b2f74802d111 100644 --- a/playground/frontend/playground_components_dev/lib/src/expect.dart +++ b/playground/frontend/playground_components_dev/lib/src/expect.dart @@ -21,9 +21,22 @@ import 'dart:ui'; import 'package:flutter_test/flutter_test.dart'; import 'package:playground_components/playground_components.dart'; +import 'examples/example_descriptor.dart'; import 'widget_tester.dart'; -void expectOutput(String text, WidgetTester wt) { +void expectOutput(ExampleDescriptor example, WidgetTester wt) { + if (example.outputTail != null) { + expectOutputEndsWith(example.outputTail, wt); + } else if (example.outputContains != null) { + for (final str in example.outputContains!) { + expectOutputContains(str, wt); + } + } else { + throw AssertionError('No pattern to check example output: ${example.path}'); + } +} + +void expectOutputEquals(String text, WidgetTester wt) { final actualText = wt.findOutputText(); expect(actualText, text); } diff --git a/playground/frontend/playground_components_dev/lib/src/finder.dart b/playground/frontend/playground_components_dev/lib/src/finder.dart index 72d2dd86a389..c4ae3d199e74 100644 --- a/playground/frontend/playground_components_dev/lib/src/finder.dart +++ b/playground/frontend/playground_components_dev/lib/src/finder.dart @@ -24,6 +24,54 @@ extension FinderExtension on Finder { Finder and(Finder another) { return _AndFinder(this, another); } + + Finder or(Finder another) { + return _OrFinder(this, another); + } + + Finder getChildrenByType(Type childType) { + final finders = evaluate(); + final childElements = finders + .map((e) => collectAllElementsFrom(e, skipOffstage: true)) + .expand((e) => e) + .where((e) => e.widget.runtimeType == childType); + + return find.byElementPredicate( + (element) => childElements.contains(element), + ); + } + + Finder horizontallyAt(int index, WidgetTester wt) => + _atIndexOnAxis(index, Axis.horizontal, wt); + + Finder verticallyAt(int index, WidgetTester wt) => + _atIndexOnAxis(index, Axis.vertical, wt); + + Finder _atIndexOnAxis(int index, Axis axis, WidgetTester wt) { + final finders = evaluate(); + + if (index > finders.length - 1) { + throw IndexError(index, finders); + } + + final offsets = <_IndexAndOffset>[]; + + for (int i = 0; i < finders.length; i++) { + offsets.add(_IndexAndOffset(i, wt.getCenter(at(i)))); + } + + offsets.sort( + (a, b) => axis == Axis.vertical + ? _compareDoubles(a.offset.dy, b.offset.dy) + : _compareDoubles(a.offset.dx, b.offset.dx), + ); + + return at(offsets[index].index); + } + + int _compareDoubles(double a, double b) { + return (a - b).sign.toInt(); + } } class _AndFinder extends ChainedFinder { @@ -39,3 +87,24 @@ class _AndFinder extends ChainedFinder { return another.apply(parentCandidates); } } + +class _OrFinder extends ChainedFinder { + _OrFinder(super.parent, this.another); + + final Finder another; + + @override + String get description => '(${parent.description} OR ${another.description})'; + + @override + Iterable filter(Iterable parentCandidates) { + return {...parentCandidates, ...another.evaluate()}; + } +} + +class _IndexAndOffset { + final int index; + final Offset offset; + + _IndexAndOffset(this.index, this.offset); +} diff --git a/playground/frontend/playground_components_dev/lib/src/widget_tester.dart b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart index 7d97cb7144af..730d05154f25 100644 --- a/playground/frontend/playground_components_dev/lib/src/widget_tester.dart +++ b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart @@ -21,15 +21,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_code_editor/flutter_code_editor.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get_it/get_it.dart'; +import 'package:keyed_collection_widgets/keyed_collection_widgets.dart'; import 'package:playground_components/playground_components.dart'; import 'package:provider/provider.dart'; import 'common_finders.dart'; +import 'examples/example_descriptor.dart'; import 'expect.dart'; -const String _kCachedResultsLog = - 'The results of this example are taken from the Apache Beam Playground cache'; - extension WidgetTesterExtension on WidgetTester { //workaround for https://github.com/flutter/flutter/issues/120060 Future enterCodeFieldText(String text) async { @@ -38,6 +37,16 @@ extension WidgetTesterExtension on WidgetTester { codeField.focusNode?.requestFocus(); } + Brightness getBrightness() { + final context = element(find.toggleThemeButton()); + return Theme.of(context).brightness; + } + + Future toggleTheme() async { + await tap(find.toggleThemeButton()); + await pumpAndSettle(); + } + CodeController findOneCodeController() { final codeField = find.codeField(); expect(codeField, findsOneWidget); @@ -45,11 +54,15 @@ extension WidgetTesterExtension on WidgetTester { return widget(codeField).controller; } - TabController findOutputTabController() { - final outputTabs = find.byType(OutputTabs); - expect(outputTabs, findsOneWidget); + KeyedTabController findOutputTabController() { + final beamTabBar = find.descendant( + of: find.outputWidget(), + matching: find.byType(BeamTabBar), + ); + + expect(beamTabBar, findsOneWidget); - return widget(outputTabs).tabController; + return beamTabBar.evaluate().first.getKeyedTabController()!; } String? findOutputText() { @@ -99,33 +112,57 @@ extension WidgetTesterExtension on WidgetTester { } /// Runs and expects that the execution is as fast as it should be for cache. - Future runExpectCached() async { - final dateTimeStart = DateTime.now(); + Future runExpectCached(ExampleDescriptor example) async { + final codeRunner = findPlaygroundController().codeRunner; await tap(find.runOrCancelButton()); - await pumpAndSettle(); + await pump(); + expect(codeRunner.isCodeRunning, true); + + try { + await pumpAndSettle( + const Duration(milliseconds: 100), + EnginePhase.sendSemanticsUpdate, + const Duration(milliseconds: 1000), + ); + + // ignore: avoid_catching_errors + } on FlutterError catch (ex) { + // Expected timeout because for some reason UI updates way longer. + } - expectOutputStartsWith(_kCachedResultsLog, this); - expect( - DateTime.now().difference(dateTimeStart), + expect(codeRunner.isCodeRunning, false); - /// This is the safe threshold considering test delays. - lessThan(const Duration(milliseconds: 3000)), - ); + await pumpAndSettle(); // Let the UI catch up. + + expectOutputStartsWith(kCachedResultsLog, this); + expectOutput(example, this); } /// Runs and expects that the execution is as fast as it should be for cache. - Future runExpectReal() async { + Future modifyRunExpectReal(ExampleDescriptor example) async { + _modifyCodeController(); await tap(find.runOrCancelButton()); await pumpAndSettle(); final actualText = findOutputText(); - expect(actualText, isNot(startsWith(_kCachedResultsLog))); + expect(actualText, isNot(startsWith(kCachedResultsLog))); + expectOutput(example, this); + } + + void _modifyCodeController() { + // Add a character into the first comment. + // This relies on that the position 10 is inside a license comment. + final controller = findOneCodeController(); + final text = controller.fullText; + + // ignore: prefer_interpolation_to_compose_strings + controller.fullText = text.substring(0, 10) + '+' + text.substring(10); } Future navigateAndSettle(String urlString) async { final url = Uri.parse(urlString); - print('Navigating: $url\n'); + print('Navigating: $url\n'); // ignore: avoid_print await _navigate(url); // These appears to be the minimal reliable delay. diff --git a/playground/frontend/playground_components_dev/pubspec.yaml b/playground/frontend/playground_components_dev/pubspec.yaml index 6064454b229d..381fd3ef296e 100644 --- a/playground/frontend/playground_components_dev/pubspec.yaml +++ b/playground/frontend/playground_components_dev/pubspec.yaml @@ -25,13 +25,14 @@ environment: flutter: '>=3.7.3' dependencies: - app_state: ^0.9.0 + app_state: ^0.9.3 flutter: { sdk: flutter } - flutter_code_editor: ^0.2.9 + flutter_code_editor: ^0.2.12 flutter_test: { sdk: flutter } get_it: ^7.2.0 highlight: ^0.7.0 http: ^0.13.5 + keyed_collection_widgets: ^0.4.3 playground_components: { path: ../playground_components } provider: ^6.0.3 total_lints: ^2.18.0 diff --git a/playground/frontend/pubspec.lock b/playground/frontend/pubspec.lock index 9eb638f50cd5..6b1ce7e65e75 100644 --- a/playground/frontend/pubspec.lock +++ b/playground/frontend/pubspec.lock @@ -394,10 +394,10 @@ packages: dependency: "direct dev" description: name: flutter_code_editor - sha256: a5d46c1740709997bbf6c2d21168b54db2a521606cbec7b1a01b79339ad4359e + sha256: a220a7dcd197d793f46f2c2132551128bb41b1ccc08f7afb781223e1b5e400a1 url: "https://pub.dev" source: hosted - version: "0.2.10" + version: "0.2.12" flutter_driver: dependency: transitive description: flutter @@ -444,10 +444,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: f999d84ad2efda1c4c3956e7968b713b3a24b06f0a0e4798e844e16bbb9bb70b + sha256: b9be7260c1fdbe0090a11d9d356fc2c88e14cf33407fc0c1829d76ab13808035 url: "https://pub.dev" source: hosted - version: "2.0.0+1" + version: "2.0.1" flutter_test: dependency: "direct dev" description: flutter @@ -1279,6 +1279,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + visibility_detector: + dependency: transitive + description: + name: visibility_detector + sha256: "15c54a459ec2c17b4705450483f3d5a2858e733aee893dcee9d75fd04814940d" + url: "https://pub.dev" + source: hosted + version: "0.3.3" vm_service: dependency: transitive description: diff --git a/playground/frontend/pubspec.yaml b/playground/frontend/pubspec.yaml index ad7c230ae27c..550d84f0246e 100644 --- a/playground/frontend/pubspec.yaml +++ b/playground/frontend/pubspec.yaml @@ -27,7 +27,7 @@ environment: dependencies: akvelon_flutter_issue_106664_workaround: ^0.1.2 aligned_dialog: ^0.0.6 - app_state: ^0.9.2 + app_state: ^0.9.3 collection: ^1.15.0 easy_localization: ^3.0.1 easy_localization_ext: ^0.1.1 @@ -53,7 +53,7 @@ dependencies: dev_dependencies: build_runner: ^2.1.4 fake_async: ^1.3.0 - flutter_code_editor: ^0.2.9 + flutter_code_editor: ^0.2.12 flutter_lints: ^2.0.1 flutter_test: { sdk: flutter } integration_test: { sdk: flutter } From 1e17d04ee8cb4e29349898efb1446f145207b614 Mon Sep 17 00:00:00 2001 From: Alexey Inkin Date: Fri, 24 Feb 2023 14:40:30 +0400 Subject: [PATCH 44/52] Change example paths to IDs in integration tests --- .../lib/src/examples/go/minimal_word_count.dart | 2 +- .../lib/src/examples/go/word_count.dart | 2 +- .../lib/src/examples/java/aggregation_max.dart | 2 +- .../lib/src/examples/java/minimal_word_count.dart | 2 +- .../lib/src/examples/python/aggregation_mean.dart | 2 +- .../lib/src/examples/python/word_count_with_metrics.dart | 2 +- .../lib/src/examples/scio/minimal_word_count.dart | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/playground/frontend/playground_components_dev/lib/src/examples/go/minimal_word_count.dart b/playground/frontend/playground_components_dev/lib/src/examples/go/minimal_word_count.dart index 50a11e859265..dc28a5976ab2 100644 --- a/playground/frontend/playground_components_dev/lib/src/examples/go/minimal_word_count.dart +++ b/playground/frontend/playground_components_dev/lib/src/examples/go/minimal_word_count.dart @@ -24,7 +24,7 @@ const goMinimalWordCount = ExampleDescriptor( // 'MinimalWordCount', contextLine1Based: 69, - dbPath: 'SDK_GO/PRECOMPILED_OBJECT_TYPE_EXAMPLE/MinimalWordCount', + dbPath: 'SDK_GO_MinimalWordCount', path: '/sdks/go/examples/minimal_wordcount/minimal_wordcount.go', sdk: Sdk.go, diff --git a/playground/frontend/playground_components_dev/lib/src/examples/go/word_count.dart b/playground/frontend/playground_components_dev/lib/src/examples/go/word_count.dart index da5dd09cbcd3..2c603f697612 100644 --- a/playground/frontend/playground_components_dev/lib/src/examples/go/word_count.dart +++ b/playground/frontend/playground_components_dev/lib/src/examples/go/word_count.dart @@ -22,7 +22,7 @@ import '../example_descriptor.dart'; const goWordCount = ExampleDescriptor( 'WordCount', - dbPath: 'SDK_GO/PRECOMPILED_OBJECT_TYPE_EXAMPLE/WordCount', + dbPath: 'SDK_GO_WordCount', path: '/sdks/go/examples/wordcount/wordcount.go', sdk: Sdk.go, ); diff --git a/playground/frontend/playground_components_dev/lib/src/examples/java/aggregation_max.dart b/playground/frontend/playground_components_dev/lib/src/examples/java/aggregation_max.dart index ecc3d532b63d..256080f69a62 100644 --- a/playground/frontend/playground_components_dev/lib/src/examples/java/aggregation_max.dart +++ b/playground/frontend/playground_components_dev/lib/src/examples/java/aggregation_max.dart @@ -23,7 +23,7 @@ import '../example_descriptor.dart'; const javaAggregationMax = ExampleDescriptor( // 'AggregationMax', - dbPath: 'SDK_JAVA/PRECOMPILED_OBJECT_TYPE_KATA/AggregationMax', + dbPath: 'SDK_JAVA_AggregationMax', path: '/learning/katas/java/Common Transforms/Aggregation/Max/src/org/apache/beam/learning/katas/commontransforms/aggregation/max/Task.java', sdk: Sdk.java, diff --git a/playground/frontend/playground_components_dev/lib/src/examples/java/minimal_word_count.dart b/playground/frontend/playground_components_dev/lib/src/examples/java/minimal_word_count.dart index 6b651f0e7e17..73be7a14004c 100644 --- a/playground/frontend/playground_components_dev/lib/src/examples/java/minimal_word_count.dart +++ b/playground/frontend/playground_components_dev/lib/src/examples/java/minimal_word_count.dart @@ -24,7 +24,7 @@ const javaMinimalWordCount = ExampleDescriptor( // 'MinimalWordCount', contextLine1Based: 71, - dbPath: 'SDK_JAVA/PRECOMPILED_OBJECT_TYPE_EXAMPLE/MinimalWordCount', + dbPath: 'SDK_JAVA_MinimalWordCount', path: '/examples/java/src/main/java/org/apache/beam/examples/MinimalWordCount.java', sdk: Sdk.java, diff --git a/playground/frontend/playground_components_dev/lib/src/examples/python/aggregation_mean.dart b/playground/frontend/playground_components_dev/lib/src/examples/python/aggregation_mean.dart index 0f2c9d1cfb56..e18ac22f502e 100644 --- a/playground/frontend/playground_components_dev/lib/src/examples/python/aggregation_mean.dart +++ b/playground/frontend/playground_components_dev/lib/src/examples/python/aggregation_mean.dart @@ -23,7 +23,7 @@ import '../example_descriptor.dart'; const pythonAggregationMean = ExampleDescriptor( // 'AggregationMean', - dbPath: 'SDK_PYTHON/PRECOMPILED_OBJECT_TYPE_KATA/AggregationMean', + dbPath: 'SDK_PYTHON_AggregationMean', path: '/learning/katas/python/Common Transforms/Aggregation/Mean/task.py', sdk: Sdk.python, diff --git a/playground/frontend/playground_components_dev/lib/src/examples/python/word_count_with_metrics.dart b/playground/frontend/playground_components_dev/lib/src/examples/python/word_count_with_metrics.dart index e94ddb4ba59e..53e09a96999a 100644 --- a/playground/frontend/playground_components_dev/lib/src/examples/python/word_count_with_metrics.dart +++ b/playground/frontend/playground_components_dev/lib/src/examples/python/word_count_with_metrics.dart @@ -24,7 +24,7 @@ const pythonWordCountWithMetrics = ExampleDescriptor( // 'WordCountWithMetrics', contextLine1Based: 48, - dbPath: 'SDK_PYTHON/PRECOMPILED_OBJECT_TYPE_EXAMPLE/WordCountWithMetrics', + dbPath: 'SDK_PYTHON_WordCountWithMetrics', path: '/sdks/python/apache_beam/examples/wordcount_with_metrics.py', sdk: Sdk.python, diff --git a/playground/frontend/playground_components_dev/lib/src/examples/scio/minimal_word_count.dart b/playground/frontend/playground_components_dev/lib/src/examples/scio/minimal_word_count.dart index 6fd03614c1cd..074a43861cab 100644 --- a/playground/frontend/playground_components_dev/lib/src/examples/scio/minimal_word_count.dart +++ b/playground/frontend/playground_components_dev/lib/src/examples/scio/minimal_word_count.dart @@ -24,7 +24,7 @@ import 'common.dart'; const scioMinimalWordCount = ExampleDescriptor( // 'MinimalWordCount', - dbPath: 'SDK_SCIO/PRECOMPILED_OBJECT_TYPE_EXAMPLE/MinimalWordCount', + dbPath: 'SDK_SCIO_MinimalWordCount', path: '/scio-examples/src/main/scala/com/spotify/scio/examples/MinimalWordCount.scala', repositoryAndRef: 'spotify/scio/$spotifyScioRef', From 08fb4ac6f6f4b08e7c309d914549c67a7f81baa0 Mon Sep 17 00:00:00 2001 From: malarg Date: Mon, 6 Mar 2023 12:19:03 +0400 Subject: [PATCH 45/52] issue25731 shortcut keys sequence --- .../standalone_editing_test.dart | 4 +- .../standalone_run_shortcuts_test.dart | 6 +- .../shortcuts/components/shortcuts_modal.dart | 13 +-- .../shortcuts/constants/global_shortcuts.dart | 8 +- .../controllers/playground_controller.dart | 12 +-- .../lib/src/models/shortcut.dart | 12 +-- .../lib/src/widgets/shortcuts_manager.dart | 2 +- .../test/src/models/shortcut_test.dart | 79 +++++++++++++++++++ .../lib/src/widget_tester.dart | 2 +- 9 files changed, 109 insertions(+), 29 deletions(-) create mode 100644 playground/frontend/playground_components/test/src/models/shortcut_test.dart diff --git a/playground/frontend/integration_test/standalone_editing_test.dart b/playground/frontend/integration_test/standalone_editing_test.dart index 83a4d433fd33..4b71cac107c0 100644 --- a/playground/frontend/integration_test/standalone_editing_test.dart +++ b/playground/frontend/integration_test/standalone_editing_test.dart @@ -43,9 +43,7 @@ Future _checkAutocomplete(WidgetTester wt) async { await wt.enterCodeFieldText('\n\n\n\n\nsdk'); final playgroundController = wt.findPlaygroundController(); - await wt.runShortcut( - playgroundController.showSuggestionsShortcut.shortcuts, - ); + await wt.runShortcut(playgroundController.showSuggestionsShortcut); await wt.pumpAndSettle(); expect(find.text('sdkHttpMetadata'), findsOneWidget); diff --git a/playground/frontend/integration_test/standalone_run_shortcuts_test.dart b/playground/frontend/integration_test/standalone_run_shortcuts_test.dart index 188cc0a2d2a2..85d71e282218 100644 --- a/playground/frontend/integration_test/standalone_run_shortcuts_test.dart +++ b/playground/frontend/integration_test/standalone_run_shortcuts_test.dart @@ -50,7 +50,7 @@ Future _checkResetShortcut( expect(controller.source, isNot(startSource)); - await wt.runShortcut(controller.resetShortcut.shortcuts); + await wt.runShortcut(controller.resetShortcut); await wt.pumpAndSettle(); expect(startSource, controller.source); @@ -61,7 +61,7 @@ Future _checkRunShortcut( PlaygroundController controller, ) async { final output = controller.codeRunner.resultLogOutput; - await wt.runShortcut(controller.runShortcut.shortcuts); + await wt.runShortcut(controller.runShortcut); await wt.pumpAndSettle(); expect(output, isNot(controller.codeRunner.resultLogOutput)); @@ -74,7 +74,7 @@ Future _checkClearOutputShortcut( expect(controller.codeRunner.resultLogOutput, isNotEmpty); expect(controller.codeRunner.resultLogOutput, isNotNull); - await wt.runShortcut(kClearOutputShortcut.shortcuts); + await wt.runShortcut(kClearOutputShortcut); await wt.pumpAndSettle(); expect(controller.codeRunner.resultLogOutput, isEmpty); diff --git a/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart b/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart index f4332e55ebd4..1c0af93d25f6 100644 --- a/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart +++ b/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart @@ -79,13 +79,14 @@ class ShortcutsModal extends StatelessWidget { ), actions: [ ElevatedButton( - style: ButtonStyle( - elevation: MaterialStateProperty.all(0.0), - fixedSize: MaterialStateProperty.all( - const Size(kButtonWidth, kButtonHeight), + style: const ButtonStyle( + elevation: MaterialStatePropertyAll(0.0), + fixedSize: MaterialStatePropertyAll( + Size(kButtonWidth, kButtonHeight), ), - shape: MaterialStateProperty.all( - const StadiumBorder(), + padding: MaterialStatePropertyAll(EdgeInsets.only(bottom: 2)), + shape: MaterialStatePropertyAll( + StadiumBorder(), ), ), onPressed: () => Navigator.of(context).pop(), diff --git a/playground/frontend/lib/modules/shortcuts/constants/global_shortcuts.dart b/playground/frontend/lib/modules/shortcuts/constants/global_shortcuts.dart index 2745c20ebdb7..4c762e1c6c31 100644 --- a/playground/frontend/lib/modules/shortcuts/constants/global_shortcuts.dart +++ b/playground/frontend/lib/modules/shortcuts/constants/global_shortcuts.dart @@ -31,10 +31,10 @@ class NewExampleIntent extends BeamIntent { } final kClearOutputShortcut = BeamShortcut( - shortcuts: LogicalKeySet( + keys: [ LogicalKeyboardKeyExtension.metaOrControl, LogicalKeyboardKey.keyB, - ), + ], actionIntent: const ClearOutputIntent(), createAction: (BuildContext context) => CallbackAction( onInvoke: (_) => Provider.of( @@ -45,10 +45,10 @@ final kClearOutputShortcut = BeamShortcut( ); final kNewExampleShortcut = BeamShortcut( - shortcuts: LogicalKeySet( + keys: [ LogicalKeyboardKeyExtension.metaOrControl, LogicalKeyboardKey.keyM, - ), + ], actionIntent: const NewExampleIntent(), createAction: (_) => CallbackAction( onInvoke: (_) => launchUrl(Uri.parse('/')), diff --git a/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart index f380af4cc02d..e3fca592449c 100644 --- a/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart +++ b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart @@ -312,10 +312,10 @@ class PlaygroundController with ChangeNotifier { } late BeamShortcut runShortcut = BeamShortcut( - shortcuts: LogicalKeySet( + keys: [ LogicalKeyboardKeyExtension.metaOrControl, LogicalKeyboardKey.enter, - ), + ], actionIntent: const RunIntent(), createAction: (BuildContext context) => CallbackAction( onInvoke: (_) => codeRunner.runCode(), @@ -323,11 +323,11 @@ class PlaygroundController with ChangeNotifier { ); late BeamShortcut resetShortcut = BeamShortcut( - shortcuts: LogicalKeySet( + keys: [ LogicalKeyboardKeyExtension.metaOrControl, LogicalKeyboardKey.shift, LogicalKeyboardKey.keyE, - ), + ], actionIntent: const ResetIntent(), createAction: (BuildContext context) => CallbackAction( onInvoke: (_) => reset(), @@ -335,11 +335,11 @@ class PlaygroundController with ChangeNotifier { ); late BeamShortcut showSuggestionsShortcut = BeamShortcut( - shortcuts: LogicalKeySet( + keys: [ LogicalKeyboardKeyExtension.metaOrControl, LogicalKeyboardKey.shift, LogicalKeyboardKey.keyS, - ), + ], actionIntent: const ShowSuggestionsIntent(), createAction: (BuildContext context) => CallbackAction( onInvoke: (_) => showSuggestions(), diff --git a/playground/frontend/playground_components/lib/src/models/shortcut.dart b/playground/frontend/playground_components/lib/src/models/shortcut.dart index ff4ede80f91d..e8b140764069 100644 --- a/playground/frontend/playground_components/lib/src/models/shortcut.dart +++ b/playground/frontend/playground_components/lib/src/models/shortcut.dart @@ -22,12 +22,14 @@ import 'package:flutter/services.dart'; import 'intents.dart'; class BeamShortcut { - final LogicalKeySet shortcuts; + final List keys; + LogicalKeySet get keySet => LogicalKeySet.fromSet(keys.toSet()); + final BeamIntent actionIntent; final CallbackAction Function(BuildContext) createAction; BeamShortcut({ - required this.shortcuts, + required this.keys, required this.actionIntent, required this.createAction, }); @@ -36,12 +38,12 @@ class BeamShortcut { static const _glue = ' + '; String get title { - return shortcuts.keys - .map(getKeyDisplayName) + return keys + .map(_getKeyDisplayName) .join(_glue); } - String getKeyDisplayName(LogicalKeyboardKey e) { + String _getKeyDisplayName(LogicalKeyboardKey e) { if (e.keyId == LogicalKeyboardKey.meta.keyId) { return _metaKeyName; } diff --git a/playground/frontend/playground_components/lib/src/widgets/shortcuts_manager.dart b/playground/frontend/playground_components/lib/src/widgets/shortcuts_manager.dart index 2006c7662e7e..d5184961f3af 100644 --- a/playground/frontend/playground_components/lib/src/widgets/shortcuts_manager.dart +++ b/playground/frontend/playground_components/lib/src/widgets/shortcuts_manager.dart @@ -43,7 +43,7 @@ class ShortcutsManager extends StatelessWidget { Map get _shortcutsMap => { for (var shortcut in shortcuts) - shortcut.shortcuts: shortcut.actionIntent + shortcut.keySet: shortcut.actionIntent }; Map> _getActions(BuildContext context) => { diff --git a/playground/frontend/playground_components/test/src/models/shortcut_test.dart b/playground/frontend/playground_components/test/src/models/shortcut_test.dart new file mode 100644 index 000000000000..c35e998e0c9e --- /dev/null +++ b/playground/frontend/playground_components/test/src/models/shortcut_test.dart @@ -0,0 +1,79 @@ +/* + * 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/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:playground_components/playground_components.dart'; + +void main() { + group( + 'BeamShortcut test. ', + () { + test( + 'Title builds correctly', + () { + const meta = 'CMD/CTRL'; + final shortcutsAndTitles = { + // + _buildShortcut([ + LogicalKeyboardKey.meta, + LogicalKeyboardKey.enter, + ]): '$meta + Enter', + + _buildShortcut([ + LogicalKeyboardKey.enter, + LogicalKeyboardKey.meta, + ]): 'Enter + $meta', + + _buildShortcut([ + LogicalKeyboardKey.meta, + LogicalKeyboardKey.shift, + LogicalKeyboardKey.keyS, + ]): '$meta + Shift + S', + + _buildShortcut([ + LogicalKeyboardKey.meta, + LogicalKeyboardKey.alt, + LogicalKeyboardKey.shift, + LogicalKeyboardKey.keyS, + ]): '$meta + Alt + Shift + S', + }; + + for (final entry in shortcutsAndTitles.entries) { + expect(entry.key.title, entry.value); + } + }, + ); + }, + ); +} + +BeamShortcut _buildShortcut(List keys) { + return BeamShortcut( + keys: keys, + actionIntent: const BeamIntent( + slug: 'slug', + ), + createAction: (_) => CallbackAction( + onInvoke: (_) { + return null; + }, + ), + ); +} diff --git a/playground/frontend/playground_components_dev/lib/src/widget_tester.dart b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart index 730d05154f25..112446c97582 100644 --- a/playground/frontend/playground_components_dev/lib/src/widget_tester.dart +++ b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart @@ -77,7 +77,7 @@ extension WidgetTesterExtension on WidgetTester { return context.read(); } - Future runShortcut(LogicalKeySet shortcut) async { + Future runShortcut(BeamShortcut shortcut) async { final list = shortcut.keys.toList(); for (final key in list) { await sendKeyDownEvent(key); From 7865e944ff048dbe388bb0519383287a6e672c23 Mon Sep 17 00:00:00 2001 From: Dmitry Repin Date: Tue, 7 Mar 2023 10:34:48 +0400 Subject: [PATCH 46/52] Update playground/frontend/playground_components/test/src/models/shortcut_test.dart Co-authored-by: alexeyinkin --- .../playground_components/test/src/models/shortcut_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playground/frontend/playground_components/test/src/models/shortcut_test.dart b/playground/frontend/playground_components/test/src/models/shortcut_test.dart index c35e998e0c9e..489cf40efcdf 100644 --- a/playground/frontend/playground_components/test/src/models/shortcut_test.dart +++ b/playground/frontend/playground_components/test/src/models/shortcut_test.dart @@ -23,7 +23,7 @@ import 'package:playground_components/playground_components.dart'; void main() { group( - 'BeamShortcut test. ', + 'BeamShortcut test.', () { test( 'Title builds correctly', From b70d080c67094eefab22113776e0ccbf097121bc Mon Sep 17 00:00:00 2001 From: malarg Date: Tue, 7 Mar 2023 14:04:10 +0400 Subject: [PATCH 47/52] issue25731 fix shortcut modal width, fix shortcut meta key text --- .../modules/shortcuts/components/shortcuts_modal.dart | 11 +++++++++++ .../lib/src/models/shortcut.dart | 2 +- .../test/src/models/shortcut_test.dart | 9 ++++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart b/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart index 1c0af93d25f6..e00d1d6d17a3 100644 --- a/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart +++ b/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart @@ -29,6 +29,7 @@ const kButtonBorderRadius = 24.0; const kButtonWidth = 120.0; const kButtonHeight = 40.0; const kDialogPadding = 40.0; +const kShortcutModalMaxWidth = 800.0; class ShortcutsModal extends StatelessWidget { final PlaygroundController playgroundController; @@ -48,6 +49,7 @@ class ShortcutsModal extends StatelessWidget { left: kDialogPadding, ), contentPadding: const EdgeInsets.all(kDialogPadding), + insetPadding: _buildDialogPadding(context), actionsPadding: const EdgeInsets.only( bottom: kDialogPadding, right: kDialogPadding, @@ -95,4 +97,13 @@ class ShortcutsModal extends StatelessWidget { ], ); } + + EdgeInsets _buildDialogPadding(BuildContext context) { + return EdgeInsets.symmetric( + horizontal: MediaQuery.of(context).size.width > + kShortcutModalMaxWidth + kDialogPadding * 2 + ? (MediaQuery.of(context).size.width - kShortcutModalMaxWidth) / 2.0 + : kDialogPadding, + ); + } } diff --git a/playground/frontend/playground_components/lib/src/models/shortcut.dart b/playground/frontend/playground_components/lib/src/models/shortcut.dart index e8b140764069..49b07c27d69d 100644 --- a/playground/frontend/playground_components/lib/src/models/shortcut.dart +++ b/playground/frontend/playground_components/lib/src/models/shortcut.dart @@ -34,7 +34,7 @@ class BeamShortcut { required this.createAction, }); - static const _metaKeyName = 'CMD/CTRL'; + static const _metaKeyName = 'Command'; static const _glue = ' + '; String get title { diff --git a/playground/frontend/playground_components/test/src/models/shortcut_test.dart b/playground/frontend/playground_components/test/src/models/shortcut_test.dart index 489cf40efcdf..db982e9bbc1f 100644 --- a/playground/frontend/playground_components/test/src/models/shortcut_test.dart +++ b/playground/frontend/playground_components/test/src/models/shortcut_test.dart @@ -28,7 +28,7 @@ void main() { test( 'Title builds correctly', () { - const meta = 'CMD/CTRL'; + const meta = 'Command'; final shortcutsAndTitles = { // _buildShortcut([ @@ -53,6 +53,13 @@ void main() { LogicalKeyboardKey.shift, LogicalKeyboardKey.keyS, ]): '$meta + Alt + Shift + S', + + _buildShortcut([ + LogicalKeyboardKey.control, + LogicalKeyboardKey.alt, + LogicalKeyboardKey.shift, + LogicalKeyboardKey.keyS, + ]): 'Control + Alt + Shift + S', }; for (final entry in shortcutsAndTitles.entries) { From a19fd619603373d26760391dbc40a453650186d4 Mon Sep 17 00:00:00 2001 From: malarg Date: Fri, 10 Mar 2023 12:03:59 +0400 Subject: [PATCH 48/52] issue25731 fix shortcut modal window --- .../common/common_finders.dart | 2 +- .../shortcuts_modal_test.dart | 2 +- .../shortcuts/components/shortcuts_modal.dart | 66 ++++--------------- .../widgets/more_actions.dart | 5 +- .../lib/playground_components.dart | 1 + .../lib/src/widgets/dialog.dart | 62 +++++++++++++++++ 6 files changed, 82 insertions(+), 56 deletions(-) create mode 100644 playground/frontend/playground_components/lib/src/widgets/dialog.dart diff --git a/playground/frontend/integration_test/common/common_finders.dart b/playground/frontend/integration_test/common/common_finders.dart index d0863dbfcbb7..a9919d55ac66 100644 --- a/playground/frontend/integration_test/common/common_finders.dart +++ b/playground/frontend/integration_test/common/common_finders.dart @@ -117,6 +117,6 @@ extension CommonFindersExtension on CommonFinders { } Finder shortcutsModal() { - return byType(ShortcutsModal); + return byType(ShortcutsModalContent); } } diff --git a/playground/frontend/integration_test/miscellaneous_ui/shortcuts_modal_test.dart b/playground/frontend/integration_test/miscellaneous_ui/shortcuts_modal_test.dart index e12752abef3c..4b9b1a9fc3b5 100644 --- a/playground/frontend/integration_test/miscellaneous_ui/shortcuts_modal_test.dart +++ b/playground/frontend/integration_test/miscellaneous_ui/shortcuts_modal_test.dart @@ -38,7 +38,7 @@ Future checkShortcutsModal(WidgetTester wt) async { expect(find.shortcutsModal(), findsOneWidget); - await wt.tap(find.text(appLocale.close)); + await wt.sendKeyEvent(LogicalKeyboardKey.escape); await wt.pumpAndSettle(); expect(find.shortcutsModal(), findsNothing); diff --git a/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart b/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart index e00d1d6d17a3..a564504e37cc 100644 --- a/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart +++ b/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart @@ -18,43 +18,28 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:playground/constants/font_weight.dart'; import 'package:playground/constants/sizes.dart'; import 'package:playground/modules/shortcuts/components/shortcut_row.dart'; import 'package:playground/modules/shortcuts/constants/global_shortcuts.dart'; import 'package:playground_components/playground_components.dart'; -const kButtonBorderRadius = 24.0; -const kButtonWidth = 120.0; -const kButtonHeight = 40.0; -const kDialogPadding = 40.0; -const kShortcutModalMaxWidth = 800.0; +class ShortcutsModalContent extends StatelessWidget { + static const _kModalMaxWidth = 400.0; + static const _kShortcutsMaxWidth = 200.0; -class ShortcutsModal extends StatelessWidget { - final PlaygroundController playgroundController; - - const ShortcutsModal({ + const ShortcutsModalContent({ + super.key, required this.playgroundController, }); + final PlaygroundController playgroundController; + @override Widget build(BuildContext context) { - AppLocalizations appLocale = AppLocalizations.of(context)!; - - return AlertDialog( - title: Text(appLocale.shortcuts), - titlePadding: const EdgeInsets.only( - top: kDialogPadding, - left: kDialogPadding, - ), - contentPadding: const EdgeInsets.all(kDialogPadding), - insetPadding: _buildDialogPadding(context), - actionsPadding: const EdgeInsets.only( - bottom: kDialogPadding, - right: kDialogPadding, - ), - content: Wrap( + return ConstrainedBox( + constraints: const BoxConstraints(maxWidth: _kModalMaxWidth), + child: Wrap( crossAxisAlignment: WrapCrossAlignment.start, runSpacing: kXlSpacing, children: [ @@ -65,10 +50,12 @@ class ShortcutsModal extends StatelessWidget { (shortcut) => Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Expanded(child: ShortcutRow(shortcut: shortcut)), + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: _kShortcutsMaxWidth), + child: ShortcutRow(shortcut: shortcut), + ), const SizedBox(width: kMdSpacing), Expanded( - flex: 3, child: Text( shortcut.actionIntent.slug.tr(), style: const TextStyle(fontWeight: kBoldWeight), @@ -79,31 +66,6 @@ class ShortcutsModal extends StatelessWidget { ) ], ), - actions: [ - ElevatedButton( - style: const ButtonStyle( - elevation: MaterialStatePropertyAll(0.0), - fixedSize: MaterialStatePropertyAll( - Size(kButtonWidth, kButtonHeight), - ), - padding: MaterialStatePropertyAll(EdgeInsets.only(bottom: 2)), - shape: MaterialStatePropertyAll( - StadiumBorder(), - ), - ), - onPressed: () => Navigator.of(context).pop(), - child: Text(appLocale.close), - ), - ], - ); - } - - EdgeInsets _buildDialogPadding(BuildContext context) { - return EdgeInsets.symmetric( - horizontal: MediaQuery.of(context).size.width > - kShortcutModalMaxWidth + kDialogPadding * 2 - ? (MediaQuery.of(context).size.width - kShortcutModalMaxWidth) / 2.0 - : kDialogPadding, ); } } diff --git a/playground/frontend/lib/pages/standalone_playground/widgets/more_actions.dart b/playground/frontend/lib/pages/standalone_playground/widgets/more_actions.dart index f82fb93707f6..028088b543b6 100644 --- a/playground/frontend/lib/pages/standalone_playground/widgets/more_actions.dart +++ b/playground/frontend/lib/pages/standalone_playground/widgets/more_actions.dart @@ -68,9 +68,10 @@ class _MoreActionsState extends State { title: Text(appLocale.shortcuts), onTap: () { AnalyticsService.get(context).trackOpenShortcutsModal(); - showDialog( + BeamDialog.show( + title: Text(appLocale.shortcuts), context: context, - builder: (BuildContext context) => ShortcutsModal( + child: ShortcutsModalContent( playgroundController: widget.playgroundController, ), ); diff --git a/playground/frontend/playground_components/lib/playground_components.dart b/playground/frontend/playground_components/lib/playground_components.dart index 1278cbc61c68..58976d8c75f6 100644 --- a/playground/frontend/playground_components/lib/playground_components.dart +++ b/playground/frontend/playground_components/lib/playground_components.dart @@ -62,6 +62,7 @@ export 'src/util/string.dart'; export 'src/widgets/bubble.dart'; export 'src/widgets/clickable.dart'; export 'src/widgets/complexity.dart'; +export 'src/widgets/dialog.dart'; export 'src/widgets/divider.dart'; export 'src/widgets/header_icon_button.dart'; export 'src/widgets/loading_error.dart'; diff --git a/playground/frontend/playground_components/lib/src/widgets/dialog.dart b/playground/frontend/playground_components/lib/src/widgets/dialog.dart new file mode 100644 index 000000000000..bc7dae320f0b --- /dev/null +++ b/playground/frontend/playground_components/lib/src/widgets/dialog.dart @@ -0,0 +1,62 @@ +/* + * 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'; + +const kDialogPadding = 40.0; + +// TODO(alexeyinkin): In future convert all dialogs to this one. +class BeamDialog extends StatelessWidget { + const BeamDialog({ + required this.child, + this.title, + }); + + final Widget child; + final Widget? title; + + static Future show({ + required Widget child, + required BuildContext context, + Widget? title, + }) async { + await showDialog( + context: context, + builder: (BuildContext context) => BeamDialog( + child: AlertDialog( + title: title, + content: child, + titlePadding: const EdgeInsets.only( + top: kDialogPadding, + left: kDialogPadding, + ), + contentPadding: const EdgeInsets.all(kDialogPadding), + actionsPadding: const EdgeInsets.only( + bottom: kDialogPadding, + right: kDialogPadding, + ), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return child; + } +} From 9ecae3c4c64d949b5c644da87e50f57467ecc083 Mon Sep 17 00:00:00 2001 From: malarg Date: Fri, 10 Mar 2023 20:00:57 +0400 Subject: [PATCH 49/52] issue25731 pr fix --- .../common/common_finders.dart | 2 +- .../shortcuts/components/shortcuts_modal.dart | 6 +-- .../widgets/more_actions.dart | 5 ++- .../assets/translations/en.yaml | 3 ++ .../lib/playground_components.dart | 1 + .../lib/src/models/shortcut.dart | 2 + .../lib/src/widgets/close_button.dart | 43 +++++++++++++++++++ .../lib/src/widgets/dialog.dart | 14 +++--- 8 files changed, 63 insertions(+), 13 deletions(-) create mode 100644 playground/frontend/playground_components/lib/src/widgets/close_button.dart diff --git a/playground/frontend/integration_test/common/common_finders.dart b/playground/frontend/integration_test/common/common_finders.dart index a9919d55ac66..3434c6087e47 100644 --- a/playground/frontend/integration_test/common/common_finders.dart +++ b/playground/frontend/integration_test/common/common_finders.dart @@ -117,6 +117,6 @@ extension CommonFindersExtension on CommonFinders { } Finder shortcutsModal() { - return byType(ShortcutsModalContent); + return byType(ShortcutsDialogContent); } } diff --git a/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart b/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart index a564504e37cc..0abac9f1d4c7 100644 --- a/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart +++ b/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart @@ -24,12 +24,11 @@ import 'package:playground/modules/shortcuts/components/shortcut_row.dart'; import 'package:playground/modules/shortcuts/constants/global_shortcuts.dart'; import 'package:playground_components/playground_components.dart'; -class ShortcutsModalContent extends StatelessWidget { +class ShortcutsDialogContent extends StatelessWidget { static const _kModalMaxWidth = 400.0; static const _kShortcutsMaxWidth = 200.0; - const ShortcutsModalContent({ - super.key, + const ShortcutsDialogContent({ required this.playgroundController, }); @@ -40,7 +39,6 @@ class ShortcutsModalContent extends StatelessWidget { return ConstrainedBox( constraints: const BoxConstraints(maxWidth: _kModalMaxWidth), child: Wrap( - crossAxisAlignment: WrapCrossAlignment.start, runSpacing: kXlSpacing, children: [ ...[ diff --git a/playground/frontend/lib/pages/standalone_playground/widgets/more_actions.dart b/playground/frontend/lib/pages/standalone_playground/widgets/more_actions.dart index 028088b543b6..81835137b1e6 100644 --- a/playground/frontend/lib/pages/standalone_playground/widgets/more_actions.dart +++ b/playground/frontend/lib/pages/standalone_playground/widgets/more_actions.dart @@ -69,9 +69,10 @@ class _MoreActionsState extends State { onTap: () { AnalyticsService.get(context).trackOpenShortcutsModal(); BeamDialog.show( - title: Text(appLocale.shortcuts), + actions: [BeamCloseButton()], context: context, - child: ShortcutsModalContent( + title: Text(appLocale.shortcuts), + child: ShortcutsDialogContent( playgroundController: widget.playgroundController, ), ); diff --git a/playground/frontend/playground_components/assets/translations/en.yaml b/playground/frontend/playground_components/assets/translations/en.yaml index 441570837ff8..d7f1ad39167c 100644 --- a/playground/frontend/playground_components/assets/translations/en.yaml +++ b/playground/frontend/playground_components/assets/translations/en.yaml @@ -43,6 +43,9 @@ widgets: codeEditor: label: 'Code Text Area' + + closeButton: + label: 'CLOSE' output: filter: diff --git a/playground/frontend/playground_components/lib/playground_components.dart b/playground/frontend/playground_components/lib/playground_components.dart index 58976d8c75f6..1a385b5fe4f4 100644 --- a/playground/frontend/playground_components/lib/playground_components.dart +++ b/playground/frontend/playground_components/lib/playground_components.dart @@ -61,6 +61,7 @@ export 'src/util/string.dart'; export 'src/widgets/bubble.dart'; export 'src/widgets/clickable.dart'; +export 'src/widgets/close_button.dart'; export 'src/widgets/complexity.dart'; export 'src/widgets/dialog.dart'; export 'src/widgets/divider.dart'; diff --git a/playground/frontend/playground_components/lib/src/models/shortcut.dart b/playground/frontend/playground_components/lib/src/models/shortcut.dart index 49b07c27d69d..ec5fa3160d09 100644 --- a/playground/frontend/playground_components/lib/src/models/shortcut.dart +++ b/playground/frontend/playground_components/lib/src/models/shortcut.dart @@ -22,6 +22,8 @@ import 'package:flutter/services.dart'; import 'intents.dart'; class BeamShortcut { + //Additional List property required for determinated displaying keys + //in shortcut dialog. final List keys; LogicalKeySet get keySet => LogicalKeySet.fromSet(keys.toSet()); diff --git a/playground/frontend/playground_components/lib/src/widgets/close_button.dart b/playground/frontend/playground_components/lib/src/widgets/close_button.dart new file mode 100644 index 000000000000..38d5a3114db4 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/widgets/close_button.dart @@ -0,0 +1,43 @@ +/* + * 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:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; + +class BeamCloseButton extends StatelessWidget { + static const kWidth = 120.0; + static const kHeight = 40.0; + + @override + Widget build(BuildContext context) { + return ElevatedButton( + style: const ButtonStyle( + elevation: MaterialStatePropertyAll(0), + fixedSize: MaterialStatePropertyAll( + Size(kWidth, kHeight), + ), + shape: MaterialStatePropertyAll( + StadiumBorder(), + ), + padding: MaterialStatePropertyAll(EdgeInsets.only(bottom: 2)), + ), + onPressed: () => Navigator.of(context).pop(), + child: const Text('widgets.closeButton.label').tr(), + ); + } +} diff --git a/playground/frontend/playground_components/lib/src/widgets/dialog.dart b/playground/frontend/playground_components/lib/src/widgets/dialog.dart index bc7dae320f0b..266115c904ea 100644 --- a/playground/frontend/playground_components/lib/src/widgets/dialog.dart +++ b/playground/frontend/playground_components/lib/src/widgets/dialog.dart @@ -34,22 +34,24 @@ class BeamDialog extends StatelessWidget { required Widget child, required BuildContext context, Widget? title, + List actions = const [], }) async { await showDialog( context: context, builder: (BuildContext context) => BeamDialog( child: AlertDialog( - title: title, + actions: actions, + actionsPadding: const EdgeInsets.only( + bottom: kDialogPadding, + right: kDialogPadding, + ), content: child, + contentPadding: const EdgeInsets.all(kDialogPadding), + title: title, titlePadding: const EdgeInsets.only( top: kDialogPadding, left: kDialogPadding, ), - contentPadding: const EdgeInsets.all(kDialogPadding), - actionsPadding: const EdgeInsets.only( - bottom: kDialogPadding, - right: kDialogPadding, - ), ), ), ); From a5de7f4f4c66907e3ee19921bcf02e0dd151f2e3 Mon Sep 17 00:00:00 2001 From: malarg Date: Thu, 16 Mar 2023 09:52:50 +0400 Subject: [PATCH 50/52] issue25731 fix pr --- .../shortcuts/components/shortcuts_modal.dart | 48 +++++++++++-------- .../assets/translations/en.yaml | 2 +- .../lib/playground_components.dart | 1 + .../lib/src/models/shortcut.dart | 6 ++- .../lib/src/util/iterable.dart | 26 ++++++++++ .../lib/src/widgets/close_button.dart | 8 ++-- .../lib/src/widgets/dialog.dart | 38 ++++++++------- 7 files changed, 85 insertions(+), 44 deletions(-) create mode 100644 playground/frontend/playground_components/lib/src/util/iterable.dart diff --git a/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart b/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart index 0abac9f1d4c7..4ebc14acdffe 100644 --- a/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart +++ b/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart @@ -36,32 +36,40 @@ class ShortcutsDialogContent extends StatelessWidget { @override Widget build(BuildContext context) { - return ConstrainedBox( - constraints: const BoxConstraints(maxWidth: _kModalMaxWidth), - child: Wrap( - runSpacing: kXlSpacing, + return SizedBox( + width: _kModalMaxWidth, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, children: [ ...[ ...playgroundController.shortcuts, ...globalShortcuts, - ].map( - (shortcut) => Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - ConstrainedBox( - constraints: const BoxConstraints(maxWidth: _kShortcutsMaxWidth), - child: ShortcutRow(shortcut: shortcut), + ] + .map( + (shortcut) => Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ConstrainedBox( + constraints: + const BoxConstraints(maxWidth: _kShortcutsMaxWidth), + child: ShortcutRow(shortcut: shortcut), + ), + const SizedBox(width: kMdSpacing), + Expanded( + child: Text( + shortcut.actionIntent.slug.tr(), + style: const TextStyle(fontWeight: kBoldWeight), + ), + ), + ], ), - const SizedBox(width: kMdSpacing), - Expanded( - child: Text( - shortcut.actionIntent.slug.tr(), - style: const TextStyle(fontWeight: kBoldWeight), - ), + ) + .alternateWith( + const SizedBox( + height: kXlSpacing, ), - ], - ), - ) + ), ], ), ); diff --git a/playground/frontend/playground_components/assets/translations/en.yaml b/playground/frontend/playground_components/assets/translations/en.yaml index d7f1ad39167c..fdee1d0e7dc5 100644 --- a/playground/frontend/playground_components/assets/translations/en.yaml +++ b/playground/frontend/playground_components/assets/translations/en.yaml @@ -45,7 +45,7 @@ widgets: label: 'Code Text Area' closeButton: - label: 'CLOSE' + label: 'Close' output: filter: diff --git a/playground/frontend/playground_components/lib/playground_components.dart b/playground/frontend/playground_components/lib/playground_components.dart index 1a385b5fe4f4..e2b012e01e0f 100644 --- a/playground/frontend/playground_components/lib/playground_components.dart +++ b/playground/frontend/playground_components/lib/playground_components.dart @@ -55,6 +55,7 @@ export 'src/router/router_delegate.dart'; export 'src/services/symbols/loaders/yaml.dart'; export 'src/theme/switch_notifier.dart'; export 'src/theme/theme.dart'; +export 'src/util/iterable.dart'; export 'src/util/logical_keyboard_key.dart'; export 'src/util/pipeline_options.dart'; export 'src/util/string.dart'; diff --git a/playground/frontend/playground_components/lib/src/models/shortcut.dart b/playground/frontend/playground_components/lib/src/models/shortcut.dart index ec5fa3160d09..1f3a3898b7e6 100644 --- a/playground/frontend/playground_components/lib/src/models/shortcut.dart +++ b/playground/frontend/playground_components/lib/src/models/shortcut.dart @@ -22,9 +22,11 @@ import 'package:flutter/services.dart'; import 'intents.dart'; class BeamShortcut { - //Additional List property required for determinated displaying keys - //in shortcut dialog. + // Keys in the order to be shown or mocked. + // + // A list is required because a [LogicalKeySet] discards the original order. final List keys; + LogicalKeySet get keySet => LogicalKeySet.fromSet(keys.toSet()); final BeamIntent actionIntent; diff --git a/playground/frontend/playground_components/lib/src/util/iterable.dart b/playground/frontend/playground_components/lib/src/util/iterable.dart new file mode 100644 index 000000000000..3ce5b6f88b46 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/util/iterable.dart @@ -0,0 +1,26 @@ +/* + * 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. + */ + +extension IterableExtension on Iterable { + Iterable alternateWith(T separator) { + return expand((item) sync* { + yield separator; + yield item as T; + }).skip(1); + } +} diff --git a/playground/frontend/playground_components/lib/src/widgets/close_button.dart b/playground/frontend/playground_components/lib/src/widgets/close_button.dart index 38d5a3114db4..868e53fa920d 100644 --- a/playground/frontend/playground_components/lib/src/widgets/close_button.dart +++ b/playground/frontend/playground_components/lib/src/widgets/close_button.dart @@ -20,8 +20,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; class BeamCloseButton extends StatelessWidget { - static const kWidth = 120.0; - static const kHeight = 40.0; + static const _width = 120.0; + static const _height = 40.0; @override Widget build(BuildContext context) { @@ -29,7 +29,7 @@ class BeamCloseButton extends StatelessWidget { style: const ButtonStyle( elevation: MaterialStatePropertyAll(0), fixedSize: MaterialStatePropertyAll( - Size(kWidth, kHeight), + Size(_width, _height), ), shape: MaterialStatePropertyAll( StadiumBorder(), @@ -37,7 +37,7 @@ class BeamCloseButton extends StatelessWidget { padding: MaterialStatePropertyAll(EdgeInsets.only(bottom: 2)), ), onPressed: () => Navigator.of(context).pop(), - child: const Text('widgets.closeButton.label').tr(), + child: Text('widgets.closeButton.label'.tr().toUpperCase()), ); } } diff --git a/playground/frontend/playground_components/lib/src/widgets/dialog.dart b/playground/frontend/playground_components/lib/src/widgets/dialog.dart index 266115c904ea..7e14d481649e 100644 --- a/playground/frontend/playground_components/lib/src/widgets/dialog.dart +++ b/playground/frontend/playground_components/lib/src/widgets/dialog.dart @@ -18,15 +18,17 @@ import 'package:flutter/material.dart'; -const kDialogPadding = 40.0; - // TODO(alexeyinkin): In future convert all dialogs to this one. class BeamDialog extends StatelessWidget { + static const _padding = 40.0; + const BeamDialog({ required this.child, + this.actions = const [], this.title, }); + final List actions; final Widget child; final Widget? title; @@ -39,26 +41,28 @@ class BeamDialog extends StatelessWidget { await showDialog( context: context, builder: (BuildContext context) => BeamDialog( - child: AlertDialog( - actions: actions, - actionsPadding: const EdgeInsets.only( - bottom: kDialogPadding, - right: kDialogPadding, - ), - content: child, - contentPadding: const EdgeInsets.all(kDialogPadding), - title: title, - titlePadding: const EdgeInsets.only( - top: kDialogPadding, - left: kDialogPadding, - ), - ), + actions: actions, + title: title, + child: child, ), ); } @override Widget build(BuildContext context) { - return child; + return AlertDialog( + actions: actions, + actionsPadding: const EdgeInsets.only( + bottom: _padding, + right: _padding, + ), + content: child, + contentPadding: const EdgeInsets.all(_padding), + title: title, + titlePadding: const EdgeInsets.only( + top: _padding, + left: _padding, + ), + ); } } From 18685afc083df235ceb730e20791c0879651ff59 Mon Sep 17 00:00:00 2001 From: malarg Date: Thu, 16 Mar 2023 14:02:27 +0400 Subject: [PATCH 51/52] issue25731 remane file --- playground/frontend/integration_test/common/common_finders.dart | 2 +- .../components/{shortcuts_modal.dart => shortcuts_dialog.dart} | 0 .../lib/pages/standalone_playground/widgets/more_actions.dart | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename playground/frontend/lib/modules/shortcuts/components/{shortcuts_modal.dart => shortcuts_dialog.dart} (100%) diff --git a/playground/frontend/integration_test/common/common_finders.dart b/playground/frontend/integration_test/common/common_finders.dart index 3434c6087e47..af49b996b540 100644 --- a/playground/frontend/integration_test/common/common_finders.dart +++ b/playground/frontend/integration_test/common/common_finders.dart @@ -26,7 +26,7 @@ import 'package:playground/modules/examples/components/description_popover/descr import 'package:playground/modules/examples/example_selector.dart'; import 'package:playground/modules/sdk/components/sdk_selector.dart'; import 'package:playground/modules/sdk/components/sdk_selector_row.dart'; -import 'package:playground/modules/shortcuts/components/shortcuts_modal.dart'; +import 'package:playground/modules/shortcuts/components/shortcuts_dialog.dart'; import 'package:playground/pages/standalone_playground/widgets/editor_textarea_wrapper.dart'; import 'package:playground/pages/standalone_playground/widgets/feedback/feedback_dropdown_content.dart'; import 'package:playground/pages/standalone_playground/widgets/feedback/playground_feedback.dart'; diff --git a/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart b/playground/frontend/lib/modules/shortcuts/components/shortcuts_dialog.dart similarity index 100% rename from playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart rename to playground/frontend/lib/modules/shortcuts/components/shortcuts_dialog.dart diff --git a/playground/frontend/lib/pages/standalone_playground/widgets/more_actions.dart b/playground/frontend/lib/pages/standalone_playground/widgets/more_actions.dart index 81835137b1e6..f541c913121f 100644 --- a/playground/frontend/lib/pages/standalone_playground/widgets/more_actions.dart +++ b/playground/frontend/lib/pages/standalone_playground/widgets/more_actions.dart @@ -24,7 +24,7 @@ import 'package:url_launcher/url_launcher.dart'; import '../../../constants/links.dart'; import '../../../modules/analytics/analytics_service.dart'; -import '../../../modules/shortcuts/components/shortcuts_modal.dart'; +import '../../../modules/shortcuts/components/shortcuts_dialog.dart'; import '../../../src/assets/assets.gen.dart'; enum HeaderAction { From b31f5c42b6fce25940224143b911fb743f6a8e7c Mon Sep 17 00:00:00 2001 From: malarg Date: Fri, 17 Mar 2023 17:03:13 +0400 Subject: [PATCH 52/52] fix test --- .../lib/src/examples/python/aggregation_mean.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playground/frontend/playground_components_dev/lib/src/examples/python/aggregation_mean.dart b/playground/frontend/playground_components_dev/lib/src/examples/python/aggregation_mean.dart index e18ac22f502e..7c91e14277f1 100644 --- a/playground/frontend/playground_components_dev/lib/src/examples/python/aggregation_mean.dart +++ b/playground/frontend/playground_components_dev/lib/src/examples/python/aggregation_mean.dart @@ -27,5 +27,5 @@ const pythonAggregationMean = ExampleDescriptor( path: '/learning/katas/python/Common Transforms/Aggregation/Mean/task.py', sdk: Sdk.python, - outputContains: ['16 [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]'], + outputContains: ['5.5'], );