chromium/ui/base/test/view_tree_validator_unittest.mm

// Copyright 2017 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/base/test/view_tree_validator.h"

#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/test/cocoa_helper.h"

using ViewTreeValidatorTest = ui::CocoaTest;

namespace {

NSView* ButtonWithFrame(int x, int y, int w, int h) {
  return [[NSButton alloc] initWithFrame:NSMakeRect(x, y, w, h)];
}

NSView* ViewWithFrame(int x, int y, int w, int h) {
  return [[NSView alloc] initWithFrame:NSMakeRect(x, y, w, h)];
}

void AdjustWidth(NSView* view, int delta) {
  NSRect r = view.frame;
  r.size.width += delta;
  view.frame = r;
}

void AdjustHeight(NSView* view, int delta) {
  NSRect r = view.frame;
  r.size.height += delta;
  view.frame = r;
}

}  // namespace

TEST_F(ViewTreeValidatorTest, CorrectnessTest) {
  NSWindow* window = test_window();
  int width = NSWidth(window.contentView.frame);
  int height = NSHeight(window.contentView.frame);

  // A diagram. Views 3–5 share edges with their parents, and views 4 and 5
  // share an overlapping edge, but in this diagram, the edges are drawn with
  // insets to make it clear which views are subviews of other views.
  // ┌────────────┬────────────┐
  // │            │ ┌────────┐ │
  // │            │ │ view_5 │ │
  // │            │ └────────┘ │
  // │   view_1   |   view_2   │
  // │ ┌────────┐ │ ┌────────┐ │
  // │ │ view_3 │ │ │ view_4 │ │
  // │ └────────┘ │ └────────┘ │
  // └────────────┴────────────┘

  NSView* view_1 = ViewWithFrame(0, 0, width / 2, height);
  NSView* view_2 = ButtonWithFrame(width / 2, 0, width / 2, height);
  NSView* view_3 = ButtonWithFrame(0, 0, width / 2, height / 2);
  NSView* view_4 = ViewWithFrame(0, 0, width / 2, height / 2);
  NSView* view_5 = ViewWithFrame(0, height / 2, width / 2, height / 2);

  [view_2 addSubview:view_4];
  [view_2 addSubview:view_5];
  [view_1 addSubview:view_3];
  [window.contentView addSubview:view_1];
  [window.contentView addSubview:view_2];

  {
    // The original layout is well-formed.
    std::optional<ui::ViewTreeProblemDetails> details =
        ui::ValidateViewTree(window.contentView);
    EXPECT_FALSE(details.has_value());
  }

  {
    // Make view_3 no longer contained within view_1.
    AdjustWidth(view_3, 1);
    std::optional<ui::ViewTreeProblemDetails> details =
        ui::ValidateViewTree(window.contentView);
    ASSERT_TRUE(details.has_value());
    EXPECT_EQ(details->type,
              ui::ViewTreeProblemDetails::ProblemType::kViewOutsideParent);
    EXPECT_EQ(details->view_a, view_3);
    EXPECT_EQ(details->view_b, view_1);
    AdjustWidth(view_3, -1);
  }

  {
    // Make view_1 overlap view_2.
    AdjustWidth(view_1, 1);
    std::optional<ui::ViewTreeProblemDetails> details =
        ui::ValidateViewTree(window.contentView);
    ASSERT_TRUE(details.has_value());
    EXPECT_EQ(details->type,
              ui::ViewTreeProblemDetails::ProblemType::kViewsOverlap);

    // Since there's no specified order for |view_a| and |view_b| for
    // kViewsOverlap, check that |view_1| and |view_2| both appear exactly
    // once in |view_a| and |view_b|.
    EXPECT_TRUE(details->view_a == view_1 || details->view_a == view_2);
    EXPECT_TRUE(details->view_b == view_1 || details->view_b == view_2);
    EXPECT_NE(details->view_a, details->view_b);
    AdjustWidth(view_1, -1);
  }

  {
    // Make view_4 overlap view_5. Since they're both not localizable, this
    // isn't an error.
    AdjustHeight(view_4, 1);
    std::optional<ui::ViewTreeProblemDetails> details =
        ui::ValidateViewTree(window.contentView);
    EXPECT_FALSE(details.has_value());
    AdjustHeight(view_4, -1);
  }
}