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

#include "chrome/browser/ui/webui/tab_strip_internals/tab_strip_internals_observer.h"

#include <memory>
#include <vector>

#include "base/functional/callback.h"
#include "base/test/mock_callback.h"
#include "chrome/browser/prefs/session_startup_pref.h"
#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
#include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/tab_restore_service_factory.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h"
#include "chrome/browser/ui/tabs/split_tab_metrics.h"
#include "chrome/browser/ui/tabs/tab_group_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/keep_alive_registry/keep_alive_types.h"
#include "components/keep_alive_registry/scoped_keep_alive.h"
#include "components/sessions/core/session_id.h"
#include "components/sessions/core/session_types.h"
#include "components/tabs/public/tab_group.h"
#include "content/public/test/browser_test.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/base/page_transition_types.h"
#include "url/gurl.h"
#include "url/url_constants.h"

// TODO(crbug.com/427204855): Revisit the flaky tests that currently use
// `Times(AtLeast())` expectations. The number of callback invocations should
// ideally be deterministic and predictable for each test case. Consider
// replacing them with exact expectations once the underlying cause of
// nondeterminism is understood. Investigate whether using a host resolver
// helps stabilize tests that internally trigger navigation related events.
// For example, tests triggering a navigation to "chrome://newtab/" or
// "chrome://about:blank"
using ::testing::AtLeast;

// Tests that the TabStripInternalsObserver correctly forwards callbacks
// from TabStripModelObserver and BrowserListObserver.
class TabStripInternalsObserverBrowserTest : public InProcessBrowserTest {
 protected:
  BrowserWindowInterface* QuitBrowserAndRestore(
      BrowserWindowInterface* browser) {
    Profile* const profile = browser->GetProfile();
    // Session restore pref must be set to LAST to test browser close and
    // restore.
    SessionStartupPref pref(SessionStartupPref::LAST);
    SessionStartupPref::SetStartupPref(profile, pref);
    // Keep the browser and profile alive while closing the browser to trigger
    // session restore.
    ScopedKeepAlive keep_alive(KeepAliveOrigin::SESSION_RESTORE,
                               KeepAliveRestartOption::DISABLED);
    ScopedProfileKeepAlive profile_keep_alive(
        profile, ProfileKeepAliveOrigin::kBrowserWindow);
    CloseBrowserSynchronously(browser);

    // Create a new window, which will trigger session restore.
    ui_test_utils::OpenNewEmptyWindowAndWaitUntilActivated(profile, true);
    return GetLastActiveBrowserWindowInterfaceWithAnyProfile();
  }
};

// BrowserAdded: Observe when a browser is added to BrowserList.
IN_PROC_BROWSER_TEST_F(TabStripInternalsObserverBrowserTest, BrowserAdded) {
  BrowserWindowInterface* extra_browser = nullptr;
  base::MockCallback<base::RepeatingCallback<void()>> mock_callback;
  // Local scope to isolate OnBrowserAdded and avoid capturing OnBrowserRemoved.
  {
    TabStripInternalsObserver observer(browser()->GetProfile(),
                                       mock_callback.Get());
    EXPECT_CALL(mock_callback, Run()).Times(AtLeast(1));

    extra_browser = CreateBrowser(browser()->GetProfile());
  }
  ASSERT_TRUE(extra_browser);
  CloseBrowserSynchronously(extra_browser);
}

// BrowserRemoved: Observe when a browser is removed from the BrowserList.
IN_PROC_BROWSER_TEST_F(TabStripInternalsObserverBrowserTest, BrowserRemoved) {
  BrowserWindowInterface* extra_browser =
      CreateBrowser(browser()->GetProfile());
  ASSERT_TRUE(extra_browser);
  base::MockCallback<base::RepeatingCallback<void()>> mock_callback;
  // Local scope to isolate OnBrowserRemoved triggered due to removal of
  // `extra_browser`. Prevents capturing OnBrowserRemoved for the main test
  // browser.
  {
    TabStripInternalsObserver observer(browser()->GetProfile(),
                                       mock_callback.Get());
    EXPECT_CALL(mock_callback, Run()).Times(AtLeast(1));

    CloseBrowserSynchronously(extra_browser);
  }
}

