// Copyright 2017 The Chromium 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 "third_party/blink/renderer/core/loader/modulescript/module_script_loader.h"

#include "base/test/scoped_feature_list.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/tokens/tokens.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/public/platform/web_url_loader_mock_factory.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/loader/modulescript/document_module_script_fetcher.h"
#include "third_party/blink/renderer/core/loader/modulescript/module_script_fetch_request.h"
#include "third_party/blink/renderer/core/loader/modulescript/module_script_loader_client.h"
#include "third_party/blink/renderer/core/loader/modulescript/module_script_loader_registry.h"
#include "third_party/blink/renderer/core/loader/modulescript/worklet_module_script_fetcher.h"
#include "third_party/blink/renderer/core/origin_trials/origin_trial_context.h"
#include "third_party/blink/renderer/core/script/modulator.h"
#include "third_party/blink/renderer/core/script/module_script.h"
#include "third_party/blink/renderer/core/script/script.h"
#include "third_party/blink/renderer/core/testing/dummy_modulator.h"
#include "third_party/blink/renderer/core/testing/page_test_base.h"
#include "third_party/blink/renderer/core/workers/global_scope_creation_params.h"
#include "third_party/blink/renderer/core/workers/worker_thread_test_helper.h"
#include "third_party/blink/renderer/core/workers/worklet_global_scope.h"
#include "third_party/blink/renderer/core/workers/worklet_global_scope_test_helper.h"
#include "third_party/blink/renderer/core/workers/worklet_module_responses_map.h"
#include "third_party/blink/renderer/platform/heap/handle.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_client_settings_object_snapshot.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
#include "third_party/blink/renderer/platform/loader/testing/fetch_testing_platform_support.h"
#include "third_party/blink/renderer/platform/loader/testing/mock_fetch_context.h"
#include "third_party/blink/renderer/platform/loader/testing/test_loader_factory.h"
#include "third_party/blink/renderer/platform/loader/testing/test_resource_fetcher_properties.h"
#include "third_party/blink/renderer/platform/testing/mock_context_lifecycle_notifier.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
#include "third_party/blink/renderer/platform/testing/url_test_helpers.h"

namespace blink {

namespace {

class TestModuleScriptLoaderClient final
    : public GarbageCollected<TestModuleScriptLoaderClient>,
      public ModuleScriptLoaderClient {
 public:
  TestModuleScriptLoaderClient() = default;
  ~TestModuleScriptLoaderClient() override = default;

  void Trace(Visitor* visitor) const override {
    visitor->Trace(module_script_);
  }

  void NotifyNewSingleModuleFinished(ModuleScript* module_script) override {
    was_notify_finished_ = true;
    module_script_ = module_script;
  }

  bool WasNotifyFinished() const { return was_notify_finished_; }
  ModuleScript* GetModuleScript() { return module_script_; }

 private:
  bool was_notify_finished_ = false;
  Member<ModuleScript> module_script_;
};

class ModuleScriptLoaderTestModulator final : public DummyModulator {
 public:
  explicit ModuleScriptLoaderTestModulator(ScriptState* script_state)
      : script_state_(script_state) {}

  ~ModuleScriptLoaderTestModulator() override = default;

  KURL ResolveModuleSpecifier(const String& module_request,
                              const KURL& base_url,
                              String* failure_reason) final {
    return KURL(base_url, module_request);
  }

  ScriptState* GetScriptState() override { return script_state_; }

  void SetModuleRequests(const Vector<String>& requests) {
    requests_.clear();
    for (const String& request : requests) {
      requests_.emplace_back(request, TextPosition::MinimumPosition(),
                             Vector<ImportAssertion>());
    }
  }
  Vector<ModuleRequest> ModuleRequestsFromModuleRecord(
      v8::Local<v8::Module>) override {
    return requests_;
  }

  ModuleScriptFetcher* CreateModuleScriptFetcher(
      ModuleScriptCustomFetchType custom_fetch_type,
      base::PassKey<ModuleScriptLoader> pass_key) override {
    auto* execution_context = ExecutionContext::From(script_state_);
    if (auto* scope = DynamicTo<WorkletGlobalScope>(execution_context)) {
      EXPECT_EQ(ModuleScriptCustomFetchType::kWorkletAddModule,
                custom_fetch_type);
      return MakeGarbageCollected<WorkletModuleScriptFetcher>(
          scope->GetModuleResponsesMap(), pass_key);
    }
    EXPECT_EQ(ModuleScriptCustomFetchType::kNone, custom_fetch_type);
    return MakeGarbageCollected<DocumentModuleScriptFetcher>(pass_key);
  }

