未验证 提交 bf620dc5 编写于 作者: C Chris Yang 提交者: GitHub

Reland "Add iOS platform view mutation XCUITests to the scenario app(#11652)" (#12707)

上级 d094a33e
......@@ -252,7 +252,8 @@ UIView* FlutterPlatformViewsController::ReconstructClipViewsChain(int number_of_
}
// If there were not enough existing clip views, add more.
while (clipIndex < number_of_clips) {
ChildClippingView* clippingView = [ChildClippingView new];
ChildClippingView* clippingView =
[[ChildClippingView alloc] initWithFrame:flutter_view_.get().bounds];
[clippingView addSubview:head];
head = clippingView;
clipIndex++;
......
......@@ -30,6 +30,14 @@
24D47D1B230C79840069DD5E /* golden_platform_view_D211AP.png in Resources */ = {isa = PBXBuildFile; fileRef = 24D47D1A230C79840069DD5E /* golden_platform_view_D211AP.png */; };
24D47D1D230CA2700069DD5E /* golden_platform_view_iPhone SE_simulator.png in Resources */ = {isa = PBXBuildFile; fileRef = 24D47D1C230CA2700069DD5E /* golden_platform_view_iPhone SE_simulator.png */; };
24F1FB89230B4579005ACE7C /* TextPlatformView.m in Sources */ = {isa = PBXBuildFile; fileRef = 24F1FB87230B4579005ACE7C /* TextPlatformView.m */; };
6816DB9E231750ED00A51400 /* GoldenPlatformViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6816DB9D231750ED00A51400 /* GoldenPlatformViewTests.m */; };
6816DBA12317573300A51400 /* GoldenImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 6816DBA02317573300A51400 /* GoldenImage.m */; };
6816DBA42318358200A51400 /* PlatformViewGoldenTestManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 6816DBA32318358200A51400 /* PlatformViewGoldenTestManager.m */; };
6816DBAA2318696600A51400 /* golden_platform_view_clippath_iPhone SE_simulator.png in Resources */ = {isa = PBXBuildFile; fileRef = 6816DBA52318696600A51400 /* golden_platform_view_clippath_iPhone SE_simulator.png */; };
6816DBAB2318696600A51400 /* golden_platform_view_transform_iPhone SE_simulator.png in Resources */ = {isa = PBXBuildFile; fileRef = 6816DBA62318696600A51400 /* golden_platform_view_transform_iPhone SE_simulator.png */; };
6816DBAC2318696600A51400 /* golden_platform_view_opacity_iPhone SE_simulator.png in Resources */ = {isa = PBXBuildFile; fileRef = 6816DBA72318696600A51400 /* golden_platform_view_opacity_iPhone SE_simulator.png */; };
6816DBAD2318696600A51400 /* golden_platform_view_cliprect_iPhone SE_simulator.png in Resources */ = {isa = PBXBuildFile; fileRef = 6816DBA82318696600A51400 /* golden_platform_view_cliprect_iPhone SE_simulator.png */; };
6816DBAE2318696600A51400 /* golden_platform_view_cliprrect_iPhone SE_simulator.png in Resources */ = {isa = PBXBuildFile; fileRef = 6816DBA92318696600A51400 /* golden_platform_view_cliprrect_iPhone SE_simulator.png */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
......@@ -113,6 +121,17 @@
24D47D1E230CA4480069DD5E /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
24F1FB87230B4579005ACE7C /* TextPlatformView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TextPlatformView.m; sourceTree = "<group>"; };
24F1FB88230B4579005ACE7C /* TextPlatformView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TextPlatformView.h; sourceTree = "<group>"; };
6816DB9C231750ED00A51400 /* GoldenPlatformViewTests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GoldenPlatformViewTests.h; sourceTree = "<group>"; };
6816DB9D231750ED00A51400 /* GoldenPlatformViewTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GoldenPlatformViewTests.m; sourceTree = "<group>"; };
6816DB9F2317573300A51400 /* GoldenImage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GoldenImage.h; sourceTree = "<group>"; };
6816DBA02317573300A51400 /* GoldenImage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GoldenImage.m; sourceTree = "<group>"; };
6816DBA22318358200A51400 /* PlatformViewGoldenTestManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PlatformViewGoldenTestManager.h; sourceTree = "<group>"; };
6816DBA32318358200A51400 /* PlatformViewGoldenTestManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PlatformViewGoldenTestManager.m; sourceTree = "<group>"; };
6816DBA52318696600A51400 /* golden_platform_view_clippath_iPhone SE_simulator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "golden_platform_view_clippath_iPhone SE_simulator.png"; sourceTree = "<group>"; };
6816DBA62318696600A51400 /* golden_platform_view_transform_iPhone SE_simulator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "golden_platform_view_transform_iPhone SE_simulator.png"; sourceTree = "<group>"; };
6816DBA72318696600A51400 /* golden_platform_view_opacity_iPhone SE_simulator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "golden_platform_view_opacity_iPhone SE_simulator.png"; sourceTree = "<group>"; };
6816DBA82318696600A51400 /* golden_platform_view_cliprect_iPhone SE_simulator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "golden_platform_view_cliprect_iPhone SE_simulator.png"; sourceTree = "<group>"; };
6816DBA92318696600A51400 /* golden_platform_view_cliprrect_iPhone SE_simulator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "golden_platform_view_cliprrect_iPhone SE_simulator.png"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
......@@ -200,9 +219,20 @@
244EA6CF230DBE8900B2D26E /* golden_platform_view_D21AP.png */,
24D47D1C230CA2700069DD5E /* golden_platform_view_iPhone SE_simulator.png */,
24D47D1A230C79840069DD5E /* golden_platform_view_D211AP.png */,
6816DBA52318696600A51400 /* golden_platform_view_clippath_iPhone SE_simulator.png */,
6816DBA82318696600A51400 /* golden_platform_view_cliprect_iPhone SE_simulator.png */,
6816DBA92318696600A51400 /* golden_platform_view_cliprrect_iPhone SE_simulator.png */,
6816DBA72318696600A51400 /* golden_platform_view_opacity_iPhone SE_simulator.png */,
6816DBA62318696600A51400 /* golden_platform_view_transform_iPhone SE_simulator.png */,
248D76EE22E388380012F0C1 /* PlatformViewUITests.m */,
248D76F022E388380012F0C1 /* Info.plist */,
24D47D1E230CA4480069DD5E /* README.md */,
6816DB9C231750ED00A51400 /* GoldenPlatformViewTests.h */,
6816DB9D231750ED00A51400 /* GoldenPlatformViewTests.m */,
6816DB9F2317573300A51400 /* GoldenImage.h */,
6816DBA02317573300A51400 /* GoldenImage.m */,
6816DBA22318358200A51400 /* PlatformViewGoldenTestManager.h */,
6816DBA32318358200A51400 /* PlatformViewGoldenTestManager.m */,
);
path = ScenariosUITests;
sourceTree = "<group>";
......@@ -340,9 +370,14 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
6816DBAE2318696600A51400 /* golden_platform_view_cliprrect_iPhone SE_simulator.png in Resources */,
6816DBAB2318696600A51400 /* golden_platform_view_transform_iPhone SE_simulator.png in Resources */,
6816DBAA2318696600A51400 /* golden_platform_view_clippath_iPhone SE_simulator.png in Resources */,
6816DBAD2318696600A51400 /* golden_platform_view_cliprect_iPhone SE_simulator.png in Resources */,
24D47D1B230C79840069DD5E /* golden_platform_view_D211AP.png in Resources */,
24D47D1D230CA2700069DD5E /* golden_platform_view_iPhone SE_simulator.png in Resources */,
244EA6D0230DBE8900B2D26E /* golden_platform_view_D21AP.png in Resources */,
6816DBAC2318696600A51400 /* golden_platform_view_opacity_iPhone SE_simulator.png in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......@@ -375,6 +410,9 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
6816DBA12317573300A51400 /* GoldenImage.m in Sources */,
6816DB9E231750ED00A51400 /* GoldenPlatformViewTests.m in Sources */,
6816DBA42318358200A51400 /* PlatformViewGoldenTestManager.m in Sources */,
248D76EF22E388380012F0C1 /* PlatformViewUITests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
......
......@@ -25,27 +25,55 @@
// This argument is used by the XCUITest for Platform Views so that the app
// under test will create platform views.
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--platform-view"]) {
FlutterEngine* engine = [[FlutterEngine alloc] initWithScenario:@"text_platform_view"
withCompletion:nil];
[engine runWithEntrypoint:nil];
FlutterViewController* flutterViewController =
[[NoStatusBarFlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil];
TextPlatformViewFactory* textPlatformViewFactory =
[[TextPlatformViewFactory alloc] initWithMessenger:flutterViewController.binaryMessenger];
NSObject<FlutterPluginRegistrar>* registrar =
[flutterViewController.engine registrarForPlugin:@"scenarios/TextPlatformViewPlugin"];
[registrar registerViewFactory:textPlatformViewFactory withId:@"scenarios/textPlatformView"];
self.window.rootViewController = flutterViewController;
// The launchArgsMap should match the one in the `PlatformVieGoldenTestManager`.
NSDictionary<NSString*, NSString*>* launchArgsMap = @{
@"--platform-view" : @"platform_view",
@"--platform-view-cliprect" : @"platform_view_cliprect",
@"--platform-view-cliprrect" : @"platform_view_cliprrect",
@"--platform-view-clippath" : @"platform_view_clippath",
@"--platform-view-transform" : @"platform_view_transform",
@"--platform-view-opacity" : @"platform_view_opacity",
};
__block NSString* goldenTestName = nil;
[launchArgsMap
enumerateKeysAndObjectsUsingBlock:^(NSString* argument, NSString* testName, BOOL* stop) {
if ([[[NSProcessInfo processInfo] arguments] containsObject:argument]) {
goldenTestName = testName;
*stop = YES;
}
}];
if (goldenTestName) {
[self readyContextForPlatformViewTests:goldenTestName];
} else if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--screen-before-flutter"]) {
self.window.rootViewController = [[ScreenBeforeFlutter alloc] initWithEngineRunCompletion:nil];
} else {
self.window.rootViewController = [[UIViewController alloc] init];
}
[self.window makeKeyAndVisible];
[self.window makeKeyAndVisible];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
- (void)readyContextForPlatformViewTests:(NSString*)scenarioIdentifier {
FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"PlatformViewTest" project:nil];
[engine runWithEntrypoint:nil];
FlutterViewController* flutterViewController =
[[NoStatusBarFlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil];
[engine.binaryMessenger
setMessageHandlerOnChannel:@"scenario_status"
binaryMessageHandler:^(NSData* _Nullable message, FlutterBinaryReply _Nonnull reply) {
[engine.binaryMessenger
sendOnChannel:@"set_scenario"
message:[scenarioIdentifier dataUsingEncoding:NSUTF8StringEncoding]];
}];
TextPlatformViewFactory* textPlatformViewFactory =
[[TextPlatformViewFactory alloc] initWithMessenger:flutterViewController.binaryMessenger];
NSObject<FlutterPluginRegistrar>* registrar =
[flutterViewController.engine registrarForPlugin:@"scenarios/TextPlatformViewPlugin"];
[registrar registerViewFactory:textPlatformViewFactory withId:@"scenarios/textPlatformView"];
self.window.rootViewController = flutterViewController;
}
@end
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface GoldenImage : NSObject
@property(readonly, copy, nonatomic) NSString* goldenName;
@property(readonly, strong, nonatomic) UIImage* image;
// Initilize with the golden file's prefix.
//
// Create an image from a golden file named prefix+devicemodel.
- (instancetype)initWithGoldenNamePrefix:(NSString*)prefix;
// Compare this GoldenImage to `image`.
//
// Return YES if the `image` of this GoldenImage have the same pixels of provided `image`.
- (BOOL)compareGoldenToImage:(UIImage*)image;
@end
NS_ASSUME_NONNULL_END
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "GoldenImage.h"
#import <XCTest/XCTest.h>
#include <sys/sysctl.h>
@interface GoldenImage ()
@end
@implementation GoldenImage
- (instancetype)initWithGoldenNamePrefix:(NSString*)prefix {
self = [super init];
if (self) {
_goldenName = [prefix stringByAppendingString:_platformName()];
NSBundle* bundle = [NSBundle bundleForClass:[self class]];
NSURL* goldenURL = [bundle URLForResource:_goldenName withExtension:@"png"];
NSData* data = [NSData dataWithContentsOfURL:goldenURL];
_image = [[UIImage alloc] initWithData:data];
}
return self;
}
- (BOOL)compareGoldenToImage:(UIImage*)image {
if (!self.image || !image) {
return NO;
}
CGImageRef imageRefA = [self.image CGImage];
CGImageRef imageRefB = [image CGImage];
NSUInteger widthA = CGImageGetWidth(imageRefA);
NSUInteger heightA = CGImageGetHeight(imageRefA);
NSUInteger widthB = CGImageGetWidth(imageRefB);
NSUInteger heightB = CGImageGetHeight(imageRefB);
if (widthA != widthB || heightA != heightB) {
return NO;
}
NSUInteger bytesPerPixel = 4;
NSUInteger size = widthA * heightA * bytesPerPixel;
NSMutableData* rawA = [NSMutableData dataWithLength:size];
NSMutableData* rawB = [NSMutableData dataWithLength:size];
if (!rawA || !rawB) {
return NO;
}
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
NSUInteger bytesPerRow = bytesPerPixel * widthA;
NSUInteger bitsPerComponent = 8;
CGContextRef contextA =
CGBitmapContextCreate(rawA.mutableBytes, widthA, heightA, bitsPerComponent, bytesPerRow,
colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGContextDrawImage(contextA, CGRectMake(0, 0, widthA, heightA), imageRefA);
CGContextRelease(contextA);
CGContextRef contextB =
CGBitmapContextCreate(rawB.mutableBytes, widthA, heightA, bitsPerComponent, bytesPerRow,
colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(contextB, CGRectMake(0, 0, widthA, heightA), imageRefB);
CGContextRelease(contextB);
if (memcmp(rawA.mutableBytes, rawB.mutableBytes, size)) {
return NO;
}
return YES;
}
NS_INLINE NSString* _platformName() {
NSString* simulatorName =
[[NSProcessInfo processInfo].environment objectForKey:@"SIMULATOR_DEVICE_NAME"];
if (simulatorName) {
return [NSString stringWithFormat:@"%@_simulator", simulatorName];
}
size_t size;
sysctlbyname("hw.model", NULL, &size, NULL, 0);
char* answer = malloc(size);
sysctlbyname("hw.model", answer, &size, NULL, 0);
NSString* results = [NSString stringWithCString:answer encoding:NSUTF8StringEncoding];
free(answer);
return results;
}
@end
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <XCTest/XCTest.h>
#import "PlatformViewGoldenTestManager.h"
NS_ASSUME_NONNULL_BEGIN
// The base class of all the PlatformView golden tests.
//
// A new PlatformView golden tests can subclass this and override the `-initiWithInvocation:`
// method, which then retun the `-initWithManager:invocation:`
//
// Then in any test method, call `checkGolden` to perform the golden test.
//
// This base class doesn't run any test case on its own.
@interface GoldenPlatformViewTests : XCTestCase
// Initialize with a `PlatformViewGoldenTestManager`.
- (instancetype)initWithManager:(PlatformViewGoldenTestManager*)manager
invocation:(NSInvocation*)invocation;
// Take a sceenshot of the test app and check it has the same pixels with goldenImage inside the
// `PlatformViewGoldenTestManager`.
- (void)checkGolden;
@end
NS_ASSUME_NONNULL_END
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "GoldenPlatformViewTests.h"
#include <sys/sysctl.h>
#import "PlatformViewGoldenTestManager.h"
static const NSInteger kSecondsToWaitForPlatformView = 30;
@interface GoldenPlatformViewTests ()
@property(nonatomic, copy) NSString* goldenName;
@property(nonatomic, strong) XCUIApplication* application;
@property(nonatomic, strong) PlatformViewGoldenTestManager* manager;
@end
@implementation GoldenPlatformViewTests
- (instancetype)initWithManager:(PlatformViewGoldenTestManager*)manager
invocation:(NSInvocation*)invocation {
self = [super initWithInvocation:invocation];
_manager = manager;
return self;
}
- (void)setUp {
[super setUp];
self.continueAfterFailure = NO;
self.application = [[XCUIApplication alloc] init];
self.application.launchArguments = @[ self.manager.launchArg ];
[self.application launch];
}
// Note: don't prefix with "test" or GoldenPlatformViewTests will run instead of the subclasses.
- (void)checkGolden {
XCUIElement* element = self.application.textViews.firstMatch;
BOOL exists = [element waitForExistenceWithTimeout:kSecondsToWaitForPlatformView];
if (!exists) {
XCTFail(@"It took longer than %@ second to find the platform view."
@"There might be issues with the platform view's construction,"
@"or with how the scenario is built.",
@(kSecondsToWaitForPlatformView));
}
GoldenImage* golden = self.manager.goldenImage;
XCUIScreenshot* screenshot = [[XCUIScreen mainScreen] screenshot];
XCTAttachment* attachment = [XCTAttachment attachmentWithScreenshot:screenshot];
attachment.lifetime = XCTAttachmentLifetimeKeepAlways;
[self addAttachment:attachment];
if (golden.image) {
XCTAttachment* goldenAttachment = [XCTAttachment attachmentWithImage:golden.image];
goldenAttachment.lifetime = XCTAttachmentLifetimeKeepAlways;
[self addAttachment:goldenAttachment];
} else {
XCTFail(@"This test will fail - no golden named %@ found. Follow the steps in the "
@"README to add a new golden.",
golden.goldenName);
}
XCTAssertTrue([golden compareGoldenToImage:screenshot.image]);
}
@end
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <Foundation/Foundation.h>
#import "GoldenImage.h"
NS_ASSUME_NONNULL_BEGIN
extern NSDictionary* launchArgsMap;
// Manages a `GoldenPlatformViewTests`.
//
// It creates the correct `identifer` based on the `launchArg`.
// It also generates the correct GoldenImage based on the `identifier`.
@interface PlatformViewGoldenTestManager : NSObject
@property(readonly, strong, nonatomic) GoldenImage* goldenImage;
@property(readonly, copy, nonatomic) NSString* identifier;
@property(readonly, copy, nonatomic) NSString* launchArg;
// Initilize with launchArg.
//
// Crahes if the launchArg is not mapped in `Appdelegate.launchArgsMap`.
- (instancetype)initWithLaunchArg:(NSString*)launchArg;
@end
NS_ASSUME_NONNULL_END
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "PlatformViewGoldenTestManager.h"
@interface PlatformViewGoldenTestManager ()
@property(readwrite, strong, nonatomic) GoldenImage* goldenImage;
@end
@implementation PlatformViewGoldenTestManager
NSDictionary* launchArgsMap;
- (instancetype)initWithLaunchArg:(NSString*)launchArg {
self = [super init];
if (self) {
// The launchArgsMap should match the one in the `PlatformVieGoldenTestManager`.
static NSDictionary<NSString*, NSString*>* launchArgsMap;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
launchArgsMap = @{
@"--platform-view" : @"platform_view",
@"--platform-view-cliprect" : @"platform_view_cliprect",
@"--platform-view-cliprrect" : @"platform_view_cliprrect",
@"--platform-view-clippath" : @"platform_view_clippath",
@"--platform-view-transform" : @"platform_view_transform",
@"--platform-view-opacity" : @"platform_view_opacity",
};
});
_identifier = launchArgsMap[launchArg];
NSString* prefix = [NSString stringWithFormat:@"golden_%@_", _identifier];
_goldenImage = [[GoldenImage alloc] initWithGoldenNamePrefix:prefix];
_launchArg = launchArg;
}
return self;
}
@end
......@@ -2,114 +2,113 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <Flutter/flutter.h>
#import <XCTest/XCTest.h>
#include <sys/sysctl.h>
#import "GoldenPlatformViewTests.h"
#import "../Scenarios/TextPlatformView.h"
@interface PlatformViewUITests : GoldenPlatformViewTests
@interface PlatformViewUITests : XCTestCase
@property(nonatomic, strong) XCUIApplication* application;
@end
@implementation PlatformViewUITests
- (void)setUp {
[super setUp];
self.continueAfterFailure = NO;
- (instancetype)initWithInvocation:(NSInvocation*)invocation {
PlatformViewGoldenTestManager* manager =
[[PlatformViewGoldenTestManager alloc] initWithLaunchArg:@"--platform-view"];
return [super initWithManager:manager invocation:invocation];
}
- (void)testPlatformView {
[self checkGolden];
}
@end
// Clip Rect Tests
@interface PlatformViewMutationClipRectTests : GoldenPlatformViewTests
@end
@implementation PlatformViewMutationClipRectTests
- (instancetype)initWithInvocation:(NSInvocation*)invocation {
PlatformViewGoldenTestManager* manager =
[[PlatformViewGoldenTestManager alloc] initWithLaunchArg:@"--platform-view-cliprect"];
return [super initWithManager:manager invocation:invocation];
}
- (void)testPlatformView {
[self checkGolden];
}
@end
@interface PlatformViewMutationClipRRectTests : GoldenPlatformViewTests
@end
self.application = [[XCUIApplication alloc] init];
self.application.launchArguments = @[ @"--platform-view" ];
[self.application launch];
@implementation PlatformViewMutationClipRRectTests
- (instancetype)initWithInvocation:(NSInvocation*)invocation {
PlatformViewGoldenTestManager* manager =
[[PlatformViewGoldenTestManager alloc] initWithLaunchArg:@"--platform-view-cliprrect"];
return [super initWithManager:manager invocation:invocation];
}
- (void)testPlatformView {
NSBundle* bundle = [NSBundle bundleForClass:[self class]];
NSString* goldenName =
[NSString stringWithFormat:@"golden_platform_view_%@", [self platformName]];
NSString* path = [bundle pathForResource:goldenName ofType:@"png"];
UIImage* golden = [[UIImage alloc] initWithContentsOfFile:path];
XCUIScreenshot* screenshot = [[XCUIScreen mainScreen] screenshot];
XCTAttachment* attachment = [XCTAttachment attachmentWithScreenshot:screenshot];
attachment.lifetime = XCTAttachmentLifetimeKeepAlways;
[self addAttachment:attachment];
if (golden) {
XCTAttachment* goldenAttachment = [XCTAttachment attachmentWithImage:golden];
goldenAttachment.lifetime = XCTAttachmentLifetimeKeepAlways;
[self addAttachment:goldenAttachment];
} else {
XCTFail(@"This test will fail - no golden named %@ found. Follow the steps in the "
@"README to add a new golden.",
goldenName);
}
XCTAssertTrue([self compareImage:golden toOther:screenshot.image]);
[self checkGolden];
}
@end
@interface PlatformViewMutationClipPathTests : GoldenPlatformViewTests
@end
@implementation PlatformViewMutationClipPathTests
- (instancetype)initWithInvocation:(NSInvocation*)invocation {
PlatformViewGoldenTestManager* manager =
[[PlatformViewGoldenTestManager alloc] initWithLaunchArg:@"--platform-view-clippath"];
return [super initWithManager:manager invocation:invocation];
}
- (NSString*)platformName {
NSString* simulatorName =
[[NSProcessInfo processInfo].environment objectForKey:@"SIMULATOR_DEVICE_NAME"];
if (simulatorName) {
return [NSString stringWithFormat:@"%@_simulator", simulatorName];
}
size_t size;
sysctlbyname("hw.model", NULL, &size, NULL, 0);
char* answer = malloc(size);
sysctlbyname("hw.model", answer, &size, NULL, 0);
NSString* results = [NSString stringWithCString:answer encoding:NSUTF8StringEncoding];
free(answer);
return results;
- (void)testPlatformView {
[self checkGolden];
}
- (BOOL)compareImage:(UIImage*)a toOther:(UIImage*)b {
CGImageRef imageRefA = [a CGImage];
CGImageRef imageRefB = [b CGImage];
@end
NSUInteger widthA = CGImageGetWidth(imageRefA);
NSUInteger heightA = CGImageGetHeight(imageRefA);
NSUInteger widthB = CGImageGetWidth(imageRefB);
NSUInteger heightB = CGImageGetHeight(imageRefB);
@interface PlatformViewMutationTransformTests : GoldenPlatformViewTests
if (widthA != widthB || heightA != heightB) {
return NO;
}
NSUInteger bytesPerPixel = 4;
NSUInteger size = widthA * heightA * bytesPerPixel;
NSMutableData* rawA = [NSMutableData dataWithLength:size];
NSMutableData* rawB = [NSMutableData dataWithLength:size];
@end
if (!rawA || !rawB) {
return NO;
}
@implementation PlatformViewMutationTransformTests
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
- (instancetype)initWithInvocation:(NSInvocation*)invocation {
PlatformViewGoldenTestManager* manager =
[[PlatformViewGoldenTestManager alloc] initWithLaunchArg:@"--platform-view-transform"];
return [super initWithManager:manager invocation:invocation];
}
- (void)testPlatformView {
[self checkGolden];
}
NSUInteger bytesPerRow = bytesPerPixel * widthA;
NSUInteger bitsPerComponent = 8;
CGContextRef contextA =
CGBitmapContextCreate(rawA.mutableBytes, widthA, heightA, bitsPerComponent, bytesPerRow,
colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
@end
CGContextDrawImage(contextA, CGRectMake(0, 0, widthA, heightA), imageRefA);
CGContextRelease(contextA);
@interface PlatformViewMutationOpacityTests : GoldenPlatformViewTests
CGContextRef contextB =
CGBitmapContextCreate(rawB.mutableBytes, widthA, heightA, bitsPerComponent, bytesPerRow,
colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
@end
CGContextDrawImage(contextB, CGRectMake(0, 0, widthA, heightA), imageRefB);
CGContextRelease(contextB);
@implementation PlatformViewMutationOpacityTests
if (memcmp(rawA.mutableBytes, rawB.mutableBytes, size)) {
return NO;
}
- (instancetype)initWithInvocation:(NSInvocation*)invocation {
PlatformViewGoldenTestManager* manager =
[[PlatformViewGoldenTestManager alloc] initWithLaunchArg:@"--platform-view-opacity"];
return [super initWithManager:manager invocation:invocation];
}
return YES;
- (void)testPlatformView {
[self checkGolden];
}
@end
......@@ -16,7 +16,12 @@ import 'src/scenario.dart';
Map<String, Scenario> _scenarios = <String, Scenario>{
'animated_color_square': AnimatedColorSquareScenario(window),
'text_platform_view': PlatformViewScenario(window, 'Hello from Scenarios (Platform View)'),
'platform_view': PlatformViewScenario(window, 'Hello from Scenarios (Platform View)', id: 0),
'platform_view_cliprect': PlatformViewClipRectScenario(window, 'PlatformViewClipRect', id: 1),
'platform_view_cliprrect': PlatformViewClipRRectScenario(window, 'PlatformViewClipRRect', id: 2),
'platform_view_clippath': PlatformViewClipPathScenario(window, 'PlatformViewClipPath', id: 3),
'platform_view_transform': PlatformViewTransformScenario(window, 'PlatformViewTransform', id: 4),
'platform_view_opacity': PlatformViewOpacityScenario(window, 'PlatformViewOpacity', id: 5),
'poppable_screen': PoppableScreenScenario(window),
};
......
......@@ -4,8 +4,10 @@
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'dart:typed_data';
import 'dart:ui';
import 'package:vector_math/vector_math_64.dart';
import 'scenario.dart';
......@@ -26,13 +28,147 @@ List<int> _to64(num value) {
}
/// A simple platform view.
class PlatformViewScenario extends Scenario {
class PlatformViewScenario extends Scenario
with _BasePlatformViewScenarioMixin {
/// Creates the PlatformView scenario.
///
/// The [window] parameter must not be null.
PlatformViewScenario(Window window, String text, {int id = 0})
: assert(window != null),
super(window) {
constructScenario(window, text, id);
}
@override
void onBeginFrame(Duration duration) {
final SceneBuilder builder = SceneBuilder();
builder.pushOffset(0, 0);
finishBuilderByAddingPlatformViewAndPicture(builder, 0);
}
}
/// Platform view with clip rect.
class PlatformViewClipRectScenario extends Scenario
with _BasePlatformViewScenarioMixin {
/// Constructs a platform view with clip rect scenario.
PlatformViewClipRectScenario(Window window, String text, {int id = 0})
: assert(window != null),
super(window) {
constructScenario(window, text, id);
}
@override
void onBeginFrame(Duration duration) {
final SceneBuilder builder = SceneBuilder();
builder.pushOffset(0, 0);
builder.pushClipRect(const Rect.fromLTRB(100, 100, 400, 400));
finishBuilderByAddingPlatformViewAndPicture(builder, 1);
}
}
/// Platform view with clip rrect.
class PlatformViewClipRRectScenario extends PlatformViewScenario {
/// Constructs a platform view with clip rrect scenario.
PlatformViewClipRRectScenario(Window window, String text, {int id = 0})
: super(window, text, id: id);
@override
void onBeginFrame(Duration duration) {
final SceneBuilder builder = SceneBuilder();
builder.pushOffset(0, 0);
builder.pushClipRRect(
RRect.fromLTRBAndCorners(
100,
100,
400,
400,
topLeft: const Radius.circular(15),
topRight: const Radius.circular(50),
bottomLeft: const Radius.circular(50),
),
);
finishBuilderByAddingPlatformViewAndPicture(builder, 2);
}
}
/// Platform view with clip path.
class PlatformViewClipPathScenario extends PlatformViewScenario {
/// Constructs a platform view with clip rrect scenario.
PlatformViewClipPathScenario(Window window, String text, {int id = 0})
: super(window, text, id: id);
@override
void onBeginFrame(Duration duration) {
final SceneBuilder builder = SceneBuilder();
builder.pushOffset(0, 0);
// Create a path of rectangle with width of 200 and height of 300, starting from (100, 100).
//
// Refer to "../../ios/Scenarios/Scenarios/ScenariosUITests/golden_platform_view_clippath_iPhone SE_simulator.png" for the exact path after clipping.
Path path = Path();
path.moveTo(100, 100);
path.quadraticBezierTo(50, 250, 100, 400);
path.lineTo(350, 400);
path.cubicTo(400, 300, 300, 200, 350, 100);
path.close();
builder.pushClipPath(path);
finishBuilderByAddingPlatformViewAndPicture(builder, 3);
}
}
/// Platform view with transform.
class PlatformViewTransformScenario extends PlatformViewScenario {
/// Constructs a platform view with transform scenario.
PlatformViewTransformScenario(Window window, String text, {int id = 0})
: super(window, text, id: id);
@override
void onBeginFrame(Duration duration) {
final SceneBuilder builder = SceneBuilder();
builder.pushOffset(0, 0);
final Matrix4 matrix4 = Matrix4.identity()
..rotateZ(1)
..scale(0.5, 0.5, 1.0)
..translate(1000.0, 100.0, 0.0);
builder.pushTransform(matrix4.storage);
finishBuilderByAddingPlatformViewAndPicture(builder, 4);
}
}
/// Platform view with opacity.
class PlatformViewOpacityScenario extends PlatformViewScenario {
/// Constructs a platform view with transform scenario.
PlatformViewOpacityScenario(Window window, String text, {int id = 0})
: super(window, text, id: id);
@override
void onBeginFrame(Duration duration) {
final SceneBuilder builder = SceneBuilder();
builder.pushOffset(0, 0);
builder.pushOpacity(150);
finishBuilderByAddingPlatformViewAndPicture(builder, 5);
}
}
mixin _BasePlatformViewScenarioMixin on Scenario {
int _textureId;
/// Construct the platform view related scenario
///
/// It prepare a TextPlatformView so it can be added to the SceneBuilder in `onBeginFrame`.
/// Call this method in the constructor of the platform view related scenarios
/// to perform necessary set up.
void constructScenario(Window window, String text, int id) {
const int _valueInt32 = 3;
const int _valueFloat64 = 6;
const int _valueString = 7;
......@@ -40,7 +176,7 @@ class PlatformViewScenario extends Scenario {
const int _valueMap = 13;
final Uint8List message = Uint8List.fromList(<int>[
_valueString,
'create'.length, // this is safe as long as these are all single byte characters.
'create'.length, // this won't work if we use multi-byte characters.
...utf8.encode('create'),
_valueMap,
if (Platform.isIOS)
......@@ -94,29 +230,27 @@ class PlatformViewScenario extends Scenario {
);
}
int _textureId;
@override
void onBeginFrame(Duration duration) {
final SceneBuilder builder = SceneBuilder();
builder.pushOffset(0, 0);
// Add a platform view and a picture to the scene, then finish the `sceneBuilder`.
void finishBuilderByAddingPlatformViewAndPicture(SceneBuilder sceneBuilder, int viewId) {
if (Platform.isIOS) {
builder.addPlatformView(0, width: 500, height: 500);
sceneBuilder.addPlatformView(viewId, width: 500, height: 500);
} else if (Platform.isAndroid && _textureId != null) {
builder.addTexture(_textureId, offset: const Offset(150, 300), width: 500, height: 500);
sceneBuilder.addTexture(_textureId,
offset: const Offset(150, 300), width: 500, height: 500);
} else {
throw UnsupportedError('Platform ${Platform.operatingSystem} is not supported');
throw UnsupportedError(
'Platform ${Platform.operatingSystem} is not supported');
}
final PictureRecorder recorder = PictureRecorder();
final Canvas canvas = Canvas(recorder);
canvas.drawCircle(const Offset(50, 50), 50, Paint()..color = const Color(0xFFABCDEF));
canvas.drawCircle(
const Offset(50, 50),
50,
Paint()..color = const Color(0xFFABCDEF),
);
final Picture picture = recorder.endRecording();
builder.addPicture(const Offset(300, 300), picture);
final Scene scene = builder.build();
sceneBuilder.addPicture(const Offset(300, 300), picture);
final Scene scene = sceneBuilder.build();
window.render(scene);
scene.dispose();
}
......
......@@ -8,3 +8,4 @@ dependencies:
path: ../../../out/host_debug_unopt/gen/dart-pkg/sky_engine
sky_services:
path: ../../../out/host_debug_unopt/gen/dart-pkg/sky_services
vector_math: ^2.0.8
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册