// TabStripModelChanged: Observe when a tab is added.
IN_PROC_BROWSER_TEST_F(TabStripInternalsObserverBrowserTest,
                       TabStripModelChanged) {
  ASSERT_TRUE(
      AddTabAtIndex(0, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
  base::MockCallback<base::RepeatingCallback<void()>> mock_callback;
  {
    TabStripInternalsObserver observer(browser()->GetProfile(),
                                       mock_callback.Get());
    EXPECT_CALL(mock_callback, Run()).Times(AtLeast(1));

    ASSERT_TRUE(
        AddTabAtIndex(1, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
  }
}

// TabGroupChanged: Observe when a tab group is created i.e. trigger
// OnTabGroupChanged with kCreated.
IN_PROC_BROWSER_TEST_F(TabStripInternalsObserverBrowserTest,
                       TabGroupChanged_GroupCreated) {
  ASSERT_TRUE(
      AddTabAtIndex(0, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
  ASSERT_TRUE(
      AddTabAtIndex(1, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));

  TabStripModel* model = browser()->tab_strip_model();
  base::MockCallback<base::RepeatingCallback<void()>> mock_callback;
  {
    TabStripInternalsObserver observer(browser()->GetProfile(),
                                       mock_callback.Get());
    EXPECT_CALL(mock_callback, Run()).Times(4);

    model->AddToNewGroup({0, 1});
  }
}

// TabGroupChanged: Observe when a tab group is closed i.e. trigger
// OnTabGroupChanged with kClosed.
IN_PROC_BROWSER_TEST_F(TabStripInternalsObserverBrowserTest,
                       TabGroupChanged_GroupClosed) {
  ASSERT_TRUE(
      AddTabAtIndex(0, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
  ASSERT_TRUE(
      AddTabAtIndex(1, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));

  TabStripModel* model = browser()->tab_strip_model();
  tab_groups::TabGroupId group_id = model->AddToNewGroup({0, 1});
  ASSERT_TRUE(model->group_model()->ContainsTabGroup(group_id));
  base::MockCallback<base::RepeatingCallback<void()>> mock_callback;
  {
    TabStripInternalsObserver observer(browser()->GetProfile(),
                                       mock_callback.Get());
    EXPECT_CALL(mock_callback, Run()).Times(AtLeast(4));

    model->CloseAllTabsInGroup(group_id);
  }
  EXPECT_FALSE(model->group_model()->ContainsTabGroup(group_id));
}

// TabGroupChanged: Observe when a tab group's collapsed state changes,
// triggering OnTabGroupChanged(TabGroupChange::kVisualsChanged).
IN_PROC_BROWSER_TEST_F(TabStripInternalsObserverBrowserTest,
                       TabGroupChanged_CollapsedStateChanged) {
  ASSERT_TRUE(
      AddTabAtIndex(0, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
  ASSERT_TRUE(
      AddTabAtIndex(1, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));

  TabStripModel* model = browser()->tab_strip_model();
  tab_groups::TabGroupId group_id = model->AddToNewGroup({0, 1});
  ASSERT_TRUE(model->group_model()->ContainsTabGroup(group_id));
  const auto* group = model->group_model()->GetTabGroup(group_id);
  ASSERT_TRUE(group);
  const tab_groups::TabGroupVisualData* old_visuals = group->visual_data();
  bool old_collapsed = old_visuals->is_collapsed();
  auto old_title = old_visuals->title();
  auto old_color = old_visuals->color();
  tab_groups::TabGroupVisualData new_visuals(
      old_visuals->title(), old_visuals->color(), !old_visuals->is_collapsed());
  base::MockCallback<base::RepeatingCallback<void()>> mock_callback;
  {
    TabStripInternalsObserver observer(browser()->GetProfile(),
                                       mock_callback.Get());
    EXPECT_CALL(mock_callback, Run()).Times(1);

    model->ChangeTabGroupVisuals(group_id, new_visuals);
  }
  const tab_groups::TabGroupVisualData* updated_visuals =
      model->group_model()->GetTabGroup(group_id)->visual_data();
  EXPECT_NE(updated_visuals->is_collapsed(), old_collapsed);
  EXPECT_EQ(updated_visuals->title(), old_title);
  EXPECT_EQ(updated_visuals->color(), old_color);
}

// TODO(crbug.com/454725279): This and SplitRemoved are failing. Re-enable these
// tests.
// SplitTabChanged: Observe when a split tab is added, triggering
// OnSplitTabChanged(SplitTabChange::kAdded).
IN_PROC_BROWSER_TEST_F(TabStripInternalsObserverBrowserTest,
                       DISABLED_SplitTabChanged_SplitAdded) {
  ASSERT_TRUE(
      AddTabAtIndex(0, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
  ASSERT_TRUE(
      AddTabAtIndex(1, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));

  TabStripModel* model = browser()->tab_strip_model();
  ASSERT_EQ(3, model->count());
  base::MockCallback<base::RepeatingCallback<void()>> mock_callback;
  {
    TabStripInternalsObserver observer(browser()->GetProfile(),
                                       mock_callback.Get());
    EXPECT_CALL(mock_callback, Run()).Times(2);

    split_tabs::SplitTabId split_id =
        model->AddToNewSplit({0}, split_tabs::SplitTabVisualData(),
                             split_tabs::SplitTabCreatedSource::kToolbarButton);
    EXPECT_TRUE(model->ContainsSplit(split_id));
  }
  ASSERT_TRUE(model->GetTabAtIndex(0)->GetSplit().has_value());
  ASSERT_TRUE(model->GetTabAtIndex(1)->GetSplit().has_value());
  EXPECT_EQ(model->GetTabAtIndex(0)->GetSplit().value(),
            model->GetTabAtIndex(1)->GetSplit().value());
}

// SplitTabChanged: Observe when a split tab is removed, triggering
// OnSplitTabChanged(SplitTabChange::kRemoved).
IN_PROC_BROWSER_TEST_F(TabStripInternalsObserverBrowserTest,
                       DISABLED_SplitTabChanged_SplitRemoved) {
  ASSERT_TRUE(
      AddTabAtIndex(0, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
  ASSERT_TRUE(
      AddTabAtIndex(1, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));

  TabStripModel* model = browser()->tab_strip_model();
  split_tabs::SplitTabId split_id =
      model->AddToNewSplit({0}, split_tabs::SplitTabVisualData(),
                           split_tabs::SplitTabCreatedSource::kToolbarButton);
  ASSERT_TRUE(model->ContainsSplit(split_id));
  ASSERT_TRUE(model->GetTabAtIndex(0)->GetSplit().has_value());
  ASSERT_TRUE(model->GetTabAtIndex(1)->GetSplit().has_value());
  EXPECT_EQ(model->GetTabAtIndex(0)->GetSplit().value(),
            model->GetTabAtIndex(1)->GetSplit().value());
  base::MockCallback<base::RepeatingCallback<void()>> mock_callback;
  {
    TabStripInternalsObserver observer(browser()->GetProfile(),
                                       mock_callback.Get());
    EXPECT_CALL(mock_callback, Run()).Times(2);

    model->RemoveSplit(split_id);
  }
  EXPECT_FALSE(model->ContainsSplit(split_id));
  EXPECT_FALSE(model->GetTabAtIndex(0)->GetSplit().has_value());
  EXPECT_FALSE(model->GetTabAtIndex(1)->GetSplit().has_value());
}

// TabChangedAt: Observe when a tab's content changes.
IN_PROC_BROWSER_TEST_F(TabStripInternalsObserverBrowserTest, TabChangedAt) {
  ASSERT_TRUE(
      AddTabAtIndex(0, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
  base::MockCallback<base::RepeatingCallback<void()>> mock_callback;
  {
    TabStripInternalsObserver observer(browser()->GetProfile(),
                                       mock_callback.Get());
    EXPECT_CALL(mock_callback, Run()).Times(AtLeast(1));

    ASSERT_TRUE(
        ui_test_utils::NavigateToURL(browser(), GURL("chrome://newtab/")));
  }
}

// TabPinnedStateChanged: Observe when a tab's pinned state changes.
IN_PROC_BROWSER_TEST_F(TabStripInternalsObserverBrowserTest,
                       TabPinnedStateChanged) {
  ASSERT_TRUE(
      AddTabAtIndex(0, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
  base::MockCallback<base::RepeatingCallback<void()>> mock_callback;
  {
    TabStripInternalsObserver observer(browser()->GetProfile(),
                                       mock_callback.Get());
    EXPECT_CALL(mock_callback, Run()).Times(1);

    browser()->tab_strip_model()->SetTabPinned(0, true);
  }
}

// TabBlockedStateChanged: Observe when a tab's blocked state changes.
IN_PROC_BROWSER_TEST_F(TabStripInternalsObserverBrowserTest,
                       TabBlockedStateChanged) {
  ASSERT_TRUE(
      AddTabAtIndex(0, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
  base::MockCallback<base::RepeatingCallback<void()>> mock_callback;
  {
    TabStripInternalsObserver observer(browser()->GetProfile(),
                                       mock_callback.Get());
    EXPECT_CALL(mock_callback, Run()).Times(1);

    browser()->tab_strip_model()->SetTabBlocked(0, true);
  }
}

// TabGroupedStateChanged: Observe when a tab is added to an existing group.
IN_PROC_BROWSER_TEST_F(TabStripInternalsObserverBrowserTest,
                       TabGroupedStateChanged_TabAddedToGroup) {
  ASSERT_TRUE(
      AddTabAtIndex(0, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
  ASSERT_TRUE(
      AddTabAtIndex(1, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));

  TabStripModel* model = browser()->tab_strip_model();
  tab_groups::TabGroupId group_id = model->AddToNewGroup({0});
  ASSERT_TRUE(model->group_model()->ContainsTabGroup(group_id));
  base::MockCallback<base::RepeatingCallback<void()>> mock_callback;
  {
    TabStripInternalsObserver observer(browser()->GetProfile(),
                                       mock_callback.Get());
    EXPECT_CALL(mock_callback, Run()).Times(1);

    model->AddToExistingGroup({1}, group_id);
  }
  EXPECT_EQ(model->GetTabGroupForTab(1), group_id);
}

// TabGroupedStateChanged: Observe when a tab is being removed from a group.
IN_PROC_BROWSER_TEST_F(TabStripInternalsObserverBrowserTest,
                       TabGroupedStateChanged_TabRemovedFromGroup) {
  ASSERT_TRUE(
      AddTabAtIndex(0, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
  ASSERT_TRUE(
      AddTabAtIndex(1, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));

  TabStripModel* model = browser()->tab_strip_model();
  tab_groups::TabGroupId group_id = model->AddToNewGroup({0, 1});
  ASSERT_TRUE(model->group_model()->ContainsTabGroup(group_id));
  base::MockCallback<base::RepeatingCallback<void()>> mock_callback;
  {
    TabStripInternalsObserver observer(browser()->GetProfile(),
                                       mock_callback.Get());
    EXPECT_CALL(mock_callback, Run()).Times(1);

    model->RemoveFromGroup({1});
  }
  EXPECT_FALSE(model->GetTabGroupForTab(1).has_value());
}

// TabGroupedStateChanged: Observe when a tab moves from one group to another.
IN_PROC_BROWSER_TEST_F(TabStripInternalsObserverBrowserTest,
                       TabGroupedStateChanged_TabMovedBetweenGroups) {
  ASSERT_TRUE(
      AddTabAtIndex(0, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
  ASSERT_TRUE(
      AddTabAtIndex(1, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
  ASSERT_TRUE(
      AddTabAtIndex(2, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
  ASSERT_TRUE(
      AddTabAtIndex(3, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));

  TabStripModel* model = browser()->tab_strip_model();
  tab_groups::TabGroupId group_a = model->AddToNewGroup({0, 1});
  tab_groups::TabGroupId group_b = model->AddToNewGroup({2, 3});
  ASSERT_TRUE(model->group_model()->ContainsTabGroup(group_a));
  ASSERT_TRUE(model->group_model()->ContainsTabGroup(group_b));
  base::MockCallback<base::RepeatingCallback<void()>> mock_callback;
  {
    TabStripInternalsObserver observer(browser()->GetProfile(),
                                       mock_callback.Get());
    EXPECT_CALL(mock_callback, Run()).Times(2);

    // Move tab at index 1 from group A to group B.
    model->RemoveFromGroup({1});
    model->AddToExistingGroup({1}, group_b);
  }
  EXPECT_EQ(model->GetTabGroupForTab(1), group_b);
}

// TabRestoreServiceChanged: Observe when a tab moves to TabRestoreService.
IN_PROC_BROWSER_TEST_F(TabStripInternalsObserverBrowserTest,
                       TabRestoreServiceChanged_TabAdded) {
  base::MockCallback<base::RepeatingCallback<void()>> mock_callback;
  ASSERT_TRUE(
      AddTabAtIndex(0, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
  {
    TabStripInternalsObserver observer(browser()->GetProfile(),
                                       mock_callback.Get());
    EXPECT_CALL(mock_callback, Run()).Times(AtLeast(1));

    // Close an existing tab.
    browser()->tab_strip_model()->CloseWebContentsAt(
        1, TabCloseTypes::CLOSE_USER_GESTURE);
  }
}

// TabRestoreServiceDestroyed: Callback should not be invoked when service is
// destroyed.
IN_PROC_BROWSER_TEST_F(TabStripInternalsObserverBrowserTest,
                       TabRestoreServiceDestroyed_NoCallback) {
  base::MockCallback<base::RepeatingCallback<void()>> mock_callback;
  {
    TabStripInternalsObserver observer(browser()->GetProfile(),
                                       mock_callback.Get());
    EXPECT_CALL(mock_callback, Run()).Times(0);

    sessions::TabRestoreService* service =
        TabRestoreServiceFactory::GetForProfile(browser()->GetProfile());
    ASSERT_TRUE(service);
    // Invoke for code coverage.
    observer.TabRestoreServiceDestroyed(service);
  }
}

// TabRestoreService_OTR_Profile: Callback should not be invoked for an OTR
// profile browser by the TabRestoreService.
IN_PROC_BROWSER_TEST_F(TabStripInternalsObserverBrowserTest,
                       TabRestoreService_OTR_Profile_NoCallback) {
  BrowserWindowInterface* otr_browser =
      CreateIncognitoBrowser(browser()->GetProfile());
  ASSERT_TRUE(otr_browser);
  Profile* otr_profile = otr_browser->GetProfile();
  ASSERT_TRUE(otr_profile->IsOffTheRecord());
  base::MockCallback<base::RepeatingCallback<void()>> mock_callback;
  {
    TabStripInternalsObserver observer(otr_profile, mock_callback.Get());
    // Use the expected callback count triggered by the TabStripModel and
    // subsequently the browser-close notification to confirm that the
    // TabRestoreService did not trigger any additional callbacks.
    EXPECT_CALL(mock_callback, Run()).Times(2);

    // Close an incognito browser tab.
    otr_browser->GetTabStripModel()->CloseWebContentsAt(
        0, TabCloseTypes::CLOSE_USER_GESTURE);
  }
  CloseBrowserSynchronously(otr_browser);
}

// OnGotSession: Browser Test verifies that callback is fired and restored
// session windows are returned by `GetRestoredSession()`.
IN_PROC_BROWSER_TEST_F(
    TabStripInternalsObserverBrowserTest,
    SessionRestore_OnGotSession_CachesSessionWindowsAndFiresCallback) {
  ASSERT_TRUE(
      AddTabAtIndex(0, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
  ASSERT_TRUE(
      AddTabAtIndex(1, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
  base::MockCallback<base::RepeatingCallback<void()>> mock_callback;
  {
    TabStripInternalsObserver observer(browser()->GetProfile(),
                                       mock_callback.Get());
    EXPECT_CALL(mock_callback, Run()).Times(AtLeast(1));
    EXPECT_TRUE(observer.GetRestoredSession().empty());

    // Trigger session restore.
    BrowserWindowInterface* restored_window = QuitBrowserAndRestore(browser());

    ASSERT_TRUE(restored_window);
    TabStripModel* tab_strip = restored_window->GetTabStripModel();
    ASSERT_TRUE(tab_strip);
    // One additional tab gets added to the tabstrip by default on restore.
    ASSERT_EQ(tab_strip->count(), 2 + 1);
    const auto& restored = observer.GetRestoredSession();
    ASSERT_EQ(restored.size(), 1u);
    const sessions::SessionWindow* window = restored[0].get();
    ASSERT_TRUE(window);
    ASSERT_EQ(window->tabs.size(), 3u);
    // Selected tab index should point to the last added tab.
    EXPECT_EQ(window->selected_tab_index, 1);
  }
}

// OnGotSession: Unit style test verifies that callback is fired exactly once.
IN_PROC_BROWSER_TEST_F(
    TabStripInternalsObserverBrowserTest,
    SessionRestore_OnGotSession_UnitTest_CachesSessionWindowsAndFiresCallback) {
  base::MockCallback<base::RepeatingCallback<void()>> mock_callback;
  {
    TabStripInternalsObserver observer(browser()->GetProfile(),
                                       mock_callback.Get());
    // Unit style test has deterministic number of callback invocations.
    EXPECT_CALL(mock_callback, Run()).Times(1);
    EXPECT_TRUE(observer.GetRestoredSession().empty());

    // Build session windows to be restored.
    auto window = std::make_unique<sessions::SessionWindow>();
    window->window_id = SessionID::FromSerializedValue(1);
    auto window2 = std::make_unique<sessions::SessionWindow>();
    window2->window_id = SessionID::FromSerializedValue(2);
    auto window3 = std::make_unique<sessions::SessionWindow>();
    window3->window_id = SessionID::FromSerializedValue(3);

    std::vector<const sessions::SessionWindow*> windows;
    windows.push_back(window.get());
    windows.push_back(window2.get());
    windows.push_back(window3.get());

    observer.OnGotSession(browser()->GetProfile(),
                          /*for_app=*/false, windows);

    const auto& restored = observer.GetRestoredSession();
    ASSERT_EQ(restored.size(), windows.size());
    EXPECT_EQ(restored[0]->window_id.id(), 1);
    EXPECT_EQ(restored[1]->window_id.id(), 2);
    EXPECT_EQ(restored[2]->window_id.id(), 3);
  }
}