  void Trace(Visitor*) const override;

 private:
  Member<ScriptState> script_state_;
  Vector<ModuleRequest> requests_;
};

void ModuleScriptLoaderTestModulator::Trace(Visitor* visitor) const {
  visitor->Trace(script_state_);
  DummyModulator::Trace(visitor);
}

}  // namespace

class ModuleScriptLoaderTest : public PageTestBase {
  DISALLOW_COPY_AND_ASSIGN(ModuleScriptLoaderTest);

 public:
  ModuleScriptLoaderTest();
  void SetUp() override;

  void InitializeForDocument();
  void InitializeForWorklet();

  void TestFetchDataURL(ModuleScriptCustomFetchType,
                        TestModuleScriptLoaderClient*);
  void TestInvalidSpecifier(ModuleScriptCustomFetchType,
                            TestModuleScriptLoaderClient*);
  void TestFetchInvalidURL(ModuleScriptCustomFetchType,
                           TestModuleScriptLoaderClient*);
  void TestFetchURL(ModuleScriptCustomFetchType, TestModuleScriptLoaderClient*);
  void TestFetchDataURLJSONModule(ModuleScriptCustomFetchType custom_fetch_type,
                                  TestModuleScriptLoaderClient* client);
  void TestFetchDataURLInvalidJSONModule(
      ModuleScriptCustomFetchType custom_fetch_type,
      TestModuleScriptLoaderClient* client);

  ModuleScriptLoaderTestModulator* GetModulator() { return modulator_.Get(); }

  void RunUntilIdle() {
    static_cast<scheduler::FakeTaskRunner*>(fetcher_->GetTaskRunner().get())
        ->RunUntilIdle();
  }

 private:
  const base::TickClock* GetTickClock() override {
    return platform_->test_task_runner()->GetMockTickClock();
  }
  base::test::ScopedFeatureList scoped_feature_list_;

 protected:
  const KURL url_;
  const scoped_refptr<const SecurityOrigin> security_origin_;

  Persistent<ResourceFetcher> fetcher_;

  ScopedTestingPlatformSupport<FetchTestingPlatformSupport> platform_;
  std::unique_ptr<MockWorkerReportingProxy> reporting_proxy_;
  Persistent<ModuleScriptLoaderTestModulator> modulator_;
  Persistent<WorkletGlobalScope> global_scope_;
};

void ModuleScriptLoaderTest::SetUp() {
  PageTestBase::SetUp(IntSize(500, 500));
}

ModuleScriptLoaderTest::ModuleScriptLoaderTest()
    : url_("https://example.test"),
      security_origin_(SecurityOrigin::Create(url_)) {
  scoped_feature_list_.InitAndEnableFeature(blink::features::kJSONModules);
  platform_->AdvanceClockSeconds(1.);  // For non-zero DocumentParserTimings
}

void ModuleScriptLoaderTest::InitializeForDocument() {
  auto* fetch_context = MakeGarbageCollected<MockFetchContext>();
  auto* properties =
      MakeGarbageCollected<TestResourceFetcherProperties>(security_origin_);
  fetcher_ = MakeGarbageCollected<ResourceFetcher>(
      ResourceFetcherInit(properties->MakeDetachable(), fetch_context,
                          base::MakeRefCounted<scheduler::FakeTaskRunner>(),
                          base::MakeRefCounted<scheduler::FakeTaskRunner>(),
                          MakeGarbageCollected<TestLoaderFactory>(
                              platform_->GetURLLoaderMockFactory()),
                          MakeGarbageCollected<MockContextLifecycleNotifier>(),
                          nullptr /* back_forward_cache_loader_helper */));
  modulator_ = MakeGarbageCollected<ModuleScriptLoaderTestModulator>(
      ToScriptStateForMainWorld(&GetFrame()));
}

void ModuleScriptLoaderTest::InitializeForWorklet() {
  auto* fetch_context = MakeGarbageCollected<MockFetchContext>();
  auto* properties =
      MakeGarbageCollected<TestResourceFetcherProperties>(security_origin_);
  fetcher_ = MakeGarbageCollected<ResourceFetcher>(
      ResourceFetcherInit(properties->MakeDetachable(), fetch_context,
                          base::MakeRefCounted<scheduler::FakeTaskRunner>(),
                          base::MakeRefCounted<scheduler::FakeTaskRunner>(),
                          MakeGarbageCollected<TestLoaderFactory>(
                              platform_->GetURLLoaderMockFactory()),
                          MakeGarbageCollected<MockContextLifecycleNotifier>(),
                          nullptr /* back_forward_cache_loader_helper */));
  reporting_proxy_ = std::make_unique<MockWorkerReportingProxy>();
  auto creation_params = std::make_unique<GlobalScopeCreationParams>(
      url_, mojom::blink::ScriptType::kModule, "GlobalScopeName", "UserAgent",
      UserAgentMetadata(), nullptr /* web_worker_fetch_context */,
      Vector<network::mojom::blink::ContentSecurityPolicyPtr>(),
      network::mojom::ReferrerPolicy::kDefault, security_origin_.get(),
      true /* is_secure_context */, HttpsState::kModern,
      nullptr /* worker_clients */, nullptr /* content_settings_client */,
      network::mojom::IPAddressSpace::kLocal, nullptr /* origin_trial_token */,
      base::UnguessableToken::Create(), nullptr /* worker_settings */,
      mojom::blink::V8CacheOptions::kDefault,
      MakeGarbageCollected<WorkletModuleResponsesMap>(),
      mojo::NullRemote() /* browser_interface_broker */,
      BeginFrameProviderParams(), nullptr /* parent_permissions_policy */,
      base::UnguessableToken::Create() /* agent_cluster_id */);
  creation_params->parent_context_token = GetFrame().GetLocalFrameToken();
  global_scope_ = MakeGarbageCollected<FakeWorkletGlobalScope>(
      std::move(creation_params), *reporting_proxy_, &GetFrame(),
      false /* create_microtask_queue */);
  global_scope_->ScriptController()->Initialize(NullURL());
  modulator_ = MakeGarbageCollected<ModuleScriptLoaderTestModulator>(
      global_scope_->ScriptController()->GetScriptState());
}
// TODO(nhiroki): Add tests for workers.

void ModuleScriptLoaderTest::TestFetchDataURL(
    ModuleScriptCustomFetchType custom_fetch_type,
    TestModuleScriptLoaderClient* client) {
  auto* registry = MakeGarbageCollected<ModuleScriptLoaderRegistry>();
  KURL url("data:text/javascript,export default 'grapes';");
  ModuleScriptLoader::Fetch(
      ModuleScriptFetchRequest::CreateForTest(url, ModuleType::kJavaScript),
      fetcher_, ModuleGraphLevel::kTopLevelModuleFetch, GetModulator(),
      custom_fetch_type, registry, client);
}

void ModuleScriptLoaderTest::TestFetchDataURLJSONModule(
    ModuleScriptCustomFetchType custom_fetch_type,
    TestModuleScriptLoaderClient* client) {
  auto* registry = MakeGarbageCollected<ModuleScriptLoaderRegistry>();
  KURL url(
      "data:application/"
      "json,{\"1\":{\"name\":\"MIKE\",\"surname\":\"TAYLOR\"},\"2\":{\"name\":"
      "\"TOM\",\"surname\":\"JERRY\"}}");
  ModuleScriptLoader::Fetch(
      ModuleScriptFetchRequest::CreateForTest(url, ModuleType::kJSON), fetcher_,
      ModuleGraphLevel::kTopLevelModuleFetch, GetModulator(), custom_fetch_type,
      registry, client);
}

void ModuleScriptLoaderTest::TestFetchDataURLInvalidJSONModule(
    ModuleScriptCustomFetchType custom_fetch_type,
    TestModuleScriptLoaderClient* client) {
  auto* registry = MakeGarbageCollected<ModuleScriptLoaderRegistry>();
  KURL url(
      "data:application/"
      "json,{{{");
  ModuleScriptLoader::Fetch(
      ModuleScriptFetchRequest::CreateForTest(url, ModuleType::kJSON), fetcher_,
      ModuleGraphLevel::kTopLevelModuleFetch, GetModulator(), custom_fetch_type,
      registry, client);
}

TEST_F(ModuleScriptLoaderTest, FetchDataURL) {
  InitializeForDocument();
  TestModuleScriptLoaderClient* client =
      MakeGarbageCollected<TestModuleScriptLoaderClient>();
  TestFetchDataURL(ModuleScriptCustomFetchType::kNone, client);

  // TODO(leszeks): This should finish synchronously, but currently due
  // to the script resource/script streamer interaction, it does not.
  RunUntilIdle();
  EXPECT_TRUE(client->WasNotifyFinished());
  ASSERT_TRUE(client->GetModuleScript());
  EXPECT_FALSE(client->GetModuleScript()->HasEmptyRecord());
  EXPECT_FALSE(client->GetModuleScript()->HasParseError());
}

TEST_F(ModuleScriptLoaderTest, FetchDataURLJSONModule) {
  InitializeForDocument();
  TestModuleScriptLoaderClient* client =
      MakeGarbageCollected<TestModuleScriptLoaderClient>();
  TestFetchDataURLJSONModule(ModuleScriptCustomFetchType::kNone, client);

  // TODO(leszeks): This should finish synchronously, but currently due
  // to the script resource/script streamer interaction, it does not.
  RunUntilIdle();
  EXPECT_TRUE(client->WasNotifyFinished());
  ASSERT_TRUE(client->GetModuleScript());
  EXPECT_FALSE(client->GetModuleScript()->HasEmptyRecord());
  EXPECT_FALSE(client->GetModuleScript()->HasParseError());
}

TEST_F(ModuleScriptLoaderTest, FetchDataURLInvalidJSONModule) {
  InitializeForDocument();
  TestModuleScriptLoaderClient* client =
      MakeGarbageCollected<TestModuleScriptLoaderClient>();
  TestFetchDataURLInvalidJSONModule(ModuleScriptCustomFetchType::kNone, client);

  // TODO(leszeks): This should finish synchronously, but currently due
  // to the script resource/script streamer interaction, it does not.
  RunUntilIdle();
  EXPECT_TRUE(client->WasNotifyFinished());
  ASSERT_TRUE(client->GetModuleScript());
  EXPECT_TRUE(client->GetModuleScript()->HasEmptyRecord());
  EXPECT_TRUE(client->GetModuleScript()->HasParseError());
}

TEST_F(ModuleScriptLoaderTest, FetchDataURL_OnWorklet) {
  InitializeForWorklet();
  TestModuleScriptLoaderClient* client1 =
      MakeGarbageCollected<TestModuleScriptLoaderClient>();
  TestFetchDataURL(ModuleScriptCustomFetchType::kWorkletAddModule, client1);

  EXPECT_FALSE(client1->WasNotifyFinished())
      << "ModuleScriptLoader should finish asynchronously.";
  RunUntilIdle();

  EXPECT_TRUE(client1->WasNotifyFinished());
  ASSERT_TRUE(client1->GetModuleScript());
  EXPECT_FALSE(client1->GetModuleScript()->HasEmptyRecord());
  EXPECT_FALSE(client1->GetModuleScript()->HasParseError());

  // Try to fetch the same URL again in order to verify the case where
  // WorkletModuleResponsesMap serves a cache.
  TestModuleScriptLoaderClient* client2 =
      MakeGarbageCollected<TestModuleScriptLoaderClient>();
  TestFetchDataURL(ModuleScriptCustomFetchType::kWorkletAddModule, client2);

  EXPECT_FALSE(client2->WasNotifyFinished())
      << "ModuleScriptLoader should finish asynchronously.";
  RunUntilIdle();

  EXPECT_TRUE(client2->WasNotifyFinished());
  ASSERT_TRUE(client2->GetModuleScript());
  EXPECT_FALSE(client2->GetModuleScript()->HasEmptyRecord());
  EXPECT_FALSE(client2->GetModuleScript()->HasParseError());
}

TEST_F(ModuleScriptLoaderTest, FetchDataURLJSONModule_OnWorklet) {
  InitializeForWorklet();
  TestModuleScriptLoaderClient* client1 =
      MakeGarbageCollected<TestModuleScriptLoaderClient>();
  TestFetchDataURLJSONModule(ModuleScriptCustomFetchType::kWorkletAddModule,
                             client1);

  EXPECT_FALSE(client1->WasNotifyFinished())
      << "ModuleScriptLoader should finish asynchronously.";
  RunUntilIdle();

  EXPECT_TRUE(client1->WasNotifyFinished());
  ASSERT_TRUE(client1->GetModuleScript());
  EXPECT_FALSE(client1->GetModuleScript()->HasEmptyRecord());
  EXPECT_FALSE(client1->GetModuleScript()->HasParseError());

  // Try to fetch the same URL again in order to verify the case where
  // WorkletModuleResponsesMap serves a cache.
  TestModuleScriptLoaderClient* client2 =
      MakeGarbageCollected<TestModuleScriptLoaderClient>();
  TestFetchDataURLJSONModule(ModuleScriptCustomFetchType::kWorkletAddModule,
                             client2);

  EXPECT_FALSE(client2->WasNotifyFinished())
      << "ModuleScriptLoader should finish asynchronously.";
  RunUntilIdle();

  EXPECT_TRUE(client2->WasNotifyFinished());
  ASSERT_TRUE(client2->GetModuleScript());
  EXPECT_FALSE(client2->GetModuleScript()->HasEmptyRecord());
  EXPECT_FALSE(client2->GetModuleScript()->HasParseError());
}

TEST_F(ModuleScriptLoaderTest, FetchDataURLInvalidJSONModule_OnWorklet) {
  InitializeForWorklet();
  TestModuleScriptLoaderClient* client1 =
      MakeGarbageCollected<TestModuleScriptLoaderClient>();
  TestFetchDataURLInvalidJSONModule(
      ModuleScriptCustomFetchType::kWorkletAddModule, client1);

  EXPECT_FALSE(client1->WasNotifyFinished())
      << "ModuleScriptLoader should finish asynchronously.";
  RunUntilIdle();

  EXPECT_TRUE(client1->WasNotifyFinished());
  ASSERT_TRUE(client1->GetModuleScript());
  EXPECT_TRUE(client1->GetModuleScript()->HasEmptyRecord());
  EXPECT_TRUE(client1->GetModuleScript()->HasParseError());

  // Try to fetch the same URL again in order to verify the case where
  // WorkletModuleResponsesMap serves a cache.
  TestModuleScriptLoaderClient* client2 =
      MakeGarbageCollected<TestModuleScriptLoaderClient>();
  TestFetchDataURLInvalidJSONModule(
      ModuleScriptCustomFetchType::kWorkletAddModule, client2);

  EXPECT_FALSE(client2->WasNotifyFinished())
      << "ModuleScriptLoader should finish asynchronously.";
  RunUntilIdle();

  EXPECT_TRUE(client2->WasNotifyFinished());
  ASSERT_TRUE(client2->GetModuleScript());
  EXPECT_TRUE(client2->GetModuleScript()->HasEmptyRecord());
  EXPECT_TRUE(client2->GetModuleScript()->HasParseError());
}

void ModuleScriptLoaderTest::TestInvalidSpecifier(
    ModuleScriptCustomFetchType custom_fetch_type,
    TestModuleScriptLoaderClient* client) {
  auto* registry = MakeGarbageCollected<ModuleScriptLoaderRegistry>();
  KURL url("data:text/javascript,import 'invalid';export default 'grapes';");
  GetModulator()->SetModuleRequests({"invalid"});
  ModuleScriptLoader::Fetch(
      ModuleScriptFetchRequest::CreateForTest(url, ModuleType::kJavaScript),
      fetcher_, ModuleGraphLevel::kTopLevelModuleFetch, GetModulator(),
      custom_fetch_type, registry, client);
}

TEST_F(ModuleScriptLoaderTest, InvalidSpecifier) {
  InitializeForDocument();
  TestModuleScriptLoaderClient* client =
      MakeGarbageCollected<TestModuleScriptLoaderClient>();
  TestInvalidSpecifier(ModuleScriptCustomFetchType::kNone, client);

  // TODO(leszeks): This should finish synchronously, but currently due
  // to the script resource/script streamer interaction, it does not.
  RunUntilIdle();
  EXPECT_TRUE(client->WasNotifyFinished());

  ASSERT_TRUE(client->GetModuleScript());
  EXPECT_TRUE(client->GetModuleScript()->HasEmptyRecord());
  EXPECT_TRUE(client->GetModuleScript()->HasParseError());
}

TEST_F(ModuleScriptLoaderTest, InvalidSpecifier_OnWorklet) {
  InitializeForWorklet();
  TestModuleScriptLoaderClient* client =
      MakeGarbageCollected<TestModuleScriptLoaderClient>();
  TestInvalidSpecifier(ModuleScriptCustomFetchType::kWorkletAddModule, client);

  EXPECT_FALSE(client->WasNotifyFinished())
      << "ModuleScriptLoader should finish asynchronously.";
  RunUntilIdle();

  EXPECT_TRUE(client->WasNotifyFinished());
  ASSERT_TRUE(client->GetModuleScript());
  EXPECT_TRUE(client->GetModuleScript()->HasEmptyRecord());
  EXPECT_TRUE(client->GetModuleScript()->HasParseError());
}

void ModuleScriptLoaderTest::TestFetchInvalidURL(
    ModuleScriptCustomFetchType custom_fetch_type,
    TestModuleScriptLoaderClient* client) {
  auto* registry = MakeGarbageCollected<ModuleScriptLoaderRegistry>();
  KURL url;
  EXPECT_FALSE(url.IsValid());
  ModuleScriptLoader::Fetch(
      ModuleScriptFetchRequest::CreateForTest(url, ModuleType::kJavaScript),
      fetcher_, ModuleGraphLevel::kTopLevelModuleFetch, GetModulator(),
      custom_fetch_type, registry, client);
}

TEST_F(ModuleScriptLoaderTest, FetchInvalidURL) {
  InitializeForDocument();
  TestModuleScriptLoaderClient* client =
      MakeGarbageCollected<TestModuleScriptLoaderClient>();
  TestFetchInvalidURL(ModuleScriptCustomFetchType::kNone, client);

  // TODO(leszeks): This should finish synchronously, but currently due
  // to the script resource/script streamer interaction, it does not.
  RunUntilIdle();
  EXPECT_TRUE(client->WasNotifyFinished());
  EXPECT_FALSE(client->GetModuleScript());
}

TEST_F(ModuleScriptLoaderTest, FetchInvalidURL_OnWorklet) {
  InitializeForWorklet();
  TestModuleScriptLoaderClient* client =
      MakeGarbageCollected<TestModuleScriptLoaderClient>();
  TestFetchInvalidURL(ModuleScriptCustomFetchType::kWorkletAddModule, client);

  EXPECT_FALSE(client->WasNotifyFinished())
      << "ModuleScriptLoader should finish asynchronously.";
  RunUntilIdle();

  EXPECT_TRUE(client->WasNotifyFinished());
  EXPECT_FALSE(client->GetModuleScript());
}

void ModuleScriptLoaderTest::TestFetchURL(
    ModuleScriptCustomFetchType custom_fetch_type,
    TestModuleScriptLoaderClient* client) {
  KURL url("https://example.test/module.js");
  url_test_helpers::RegisterMockedURLLoad(
      url, test::CoreTestDataPath("module.js"), "text/javascript",
      platform_->GetURLLoaderMockFactory());

  auto* registry = MakeGarbageCollected<ModuleScriptLoaderRegistry>();
  ModuleScriptLoader::Fetch(
      ModuleScriptFetchRequest::CreateForTest(url, ModuleType::kJavaScript),
      fetcher_, ModuleGraphLevel::kTopLevelModuleFetch, GetModulator(),
      custom_fetch_type, registry, client);
}

TEST_F(ModuleScriptLoaderTest, FetchURL) {
  InitializeForDocument();
  TestModuleScriptLoaderClient* client =
      MakeGarbageCollected<TestModuleScriptLoaderClient>();
  TestFetchURL(ModuleScriptCustomFetchType::kNone, client);

  EXPECT_FALSE(client->WasNotifyFinished())
      << "ModuleScriptLoader unexpectedly finished synchronously.";
  platform_->GetURLLoaderMockFactory()->ServeAsynchronousRequests();
  // TODO(leszeks): This should finish synchronously, but currently due
  // to the script resource/script streamer interaction, it does not.
  RunUntilIdle();

  EXPECT_TRUE(client->WasNotifyFinished());
  EXPECT_TRUE(client->GetModuleScript());
}

TEST_F(ModuleScriptLoaderTest, FetchURL_OnWorklet) {
  InitializeForWorklet();
  TestModuleScriptLoaderClient* client =
      MakeGarbageCollected<TestModuleScriptLoaderClient>();
  TestFetchURL(ModuleScriptCustomFetchType::kWorkletAddModule, client);

  EXPECT_FALSE(client->WasNotifyFinished())
      << "ModuleScriptLoader unexpectedly finished synchronously.";
  platform_->GetURLLoaderMockFactory()->ServeAsynchronousRequests();
  RunUntilIdle();

  EXPECT_TRUE(client->WasNotifyFinished());
  EXPECT_TRUE(client->GetModuleScript());
}

}  // namespace blink
