Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
#import <OCMock/OCMock.h>
#import "flutter/testing/testing.h"

@interface FlutterTextField (Testing)
- (void)setPlatformNode:(flutter::FlutterTextPlatformNode*)node;
@end

@interface FlutterTextFieldMock : FlutterTextField

@property(nonatomic) NSString* lastUpdatedString;
Expand Down Expand Up @@ -1434,37 +1438,47 @@ - (bool)testSelectorsAreForwardedToFramework {
node_data.SetValue("initial text");
ax_node.SetData(node_data);
delegate.Init(engine.accessibilityBridge, &ax_node);
FlutterTextPlatformNode text_platform_node(&delegate, viewController);
{
FlutterTextPlatformNode text_platform_node(&delegate, viewController);

FlutterTextFieldMock* mockTextField =
[[FlutterTextFieldMock alloc] initWithPlatformNode:&text_platform_node
fieldEditor:viewController.textInputPlugin];
[viewController.view addSubview:mockTextField];
[mockTextField startEditing];

NSDictionary* arguments = @{
@"inputAction" : @"action",
@"inputType" : @{@"name" : @"inputName"},
};
FlutterMethodCall* methodCall =
[FlutterMethodCall methodCallWithMethodName:@"TextInput.setClient"
arguments:@[ @(1), arguments ]];
FlutterResult result = ^(id result) {
};
[viewController.textInputPlugin handleMethodCall:methodCall result:result];

arguments = @{
@"text" : @"new text",
@"selectionBase" : @(1),
@"selectionExtent" : @(2),
@"composingBase" : @(-1),
@"composingExtent" : @(-1),
};

FlutterTextFieldMock* mockTextField =
[[FlutterTextFieldMock alloc] initWithPlatformNode:&text_platform_node
fieldEditor:viewController.textInputPlugin];
[viewController.view addSubview:mockTextField];
[mockTextField startEditing];
methodCall = [FlutterMethodCall methodCallWithMethodName:@"TextInput.setEditingState"
arguments:arguments];
[viewController.textInputPlugin handleMethodCall:methodCall result:result];
EXPECT_EQ([mockTextField.lastUpdatedString isEqualToString:@"new text"], YES);
EXPECT_EQ(NSEqualRanges(mockTextField.lastUpdatedSelection, NSMakeRange(1, 1)), YES);

NSDictionary* arguments = @{
@"inputAction" : @"action",
@"inputType" : @{@"name" : @"inputName"},
};
FlutterMethodCall* methodCall = [FlutterMethodCall methodCallWithMethodName:@"TextInput.setClient"
arguments:@[ @(1), arguments ]];
FlutterResult result = ^(id result) {
};
[viewController.textInputPlugin handleMethodCall:methodCall result:result];

arguments = @{
@"text" : @"new text",
@"selectionBase" : @(1),
@"selectionExtent" : @(2),
@"composingBase" : @(-1),
@"composingExtent" : @(-1),
};
// This blocks the FlutterTextFieldMock, which is held onto by the main event
// loop, from crashing.
[mockTextField setPlatformNode:nil];
}

methodCall = [FlutterMethodCall methodCallWithMethodName:@"TextInput.setEditingState"
arguments:arguments];
[viewController.textInputPlugin handleMethodCall:methodCall result:result];
EXPECT_EQ([mockTextField.lastUpdatedString isEqualToString:@"new text"], YES);
EXPECT_EQ(NSEqualRanges(mockTextField.lastUpdatedSelection, NSMakeRange(1, 1)), YES);
// This verifies that clearing the platform node works.
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
}

