chromium/chrome/browser/ui/webui/ash/office_fallback/office_fallback_dialog_browsertest.cc

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <utility>

#include "base/test/bind.h"
#include "base/test/run_until.h"
#include "chrome/browser/ash/file_manager/file_manager_test_util.h"
#include "chrome/browser/ash/file_manager/office_file_tasks.h"
#include "chrome/browser/ash/file_system_provider/service.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/webui/ash/office_fallback/office_fallback_dialog.h"
#include "chrome/browser/ui/webui/ash/office_fallback/office_fallback_ui.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chromeos/constants/chromeos_features.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "ui/base/l10n/l10n_util.h"

namespace ash::office_fallback {

namespace {
static const char kTaskTitle[] = "some app";
}  // namespace

content::WebContents* GetWebContentsFromOfficeFallbackDialog() {
  ash::SystemWebDialogDelegate* dialog =
      ash::SystemWebDialogDelegate::FindInstance(
          chrome::kChromeUIOfficeFallbackURL);
  EXPECT_TRUE(dialog);
  content::WebUI* webui = dialog->GetWebUIForTest();
  EXPECT_TRUE(webui);
  content::WebContents* web_contents = webui->GetWebContents();
  EXPECT_TRUE(web_contents);
  return web_contents;
}

// Launch the Office Fallback dialog at chrome://office-fallback by calling
// OfficeFallbackDialog::Show() with the arguments provided. Wait until the
// "office-fallback" DOM element exists at chrome://office-fallback and return
// the web contents.
content::WebContents* LaunchOfficeFallbackDialogAndGetWebContentsForDialog(
    const std::vector<storage::FileSystemURL>& file_urls,
    FallbackReason fallback_reason,
    const std::string& task_title,
    DialogChoiceCallback callback) {
  // Watch for Office Fallback dialog URL chrome://office-fallback.
  content::TestNavigationObserver navigation_observer_dialog(
      (GURL(chrome::kChromeUIOfficeFallbackURL)));
  navigation_observer_dialog.StartWatchingNewWebContents();

  // Launch Office Fallback dialog.
  EXPECT_TRUE(OfficeFallbackDialog::Show(file_urls, fallback_reason, task_title,
                                         std::move(callback)));

  // Wait for chrome://office-fallback to open.
  navigation_observer_dialog.Wait();
  EXPECT_TRUE(navigation_observer_dialog.last_navigation_succeeded());

  // Get the web contents of the dialog to be able to check that it is exists.
  content::WebContents* web_contents = GetWebContentsFromOfficeFallbackDialog();

  // Wait until the DOM element actually exists at office-fallback.
  EXPECT_TRUE(base::test::RunUntil([&] {
    return content::EvalJs(web_contents,
                           "!!document.querySelector('office-fallback')")
        .ExtractBool();
  }));

  return web_contents;
}

class OfficeFallbackDialogBrowserTest : public InProcessBrowserTest {
 public:
  OfficeFallbackDialogBrowserTest() {
    feature_list_.InitWithFeatures(
        {chromeos::features::kUploadOfficeToCloud,
         chromeos::features::kMicrosoftOneDriveIntegrationForEnterprise,
         chromeos::features::kUploadOfficeToCloudForEnterprise},
        {});
  }

  OfficeFallbackDialogBrowserTest(const OfficeFallbackDialogBrowserTest&) =
      delete;
  OfficeFallbackDialogBrowserTest& operator=(
      const OfficeFallbackDialogBrowserTest&) = delete;

  void SetUpOnMainThread() override {
    InProcessBrowserTest::SetUpOnMainThread();

    // This is needed to simulate the presence of the ODFS extension, which is
    // checked in `IsMicrosoftOfficeOneDriveIntegrationAllowedAndOdfsInstalled`.
    auto fake_provider =
        ash::file_system_provider::FakeExtensionProvider::Create(
            extension_misc::kODFSExtensionId);
    auto* service =
        ash::file_system_provider::Service::Get(browser()->profile());
    service->RegisterProvider(std::move(fake_provider));

    files_ = file_manager::test::CopyTestFilesIntoMyFiles(browser()->profile(),
                                                          {"text.docx"});
  }

 protected:
  std::vector<storage::FileSystemURL> files_;

