| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "ios/chrome/browser/ui/util/util_swift.h" |
| |
| #import <UIKit/UIKit.h> |
| |
| #import "testing/gtest/include/gtest/gtest.h" |
| #import "testing/platform_test.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| // Sets up a view and a layout guide. |
| class FrameLayoutGuideTest : public PlatformTest { |
| protected: |
| FrameLayoutGuideTest() |
| : window_([[UIWindow alloc] init]), |
| view_([[UIView alloc] init]), |
| frame_layout_guide_([[FrameLayoutGuide alloc] init]) {} |
| |
| UIWindow* window_; |
| UIView* view_; |
| FrameLayoutGuide* frame_layout_guide_; |
| }; |
| |
| // Checks that the callback is called when the owning view is added to the |
| // window. |
| TEST_F(FrameLayoutGuideTest, Set) { |
| [view_ addLayoutGuide:frame_layout_guide_]; |
| __block BOOL callback_called = NO; |
| UILayoutGuide* retained_layout_guide = frame_layout_guide_; |
| frame_layout_guide_.onDidMoveToWindow = ^(UILayoutGuide* layout_guide) { |
| callback_called = YES; |
| EXPECT_EQ(layout_guide, retained_layout_guide); |
| }; |
| |
| [window_ addSubview:view_]; |
| |
| EXPECT_TRUE(callback_called); |
| } |
| |
| // Checks that the callback is not called when the owning view is added to the |
| // window and `onDidMoveToWindow` is reset to `nil`. |
| TEST_F(FrameLayoutGuideTest, Reset) { |
| [view_ addLayoutGuide:frame_layout_guide_]; |
| frame_layout_guide_.onDidMoveToWindow = ^(UILayoutGuide* layout_guide) { |
| FAIL() << "Callback should not have been called after being unset."; |
| }; |
| frame_layout_guide_.onDidMoveToWindow = nil; |
| |
| [window_ addSubview:view_]; |
| } |
| |
| // Checks that removing the owning view from its window calls the callback. |
| TEST_F(FrameLayoutGuideTest, RemoveFromWindow) { |
| [view_ addLayoutGuide:frame_layout_guide_]; |
| [window_ addSubview:view_]; |
| __block BOOL callback_called = NO; |
| frame_layout_guide_.onDidMoveToWindow = ^(UILayoutGuide* layout_guide) { |
| callback_called = YES; |
| }; |
| |
| [view_ removeFromSuperview]; |
| |
| EXPECT_EQ(view_.window, nil); |
| EXPECT_TRUE(callback_called); |
| } |
| |
| // Checks that removing the layout guide from its owning view doesn't call the |
| // callback once the view's window changes. |
| TEST_F(FrameLayoutGuideTest, RemoveFromView) { |
| [view_ addLayoutGuide:frame_layout_guide_]; |
| frame_layout_guide_.onDidMoveToWindow = ^(UILayoutGuide* layout_guide) { |
| FAIL() << "Callback should not have been called after the layout guide was " |
| "removed from the non-windowed view."; |
| }; |
| [view_ removeLayoutGuide:frame_layout_guide_]; |
| |
| [window_ addSubview:view_]; |
| } |
| |
| // Checks that the callback is called only on the current owning view, and not |
| // on prior owning views. |
| TEST_F(FrameLayoutGuideTest, ChangeView) { |
| [view_ addLayoutGuide:frame_layout_guide_]; |
| UIView* other_view = [[UIView alloc] init]; |
| [other_view addLayoutGuide:frame_layout_guide_]; |
| __block BOOL callback_called = NO; |
| frame_layout_guide_.onDidMoveToWindow = ^(UILayoutGuide* layout_guide) { |
| callback_called = YES; |
| }; |
| |
| // Check that adding the initial view to the window doesn't call the callback. |
| [window_ addSubview:view_]; |
| EXPECT_FALSE(callback_called); |
| |
| // Check that adding the other view calls the callback, i.e. the observation |
| // was transferred from view_ to other_view. |
| [window_ addSubview:other_view]; |
| EXPECT_TRUE(callback_called); |
| } |
| |
| // Checks that passing the layout guide from one non-windowed view to another |
| // doesn't call the callback (since both have `window` nil). |
| TEST_F(FrameLayoutGuideTest, NotCalledOnReparentNonWindowedViews) { |
| [view_ addLayoutGuide:frame_layout_guide_]; |
| UIView* other_view = [[UIView alloc] init]; |
| frame_layout_guide_.onDidMoveToWindow = ^(UILayoutGuide* layout_guide) { |
| FAIL() << "Callback should not have been called while being moved between " |
| "non-windowed views."; |
| }; |
| |
| [other_view addLayoutGuide:frame_layout_guide_]; |
| } |
| |
| // Checks that passing the layout guide from one view to another in the same |
| // window doesn't call the callback. |
| TEST_F(FrameLayoutGuideTest, NotCalledOnReparentViewsInSameWindow) { |
| [view_ addLayoutGuide:frame_layout_guide_]; |
| UIView* other_view = [[UIView alloc] init]; |
| [window_ addSubview:view_]; |
| [window_ addSubview:other_view]; |
| frame_layout_guide_.onDidMoveToWindow = ^(UILayoutGuide* layout_guide) { |
| FAIL() << "Callback should not have been called while being moved between " |
| "views in the same window."; |
| }; |
| |
| [other_view addLayoutGuide:frame_layout_guide_]; |
| } |
| |
| // Checks that setting the constrained frame while the guide is not added to a |
| // view doesn't change the layout frame. |
| TEST_F(FrameLayoutGuideTest, NoOwningViewZeroLayoutFrame) { |
| CGRect rect = CGRectMake(10, 20, 30, 40); |
| |
| frame_layout_guide_.constrainedFrame = rect; |
| |
| EXPECT_TRUE(CGRectEqualToRect(frame_layout_guide_.layoutFrame, CGRectZero)); |
| EXPECT_TRUE(CGRectEqualToRect(frame_layout_guide_.constrainedFrame, rect)); |
| } |
| |
| // Checks that adding the guide to a guide with a constrained frame updates the |
| // layout frame accordingly. |
| TEST_F(FrameLayoutGuideTest, OwningViewSetsLayoutFrame) { |
| CGRect rect = CGRectMake(10, 20, 30, 40); |
| frame_layout_guide_.constrainedFrame = rect; |
| |
| [view_ addLayoutGuide:frame_layout_guide_]; |
| [view_ setNeedsLayout]; |
| [view_ layoutIfNeeded]; |
| |
| EXPECT_TRUE(CGRectEqualToRect(frame_layout_guide_.layoutFrame, rect)); |
| EXPECT_TRUE(CGRectEqualToRect(frame_layout_guide_.constrainedFrame, rect)); |
| } |
| |
| // Checks that removing the guide from its owning view resets the layout frame |
| // but keeps the constrained frame. |
| TEST_F(FrameLayoutGuideTest, |
| ResetOwningViewResetsLayoutFrameKeepsConstrainedFrame) { |
| CGRect rect = CGRectMake(10, 20, 30, 40); |
| frame_layout_guide_.constrainedFrame = rect; |
| [view_ addLayoutGuide:frame_layout_guide_]; |
| [view_ setNeedsLayout]; |
| [view_ layoutIfNeeded]; |
| |
| [view_ removeLayoutGuide:frame_layout_guide_]; |
| |
| EXPECT_TRUE(CGRectEqualToRect(frame_layout_guide_.layoutFrame, CGRectZero)); |
| EXPECT_TRUE(CGRectEqualToRect(frame_layout_guide_.constrainedFrame, rect)); |
| } |