TEST(FlutterTextInputPluginTest, CanNotBecomeResponderIfNoViewController) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,18 @@ - (void)updateString:(NSString*)string withSelection:(NSRange)selection {
#pragma mark - NSView

- (NSRect)frame {
if (!_node) {
return NSZeroRect;
}
return _node->GetFrame();
}

#pragma mark - NSAccessibilityProtocol

- (void)setAccessibilityFocused:(BOOL)isFocused {
if (!_node) {
return;
}
[super setAccessibilityFocused:isFocused];
ui::AXActionData data;
data.action = isFocused ? ax::mojom::Action::kFocus : ax::mojom::Action::kBlur;
Expand All @@ -110,6 +116,9 @@ - (void)startEditing {
if (self.currentEditor == _plugin) {
return;
}
if (!_node) {
return;
}
// Selecting text seems to be the only way to make the field editor
// current editor.
[self selectText:self];
Expand All @@ -133,6 +142,10 @@ - (void)startEditing {
[self updateString:textValue withSelection:selection];
}

- (void)setPlatformNode:(flutter::FlutterTextPlatformNode*)node {
_node = node;
}

#pragma mark - NSObject

- (void)dealloc {
Expand All @@ -159,6 +172,7 @@ - (void)dealloc {
}

FlutterTextPlatformNode::~FlutterTextPlatformNode() {
[appkit_text_field_ setPlatformNode:nil];
EnsureDetachedFromView();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,39 +27,48 @@

TEST(FlutterTextInputSemanticsObjectTest, DoesInitialize) {
FlutterEngine* engine = CreateTestEngine();
NSString* fixtures = @(testing::GetFixturesPath());
FlutterDartProject* project = [[FlutterDartProject alloc]
initWithAssetsPath:fixtures
ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
FlutterViewController* viewController = [[FlutterViewController alloc] initWithProject:project];
[viewController loadView];
[engine setViewController:viewController];
// Create a NSWindow so that the native text field can become first responder.
NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO];
window.contentView = viewController.view;
{
NSString* fixtures = @(testing::GetFixturesPath());
FlutterDartProject* project = [[FlutterDartProject alloc]
initWithAssetsPath:fixtures
ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
FlutterViewController* viewController = [[FlutterViewController alloc] initWithProject:project];
[viewController loadView];
[engine setViewController:viewController];
// Create a NSWindow so that the native text field can become first responder.
NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO];
window.contentView = viewController.view;

engine.semanticsEnabled = YES;

engine.semanticsEnabled = YES;
auto bridge = engine.accessibilityBridge.lock();
FlutterPlatformNodeDelegateMac delegate(bridge, viewController);
ui::AXTree tree;
ui::AXNode ax_node(&tree, nullptr, 0, 0);
ui::AXNodeData node_data;
node_data.SetValue("initial text");
ax_node.SetData(node_data);
delegate.Init(engine.accessibilityBridge, &ax_node);
// Verify that a FlutterTextField is attached to the view.
FlutterTextPlatformNode text_platform_node(&delegate, viewController);
id native_accessibility = text_platform_node.GetNativeViewAccessible();
EXPECT_TRUE([native_accessibility isKindOfClass:[FlutterTextField class]]);
auto subviews = [viewController.view subviews];
EXPECT_EQ([subviews count], 2u);
EXPECT_TRUE([subviews[0] isKindOfClass:[FlutterTextField class]]);
FlutterTextField* nativeTextField = subviews[0];
EXPECT_EQ(text_platform_node.GetNativeViewAccessible(), nativeTextField);
}

auto bridge = engine.accessibilityBridge.lock();
FlutterPlatformNodeDelegateMac delegate(bridge, viewController);
ui::AXTree tree;
ui::AXNode ax_node(&tree, nullptr, 0, 0);
ui::AXNodeData node_data;
node_data.SetValue("initial text");
ax_node.SetData(node_data);
delegate.Init(engine.accessibilityBridge, &ax_node);
// Verify that a FlutterTextField is attached to the view.
FlutterTextPlatformNode text_platform_node(&delegate, viewController);
id native_accessibility = text_platform_node.GetNativeViewAccessible();
EXPECT_TRUE([native_accessibility isKindOfClass:[FlutterTextField class]]);
auto subviews = [viewController.view subviews];
EXPECT_EQ([subviews count], 2u);
EXPECT_TRUE([subviews[0] isKindOfClass:[FlutterTextField class]]);
FlutterTextField* nativeTextField = subviews[0];
EXPECT_EQ(text_platform_node.GetNativeViewAccessible(), nativeTextField);
[engine shutDownEngine];
engine = nil;
// Pump the event loop to make sure no stray nodes cause crashes after the
// engine has been destroyed.
// From issue: https://github.com/flutter/flutter/issues/115599
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
}

} // namespace flutter::testing