 private:
  base::test::ScopedFeatureList feature_list_;
};

// Test which launches an `OfficeFallbackDialog` which in turn creates an
// `OfficeFallbackElement`. Tests that the correct title and reason is displayed
// when the fallback reason is that the system is offline.
IN_PROC_BROWSER_TEST_F(OfficeFallbackDialogBrowserTest,
                       OfficeFallbackDialogWhenOffline) {
  // Launch Office Fallback dialog.
  const std::string application_name = "Test app name";
  content::WebContents* web_contents =
      LaunchOfficeFallbackDialogAndGetWebContentsForDialog(
          files_, FallbackReason::kOffline, application_name,
          base::DoNothing());

  content::EvalJsResult eval_result_title =
      content::EvalJs(web_contents,
                      "document.querySelector('office-fallback')"
                      ".$('#title').innerText");
  EXPECT_EQ(eval_result_title.ExtractString(),
            l10n_util::GetStringFUTF8(
                IDS_OFFICE_FALLBACK_TITLE_OFFLINE,
                files_.front().path().BaseName().LossyDisplayName()));

  content::EvalJsResult eval_result_reason =
      content::EvalJs(web_contents,
                      "document.querySelector('office-fallback')"
                      ".$('#reason-message').innerText");
  EXPECT_EQ(eval_result_reason.ExtractString(),
            l10n_util::GetStringFUTF8(IDS_OFFICE_FALLBACK_REASON_OFFLINE,
                                      base::UTF8ToUTF16(application_name)));
}

// Test which launches an `OfficeFallbackDialog` which in turn creates an
// `OfficeFallbackElement`. Tests that the correct title is displayed when the
// fallback reason is that Drive authentication is not ready.
IN_PROC_BROWSER_TEST_F(OfficeFallbackDialogBrowserTest,
                       OfficeFallbackDialogWhenDriveAuthenticationNotReady) {
  // Launch Office Fallback dialog.
  content::WebContents* web_contents =
      LaunchOfficeFallbackDialogAndGetWebContentsForDialog(
          files_, FallbackReason::kDriveAuthenticationNotReady, kTaskTitle,
          base::DoNothing());

  content::EvalJsResult eval_result =
      content::EvalJs(web_contents,
                      "document.querySelector('office-fallback')"
                      ".$('#title').innerText");
  EXPECT_EQ(eval_result.ExtractString(),
            l10n_util::GetStringFUTF8(
                IDS_OFFICE_FALLBACK_TITLE_OFFLINE,
                files_.front().path().BaseName().LossyDisplayName()));
}

// Test which launches an `OfficeFallbackDialog` which in turn creates an
// `OfficeFallbackElement`. Tests that the correct instructions are displayed
// when the fallback reason is that the disable Drive preference is set.
IN_PROC_BROWSER_TEST_F(OfficeFallbackDialogBrowserTest,
                       OfficeFallbackDialogWhenDisableDrivePreferenceSet) {
  // Launch Office Fallback dialog.
  content::WebContents* web_contents =
      LaunchOfficeFallbackDialogAndGetWebContentsForDialog(
          files_, FallbackReason::kDisableDrivePreferenceSet, kTaskTitle,
          base::DoNothing());

  content::EvalJsResult eval_result =
      content::EvalJs(web_contents,
                      "document.querySelector('office-fallback')"
                      ".$('#instructions-message').innerText");
  EXPECT_EQ(eval_result.ExtractString(),
            l10n_util::GetStringUTF8(
                IDS_OFFICE_FALLBACK_INSTRUCTIONS_DISABLE_DRIVE_PREFERENCE));
}

// Test which launches an `OfficeFallbackDialog` which in turn creates an
// `OfficeFallbackElement`. Tests that the correct reason and instructions are
// displayed when the fallback reason is that Drive is unavailable for the
// account type.
IN_PROC_BROWSER_TEST_F(OfficeFallbackDialogBrowserTest,
                       OfficeFallbackDialogWhenDriveDisabledForAccountType) {
  // Launch Office Fallback dialog.
  content::WebContents* web_contents =
      LaunchOfficeFallbackDialogAndGetWebContentsForDialog(
          files_, FallbackReason::kDriveDisabledForAccountType, kTaskTitle,
          base::DoNothing());

  content::EvalJsResult eval_result_instructions =
      content::EvalJs(web_contents,
                      "document.querySelector('office-fallback')"
                      ".$('#instructions-message').innerText");
  EXPECT_EQ(eval_result_instructions.ExtractString(),
            l10n_util::GetStringUTF8(
                IDS_OFFICE_FALLBACK_INSTRUCTIONS_DRIVE_DISABLED_FOR_ACCOUNT));

  content::EvalJsResult eval_result_reason =
      content::EvalJs(web_contents,
                      "document.querySelector('office-fallback')"
                      ".$('#reason-message').innerText");
  EXPECT_EQ(eval_result_reason.ExtractString(),
            l10n_util::GetStringUTF8(
                IDS_OFFICE_FALLBACK_REASON_DRIVE_DISABLED_FOR_ACCOUNT));
}

// Test which launches an `OfficeFallbackDialog` which in turn creates an
// `OfficeFallbackElement`. Tests that the correct instructions are displayed
// when the fallback reason is that Drive has not service.
IN_PROC_BROWSER_TEST_F(OfficeFallbackDialogBrowserTest,
                       OfficeFallbackDialogWhenNoDriveService) {
  // Launch Office Fallback dialog.
  content::WebContents* web_contents =
      LaunchOfficeFallbackDialogAndGetWebContentsForDialog(
          files_, FallbackReason::kNoDriveService, kTaskTitle,
          base::DoNothing());

  content::EvalJsResult eval_result =
      content::EvalJs(web_contents,
                      "document.querySelector('office-fallback')"
                      ".$('#instructions-message').innerText");
  EXPECT_EQ(eval_result.ExtractString(),
            l10n_util::GetStringUTF8(IDS_OFFICE_FALLBACK_INSTRUCTIONS));
}

// Test which launches an `OfficeFallbackDialog` which in turn creates an
// `OfficeFallbackElement`. Tests that the correct instructions are displayed
// when the fallback reason is that the file is still waiting to be uploaded,
// and that the correct user choice is received after clicking the OK button.
IN_PROC_BROWSER_TEST_F(OfficeFallbackDialogBrowserTest,
                       OfficeFallbackDialogWhenWaitingForUpload) {
  base::RunLoop run_loop;
  // Launch Office Fallback dialog.
  content::WebContents* web_contents =
      LaunchOfficeFallbackDialogAndGetWebContentsForDialog(
          files_, FallbackReason::kWaitingForUpload, kTaskTitle,
          base::BindLambdaForTesting(
              [&run_loop](std::optional<const std::string> choice) {
                // Expect the dialog is closed with the "cancel" user choice.
                if (choice.has_value() &&
                    choice.value() == ash::office_fallback::kDialogChoiceOk) {
                  run_loop.Quit();
                }
              }));

  // Check the displayed instruction.
  content::EvalJsResult eval_result =
      content::EvalJs(web_contents,
                      "document.querySelector('office-fallback')"
                      ".$('#instructions-message').innerText");
  EXPECT_EQ(eval_result.ExtractString(),
            l10n_util::GetStringUTF8(
                IDS_OFFICE_FALLBACK_INSTRUCTIONS_WAITING_FOR_UPLOAD));

  // Click the OK button and wait until the dialog is closed with the correct
  // user choice.
  EXPECT_TRUE(content::ExecJs(web_contents,
                              "document.querySelector('office-fallback')"
                              ".$('#ok-button').click()"));
  run_loop.Run();
}

// Test which launches an `OfficeFallbackDialog` which in turn creates an
// `OfficeFallbackElement`. Tests that the correct instructions are displayed
// when the fallback reason is that the file cannot be open from its current
// Android OneDrive location, and that the correct user choice is received after
// clicking the OK button.
IN_PROC_BROWSER_TEST_F(
    OfficeFallbackDialogBrowserTest,
    OfficeFallbackDialogWhenAndroidOneDriveLocationNotSupported) {
  base::RunLoop run_loop;
  // Launch Office Fallback dialog.
  content::WebContents* web_contents =
      LaunchOfficeFallbackDialogAndGetWebContentsForDialog(
          files_, FallbackReason::kAndroidOneDriveUnsupportedLocation,
          "Microsoft 365",
          base::BindLambdaForTesting(
              [&run_loop](std::optional<const std::string> choice) {
                // Expect the dialog is closed with the "OK" user choice.
                if (choice.has_value() &&
                    choice.value() == ash::office_fallback::kDialogChoiceOk) {
                  run_loop.Quit();
                }
              }));

  // Check the displayed instruction.
  content::EvalJsResult eval_result =
      content::EvalJs(web_contents,
                      "document.querySelector('office-fallback')"
                      ".$('#instructions-message').innerText");
  EXPECT_EQ(
      eval_result.ExtractString(),
      l10n_util::GetStringUTF8(
          IDS_OFFICE_FALLBACK_INSTRUCTIONS_ANDROID_ONE_DRIVE_LOCATION_NOT_SUPPORTED));

  // Click the OK button and wait until the dialog is closed with the correct
  // user choice.
  EXPECT_TRUE(content::ExecJs(web_contents,
                              "document.querySelector('office-fallback')"
                              ".$('#ok-button').click()"));
  run_loop.Run();
}

// Test which launches an `OfficeFallbackDialog` which in turn creates an
// `OfficeFallbackElement`. Tests that the cancel button works.
IN_PROC_BROWSER_TEST_F(OfficeFallbackDialogBrowserTest, ClickCancel) {
  base::RunLoop run_loop;
  content::WebContents* web_contents =
      LaunchOfficeFallbackDialogAndGetWebContentsForDialog(
          files_, FallbackReason::kOffline, kTaskTitle,
          base::BindLambdaForTesting(
              [&run_loop](std::optional<const std::string> choice) {
                // Expect the dialog is closed with the "cancel" user choice.
                if (choice.has_value() &&
                    choice.value() ==
                        ash::office_fallback::kDialogChoiceCancel) {
                  run_loop.Quit();
                }
              }));

  // Click the close button and wait until the dialog is closed with the correct
  // user choice.
  EXPECT_TRUE(content::ExecJs(web_contents,
                              "document.querySelector('office-fallback')"
                              ".$('#cancel-button').click()"));

  run_loop.Run();
}

// Test which launches an `OfficeFallbackDialog` which in turn creates an
// `OfficeFallbackElement`. Tests that the try again button works.
IN_PROC_BROWSER_TEST_F(OfficeFallbackDialogBrowserTest, ClickTryAgain) {
  base::RunLoop run_loop;
  content::WebContents* web_contents =
      LaunchOfficeFallbackDialogAndGetWebContentsForDialog(
          files_, FallbackReason::kOffline, kTaskTitle,
          base::BindLambdaForTesting(
              [&run_loop](std::optional<const std::string> choice) {
                // Expect the dialog is closed with the "cancel" user choice.
                if (choice.has_value() &&
                    choice.value() ==
                        ash::office_fallback::kDialogChoiceTryAgain) {
                  run_loop.Quit();
                }
              }));

  // Click the try again button and wait until the dialog is closed with the
  // correct user choice.
  EXPECT_TRUE(content::ExecJs(web_contents,
                              "document.querySelector('office-fallback')"
                              ".$('#try-again-button').click()"));

  run_loop.Run();
}

// Test which launches an `OfficeFallbackDialog` which in turn creates an
// `OfficeFallbackElement`. Tests that the quick office button works.
IN_PROC_BROWSER_TEST_F(OfficeFallbackDialogBrowserTest, ClickQuickOffice) {
  base::RunLoop run_loop;
  content::WebContents* web_contents =
      LaunchOfficeFallbackDialogAndGetWebContentsForDialog(
          files_, FallbackReason::kOffline, kTaskTitle,
          base::BindLambdaForTesting(
              [&run_loop](std::optional<const std::string> choice) {
                // Expect the dialog is closed with the "cancel" user choice.
                if (choice.has_value() &&
                    choice.value() ==
                        ash::office_fallback::kDialogChoiceQuickOffice) {
                  run_loop.Quit();
                }
              }));

  // Click the try again button and wait until the dialog is closed with the
  // correct user choice.
  EXPECT_TRUE(content::ExecJs(web_contents,
                              "document.querySelector('office-fallback')"
                              ".$('#quick-office-button').click()"));

  run_loop.Run();
}

}  // namespace ash::office_fallback