Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Clipboard hasStrings method on iOS #19859

Merged
merged 10 commits into from
Aug 3, 2020
Merged

Conversation

justinmc
Copy link
Contributor

@justinmc justinmc commented Jul 17, 2020

Description

hasStrings is needed on iOS to allow us to check for clipboard contents without triggering the new iOS14 clipboard access notification.

Related Issues

flutter/flutter#60145

Tests

I'm desperately struggling to test this, as no other platform channel handlers seem to be tested at all for reference. See the TODOs in my code.

  • I was able to call and receive a result from hasStrings via handleMethodCall in a new test file.

@justinmc
Copy link
Contributor Author

justinmc commented Jul 17, 2020

Reviewers: Any idea how to test this? I made some attempts that didn't work and left them commented out with some questions. Let me know if you have any ideas.

Edit: I also tried creating a new FlutterPlatformPluginTest.mm file, but I couldn't get run_tests.py to pick it up.

@justinmc
Copy link
Contributor Author

Another question for reviewers:

How do we usually expose APIs that only exist on certain platforms (in this case iOS)? Is it alright that the framework needs to know to only call this for iOS devices? Another option is to implement this for the other platforms as well, but re-implement the behavior of hasStrings for them by reading the full clipboard text and returning whether or not it's a pasteable string. Then the framework could call it for any platform.

Copy link
Contributor

@GaryQian GaryQian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation LGTM, @jmagman for guidance on iOS testing (not too familiar myself)

/*
- (void)testHasStrings {
id project = OCMClassMock([FlutterDartProject class]);
FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a way to create a mock clipboard, and pass it into the mock engine/plugin, though not clear on implementation details

@@ -143,4 +144,25 @@ - (void)testCanCreatePlatformViewWithoutFlutterView {
flutterPlatformViewsController->Reset();
}

// TODO(justinmc): I wrote this test before realizing handleMethodCall is
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might be able to extend the class and expose a public version in the test class?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to make the channel with the same identifier and send a message to it and wait for a reply. There should be an example of this somewhere in our tests but I can't find one. Did you look at this test for ideas? https://github.com/gaaclarke/engine/blob/linting-warning/shell/platform/darwin/common/framework/Source/FlutterChannelsTest.m

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried this:

- (void)testClipboard {
  id project = OCMClassMock([FlutterDartProject class]);
  FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
  FlutterMethodChannel* channel =
      [FlutterMethodChannel methodChannelWithName:@"flutter/platform"
                                  binaryMessenger:engine.binaryMessenger
                                            codec:[FlutterJSONMethodCodec sharedInstance]];
  [engine run];
  XCTestExpectation* didCallReply = [self expectationWithDescription:@"didCallReply"];
  [channel invokeMethod:@"Clipboard.getData"
           arguments:nil
              result:^(id _Nullable result){
                [didCallReply fulfill];
              }];
  [self waitForExpectationsWithTimeout:1.0 handler:nil];
}

But it crashes because it tries to run on a fake project. Channels don't work unless an engine is running and the engine doesn't run unless there is a project and in unit tests the engine doesn't have a project... I'll look into this a bit more this afternoon to see what the best approach will be.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks I appreciate the help.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I looked at a few options and this appears to be more complicated than I had hoped. handleMethodCall isn't private so you can just call it directly for the test.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I forgot I could just call it with square brackets in objective c. That seems to work. Thanks for the help!

@GaryQian
Copy link
Contributor

Another question for reviewers:

How do we usually expose APIs that only exist on certain platforms (in this case iOS)? Is it alright that the framework needs to know to only call this for iOS devices? Another option is to implement this for the other platforms as well, but re-implement the behavior of hasStrings for them by reading the full clipboard text and returning whether or not it's a pasteable string. Then the framework could call it for any platform.

cc @Hixie for advice on this?

@Hixie
Copy link
Contributor

Hixie commented Jul 23, 2020

The API might not exist in literal form on other platforms but surely we can implement it ourselves based on the APIs they do expose, no? After all, we're going to want to use the same logic to enable/disable paste on all platforms.

@justinmc
Copy link
Contributor Author

The behavior may be slightly different on different native platforms, but yes I agree. I'll implement this method for our other platforms as well (in separate PRs I think).

@LongCatIsLooong
Copy link
Contributor

I also tried creating a new FlutterPlatformPluginTest.mm file, but I couldn't get run_tests.py to pick it up.

according to the README file:

When you add a new unit test file, also add a reference to that file in shell/platform/darwin/ios/BUILD.gn, under the sources list of the ios_flutter_test target. Once it's there, it will execute with the other tests.

@justinmc
Copy link
Contributor Author

Thanks for the pointer @LongCatIsLooong! It's working in its own file now.

@justinmc
Copy link
Contributor Author

@gaaclarke @GaryQian @LongCatIsLooong Ready for a final review.

Copy link
Contributor

@LongCatIsLooong LongCatIsLooong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation LGTM. Is the idea optionally sending the "hasStrings" message based on what defaultTargetPlatform says in the framework, instead of implementing such API for every platform?

[plugin handleMethodCall:methodCall result:result];

XCTAssertEqual(called, true);
XCTAssertEqual(value, true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the generalPasteboard always guaranteed to hasStrings ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point, I'm not sure. I'm going to add a call to setClipboardData right before to guarantee that it's true.

value = result[@"value"];
};
FlutterMethodCall* methodCall =
[FlutterMethodCall methodCallWithMethodName:@"Clipboard.hasStrings" arguments:@{}];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: is the arguments supposed to be nil instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, thanks.

Copy link
Contributor

@GaryQian GaryQian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@justinmc justinmc added the waiting for tree to go green This PR is approved and tested, but waiting for the tree to be green to land. label Aug 3, 2020
@justinmc
Copy link
Contributor Author

justinmc commented Aug 3, 2020

@LongCatIsLooong Yes that's right. I plan to implement hasStrings on all platforms and have the framework call it without caring what device it's running on. The alternative would be to implement it only on iOS and have the framework only call it for iOS and use other things like getData for other platforms.

@zanderso
Copy link
Member

zanderso commented Aug 3, 2020

The Fuchsia test failure is a flake. Landing on red to kick the tree.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
cla: yes platform-ios waiting for tree to go green This PR is approved and tested, but waiting for the tree to be green to land.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants