| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <fuchsia/modular/testing/cpp/fidl.h> |
| #include <lib/fsl/vmo/strings.h> |
| #include <lib/modular_test_harness/cpp/fake_module.h> |
| #include <lib/modular_test_harness/cpp/fake_session_shell.h> |
| #include <lib/modular_test_harness/cpp/fake_story_shell.h> |
| #include <lib/modular_test_harness/cpp/test_harness_fixture.h> |
| #include <lib/ui/scenic/cpp/view_token_pair.h> |
| |
| #include <src/lib/fxl/logging.h> |
| |
| namespace { |
| |
| constexpr char kParentModuleName[] = "parent_name"; |
| constexpr char kEmbeddedModuleName[] = "embedded_name"; |
| constexpr char kThirdModuleName[] = "third_name"; |
| constexpr char kStoryName[] = "story"; |
| |
| class StoryShellEmbeddedModTest : public modular::testing::TestHarnessFixture { |
| public: |
| void SetUp() override { |
| // Listen for session shell interception |
| builder_.InterceptSessionShell(fake_session_shell_.GetOnCreateHandler(), |
| {.sandbox_services = {"fuchsia.modular.SessionShellContext", |
| "fuchsia.modular.PuppetMaster"}}); |
| |
| // Listen for story shell interception |
| builder_.InterceptStoryShell(test_story_shell_.GetOnCreateHandler(), |
| {.sandbox_services = {"fuchsia.modular.StoryShellContext"}}); |
| |
| // Listen for parent module interception |
| parent_module_ = |
| std::make_unique<modular::testing::FakeModule>([](fuchsia::modular::Intent intent) {}); |
| parent_module_url_ = modular_testing::TestHarnessBuilder::GenerateFakeUrl(); |
| builder_.InterceptComponent( |
| parent_module_->GetOnCreateHandler(), |
| {.url = parent_module_url_, |
| .sandbox_services = modular::testing::FakeModule::GetSandboxServices()}); |
| |
| // Listen for embedded module interception |
| embedded_module_ = |
| std::make_unique<modular::testing::FakeModule>([](fuchsia::modular::Intent intent) {}); |
| embedded_module_url_ = modular_testing::TestHarnessBuilder::GenerateFakeUrl(); |
| builder_.InterceptComponent( |
| embedded_module_->GetOnCreateHandler(), |
| {.url = embedded_module_url_, |
| .sandbox_services = modular::testing::FakeModule::GetSandboxServices()}); |
| |
| // Listen for third module interception |
| third_module_ = |
| std::make_unique<modular::testing::FakeModule>([](fuchsia::modular::Intent intent) {}); |
| third_module_url_ = modular_testing::TestHarnessBuilder::GenerateFakeUrl(); |
| builder_.InterceptComponent( |
| third_module_->GetOnCreateHandler(), |
| {.url = third_module_url_, |
| .sandbox_services = modular::testing::FakeModule::GetSandboxServices()}); |
| |
| // Start modular |
| builder_.BuildAndRun(test_harness()); |
| |
| // Wait for session shell to start |
| RunLoopUntil([&] { return fake_session_shell_.is_running(); }); |
| } |
| |
| // Launches an initial parent module. |
| void LaunchParentModule() { |
| auto parent_mod_intent = fuchsia::modular::Intent{.handler = parent_module_url_}; |
| modular::testing::AddModToStory(test_harness(), kStoryName, kParentModuleName, |
| std::move(parent_mod_intent)); |
| |
| RunLoopUntil([&] { return parent_module_->is_running(); }); |
| } |
| |
| // The parent module embeds a module. |
| void ParentModuleEmbedsModule() { |
| auto embedded_mod_intent = fuchsia::modular::Intent{.handler = embedded_module_url_}; |
| auto [view_token, view_holder_token] = scenic::ViewTokenPair::New(); |
| fuchsia::modular::ModuleControllerPtr module_controller; |
| parent_module_->module_context()->EmbedModule2( |
| kEmbeddedModuleName, std::move(embedded_mod_intent), module_controller.NewRequest(), |
| std::move(view_token), [](const fuchsia::modular::StartModuleStatus status) {}); |
| |
| RunLoopUntil([&] { return embedded_module_->is_running(); }); |
| } |
| |
| // The embedded module launches a third module in the story shell. |
| // In this case, the story shell doesn't know about the direct parent of |
| // the third module because it's embedded and its view is not sent to the |
| // story shell. Instead, module one must be used as the display parent module |
| // declared to the story shell for the view of module three. |
| void EmbeddedModuleLaunchesModule() { |
| fuchsia::modular::ModuleControllerPtr third_module_ptr; |
| auto third_module_intent = fuchsia::modular::Intent{.handler = third_module_url_}; |
| embedded_module_->module_context()->AddModuleToStory( |
| kThirdModuleName, std::move(third_module_intent), third_module_ptr.NewRequest(), |
| /* surface_relation */ nullptr, [](const fuchsia::modular::StartModuleStatus) {}); |
| |
| RunLoopUntil([&] { return third_module_->is_running(); }); |
| } |
| |
| modular::testing::FakeSessionShell fake_session_shell_; |
| modular::testing::FakeStoryShell test_story_shell_; |
| std::unique_ptr<modular::testing::FakeModule> parent_module_; |
| std::unique_ptr<modular::testing::FakeModule> embedded_module_; |
| std::unique_ptr<modular::testing::FakeModule> third_module_; |
| modular_testing::TestHarnessBuilder builder_; |
| std::string parent_module_url_; |
| std::string embedded_module_url_; |
| std::string third_module_url_; |
| }; |
| |
| } // namespace |
| |
| // Checks the surface relationships between three modules. |
| TEST_F(StoryShellEmbeddedModTest, SurfaceRelationships) { |
| // Set expectations for AddSurface(). Note that we only expect story shell to |
| // add a surface for non-embedded modules. |
| test_story_shell_.set_on_add_surface([&](fuchsia::modular::ViewConnection view_connection, |
| fuchsia::modular::SurfaceInfo surface_info) { |
| // Continue if this is adding the parent module |
| if (view_connection.surface_id == kParentModuleName) { |
| return; |
| } |
| |
| // These should pass when the third module is added to the story |
| EXPECT_EQ(view_connection.surface_id, "parent_name:embedded_name:third_name"); |
| EXPECT_EQ(surface_info.parent_id, kParentModuleName); |
| }); |
| |
| LaunchParentModule(); |
| |
| // Have the parent module launch an embedded module |
| ParentModuleEmbedsModule(); |
| |
| // Have the embedded module launch a third module |
| EmbeddedModuleLaunchesModule(); |
| } |
| |
| // Checks that embedded modules are not reinflated when stories are retarted. |
| TEST_F(StoryShellEmbeddedModTest, ReinflateModules) { |
| LaunchParentModule(); |
| ParentModuleEmbedsModule(); |
| EmbeddedModuleLaunchesModule(); |
| |
| fuchsia::modular::StoryControllerPtr story_controller; |
| fake_session_shell_.story_provider()->GetController(kStoryName, story_controller.NewRequest()); |
| bool modules_reinflated_correctly{false}; |
| |
| // Stop and restart the story |
| story_controller->Stop([&] { |
| story_controller->RequestStart(); |
| story_controller->GetActiveModules( |
| [&](std::vector<fuchsia::modular::ModuleData> active_modules) mutable { |
| size_t num_embedded_mods = 0u; |
| for (const fuchsia::modular::ModuleData& mod : active_modules) { |
| num_embedded_mods += mod.is_embedded; |
| } |
| if (num_embedded_mods == 0 && active_modules.size() == 2u) { |
| modules_reinflated_correctly = true; |
| } |
| }); |
| }); |
| |
| RunLoopUntil([&] { return modules_reinflated_correctly; }); |
| } |