// 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 "ui/views/accessibility/tree/widget_view_ax_cache.h"

#include <memory>
#include <utility>

#include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/views/accessibility/ax_virtual_view.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/view.h"

namespace views::test {

class WidgetViewAXCacheTest : public testing::Test {
 protected:
  WidgetViewAXCacheTest() = default;
  ~WidgetViewAXCacheTest() override = default;

  WidgetViewAXCache& cache() { return cache_; }

 private:
  WidgetViewAXCache cache_;
};

TEST_F(WidgetViewAXCacheTest, CacheInsertGetRemove) {
  auto v = std::make_unique<View>();

  EXPECT_EQ(cache().Get(v->GetViewAccessibility().GetUniqueId()), nullptr);

  cache().Insert(&v->GetViewAccessibility());
  EXPECT_EQ(cache().Get(v->GetViewAccessibility().GetUniqueId()),
            &v->GetViewAccessibility());

  cache().Remove(v->GetViewAccessibility().GetUniqueId());
  EXPECT_EQ(cache().Get(v->GetViewAccessibility().GetUniqueId()), nullptr);
  EXPECT_FALSE(cache().HasCachedChildren(&v->GetViewAccessibility()));
}

TEST_F(WidgetViewAXCacheTest, ChildSnapshotIsStableUntilCleared) {
  auto parent = std::make_unique<View>();
  auto* a = parent->AddChildView(std::make_unique<View>());
  auto* b = parent->AddChildView(std::make_unique<View>());

  // Take snapshot.
  cache().CacheChildrenIfNeeded(&parent->GetViewAccessibility());

  // Snapshot sees exactly [a, b].
  EXPECT_TRUE(cache().HasCachedChildren(&parent->GetViewAccessibility()));
  EXPECT_EQ(cache().CachedChildCount(&parent->GetViewAccessibility()), 2u);
  EXPECT_EQ(cache().CachedChildAt(&parent->GetViewAccessibility(), 0),
            &a->GetViewAccessibility());
  EXPECT_EQ(cache().CachedChildAt(&parent->GetViewAccessibility(), 1),
            &b->GetViewAccessibility());
  EXPECT_EQ(cache().CachedChildAt(&parent->GetViewAccessibility(), 2), nullptr);

  // Mutate the live view hierarchy after the snapshot, which shouldn't update
  // the snapshot.
  auto removed_b = parent->RemoveChildViewT(b);
  auto* c = parent->AddChildView(std::make_unique<View>());

  // Snapshot must not change until cleared.
  EXPECT_EQ(cache().CachedChildCount(&parent->GetViewAccessibility()), 2u);
  EXPECT_EQ(cache().CachedChildAt(&parent->GetViewAccessibility(), 0),
            &a->GetViewAccessibility());
  EXPECT_EQ(cache().CachedChildAt(&parent->GetViewAccessibility(), 1),
            &removed_b->GetViewAccessibility());
  EXPECT_EQ(cache().CachedChildAt(&parent->GetViewAccessibility(), 2), nullptr);

  // Clear and re-snapshot and validate the current live children are [a, c].
  cache().RemoveFromChildCache(&parent->GetViewAccessibility());
  EXPECT_FALSE(cache().HasCachedChildren(&parent->GetViewAccessibility()));
  cache().CacheChildrenIfNeeded(&parent->GetViewAccessibility());
  EXPECT_EQ(cache().CachedChildCount(&parent->GetViewAccessibility()), 2u);
  EXPECT_EQ(cache().CachedChildAt(&parent->GetViewAccessibility(), 0),
            &a->GetViewAccessibility());
  EXPECT_EQ(cache().CachedChildAt(&parent->GetViewAccessibility(), 1),
            &c->GetViewAccessibility());

  // Clear the cache again and validate that no children are cached.
  cache().RemoveFromChildCache(&parent->GetViewAccessibility());
  EXPECT_FALSE(cache().HasCachedChildren(&parent->GetViewAccessibility()));
  EXPECT_EQ(cache().CachedChildCount(&parent->GetViewAccessibility()), 0u);
  EXPECT_EQ(cache().CachedChildAt(&parent->GetViewAccessibility(), 0), nullptr);
  EXPECT_EQ(cache().CachedChildAt(&parent->GetViewAccessibility(), 1), nullptr);
  EXPECT_EQ(cache().CachedChildAt(&parent->GetViewAccessibility(), 2), nullptr);
}

TEST_F(WidgetViewAXCacheTest, Init_RootOnly) {
  auto parent = std::make_unique<View>();
  auto* a = parent->AddChildView(std::make_unique<View>());
  auto* b = parent->AddChildView(std::make_unique<View>());
  auto* b1 = b->AddChildView(std::make_unique<View>());
  auto* b2 = b->AddChildView(std::make_unique<View>());
  auto excluded = std::make_unique<View>();

  cache().Init(parent->GetViewAccessibility(), false /* full_tree */);
  EXPECT_EQ(cache().Get(parent->GetViewAccessibility().GetUniqueId()),
            &parent->GetViewAccessibility());
  EXPECT_EQ(cache().Get(a->GetViewAccessibility().GetUniqueId()), nullptr);
  EXPECT_EQ(cache().Get(b->GetViewAccessibility().GetUniqueId()), nullptr);
  EXPECT_EQ(cache().Get(b1->GetViewAccessibility().GetUniqueId()), nullptr);
  EXPECT_EQ(cache().Get(b2->GetViewAccessibility().GetUniqueId()), nullptr);
  EXPECT_EQ(cache().Get(excluded->GetViewAccessibility().GetUniqueId()),
            nullptr);
}

TEST_F(WidgetViewAXCacheTest, Init_FullTree) {
  auto parent = std::make_unique<View>();
  auto* a = parent->AddChildView(std::make_unique<View>());
  auto* b = parent->AddChildView(std::make_unique<View>());
  auto* b1 = b->AddChildView(std::make_unique<View>());
  auto* b2 = b->AddChildView(std::make_unique<View>());
  auto excluded = std::make_unique<View>();

  cache().Init(parent->GetViewAccessibility());
  EXPECT_EQ(cache().Get(parent->GetViewAccessibility().GetUniqueId()),
            &parent->GetViewAccessibility());
  EXPECT_EQ(cache().Get(a->GetViewAccessibility().GetUniqueId()),
            &a->GetViewAccessibility());
  EXPECT_EQ(cache().Get(b->GetViewAccessibility().GetUniqueId()),
            &b->GetViewAccessibility());
  EXPECT_EQ(cache().Get(b1->GetViewAccessibility().GetUniqueId()),
            &b1->GetViewAccessibility());
  EXPECT_EQ(cache().Get(b2->GetViewAccessibility().GetUniqueId()),
            &b2->GetViewAccessibility());
  EXPECT_EQ(cache().Get(excluded->GetViewAccessibility().GetUniqueId()),
            nullptr);
}

TEST_F(WidgetViewAXCacheTest, Init_FirstRootOnlyThenFullTree) {
  auto parent = std::make_unique<View>();
  auto* a = parent->AddChildView(std::make_unique<View>());
  auto* b = parent->AddChildView(std::make_unique<View>());
  auto* b1 = b->AddChildView(std::make_unique<View>());
  auto* b2 = b->AddChildView(std::make_unique<View>());
  auto excluded = std::make_unique<View>();

  cache().Init(parent->GetViewAccessibility(), false /* full_tree */);
  EXPECT_EQ(cache().Get(parent->GetViewAccessibility().GetUniqueId()),
            &parent->GetViewAccessibility());
  EXPECT_EQ(cache().Get(a->GetViewAccessibility().GetUniqueId()), nullptr);
  EXPECT_EQ(cache().Get(b->GetViewAccessibility().GetUniqueId()), nullptr);
  EXPECT_EQ(cache().Get(b1->GetViewAccessibility().GetUniqueId()), nullptr);
  EXPECT_EQ(cache().Get(b2->GetViewAccessibility().GetUniqueId()), nullptr);
  EXPECT_EQ(cache().Get(excluded->GetViewAccessibility().GetUniqueId()),
            nullptr);

  cache().Init(parent->GetViewAccessibility());
  EXPECT_EQ(cache().Get(parent->GetViewAccessibility().GetUniqueId()),
            &parent->GetViewAccessibility());
  EXPECT_EQ(cache().Get(a->GetViewAccessibility().GetUniqueId()),
            &a->GetViewAccessibility());
  EXPECT_EQ(cache().Get(b->GetViewAccessibility().GetUniqueId()),
            &b->GetViewAccessibility());
  EXPECT_EQ(cache().Get(b1->GetViewAccessibility().GetUniqueId()),
            &b1->GetViewAccessibility());
  EXPECT_EQ(cache().Get(b2->GetViewAccessibility().GetUniqueId()),
            &b2->GetViewAccessibility());
  EXPECT_EQ(cache().Get(excluded->GetViewAccessibility().GetUniqueId()),
            nullptr);
}

TEST_F(WidgetViewAXCacheTest, CachedChildrenSnapshotIgnoresMutations) {
  auto parent = std::make_unique<View>();
  auto* a = parent->AddChildView(std::make_unique<View>());
  auto* b = parent->AddChildView(std::make_unique<View>());
  auto& pax = parent->GetViewAccessibility();

  cache().CacheChildrenIfNeeded(&pax);

  // Mutate the live hierarchy after the snapshot.
  parent->AddChildView(std::make_unique<View>());

  EXPECT_TRUE(cache().HasCachedChildren(&pax));
  EXPECT_EQ(cache().CachedChildCount(&pax), 2u);
  EXPECT_EQ(cache().CachedChildAt(&pax, 0), &a->GetViewAccessibility());
  EXPECT_EQ(cache().CachedChildAt(&pax, 1), &b->GetViewAccessibility());
  EXPECT_EQ(cache().CachedChildAt(&pax, 2), nullptr);
}

TEST_F(WidgetViewAXCacheTest, RemoveFromChildCache_ClearsSnapshot) {
  auto parent = std::make_unique<View>();
  auto* a = parent->AddChildView(std::make_unique<View>());
  auto* b = parent->AddChildView(std::make_unique<View>());
  auto& pax = parent->GetViewAccessibility();

  cache().Insert(&pax);
  cache().Insert(&a->GetViewAccessibility());
  cache().Insert(&b->GetViewAccessibility());

  cache().CacheChildrenIfNeeded(&pax);
  ASSERT_TRUE(cache().HasCachedChildren(&pax));
  ASSERT_EQ(cache().CachedChildCount(&pax), 2u);

  cache().RemoveFromChildCache(&pax);

  EXPECT_FALSE(cache().HasCachedChildren(&pax));
  EXPECT_EQ(cache().CachedChildCount(&pax), 0u);
  EXPECT_EQ(cache().CachedChildAt(&pax, 0), nullptr);

  EXPECT_EQ(cache().Get(a->GetViewAccessibility().GetUniqueId()),
            &a->GetViewAccessibility());
  EXPECT_EQ(cache().Get(b->GetViewAccessibility().GetUniqueId()),
            &b->GetViewAccessibility());
}

TEST_F(WidgetViewAXCacheTest, Init_IncludesNestedVirtualChildren) {
  auto parent = std::make_unique<View>();
  auto virtual_child = std::make_unique<AXVirtualView>();
  auto nested_virtual = std::make_unique<AXVirtualView>();

  auto virtual_child_id = virtual_child->ViewAccessibility::GetUniqueId();
  auto nested_virtual_id = nested_virtual->ViewAccessibility::GetUniqueId();

  virtual_child->AddChildView(std::move(nested_virtual));
  parent->GetViewAccessibility().AddVirtualChildView(std::move(virtual_child));

  cache().Init(parent->GetViewAccessibility());

  EXPECT_EQ(cache().Get(parent->GetViewAccessibility().GetUniqueId()),
            &parent->GetViewAccessibility());
  EXPECT_NE(cache().Get(virtual_child_id), nullptr);
  EXPECT_NE(cache().Get(nested_virtual_id), nullptr);
}

}  // namespace views::test
