From 933bb24a2f58cd6cfa1e35e8b41a9baa4c13815f Mon Sep 17 00:00:00 2001 From: Alexey Inkin Date: Tue, 20 Dec 2022 17:56:42 +0400 Subject: [PATCH 1/7] 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 2/7] 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 3/7] 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 4/7] 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 5/7] 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 6/7] 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 7/7] 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