From cc64ce197173c38a5b682857c791ee7b0041d7a1 Mon Sep 17 00:00:00 2001 From: Di Da Date: Fri, 6 Dec 2019 15:56:14 -0800 Subject: [PATCH 01/24] Initial TreeDump change --- packages/E2ETest/app/Consts.ts | 5 + packages/E2ETest/app/ImageTestPage.tsx | 62 ++++++ packages/E2ETest/app/TestPages.ts | 7 + packages/E2ETest/wdio/pages/HomePage.ts | 11 ++ packages/E2ETest/wdio/pages/ImageTestPage.ts | 28 +++ packages/E2ETest/wdio/test/Image.spec.ts | 23 +++ packages/E2ETest/windows/ReactUWPTestApp.sln | 15 ++ .../windows/ReactUWPTestApp/App.xaml.cs | 5 + .../ReactUWPTestApp/ReactUWPTestApp.csproj | 7 + .../windows/ReactUWPTestApp/copyTreeDump.cmd | 1 + .../TreeDumpLibrary/ReactPackageProvider.cs | 16 ++ .../TreeDumpControlViewManager.cs | 142 ++++++++++++++ .../TreeDumpLibrary/TreeDumpLibrary.csproj | 120 ++++++++++++ .../TreeDumpLibrary/VisualTreeDumper.cs | 178 ++++++++++++++++++ vnext/ReactUWP/Views/ReactControl.cpp | 4 + yarn.lock | 8 +- 16 files changed, 628 insertions(+), 4 deletions(-) create mode 100644 packages/E2ETest/app/ImageTestPage.tsx create mode 100644 packages/E2ETest/wdio/pages/ImageTestPage.ts create mode 100644 packages/E2ETest/wdio/test/Image.spec.ts create mode 100644 packages/E2ETest/windows/ReactUWPTestApp/copyTreeDump.cmd create mode 100644 packages/E2ETest/windows/TreeDumpLibrary/ReactPackageProvider.cs create mode 100644 packages/E2ETest/windows/TreeDumpLibrary/TreeDumpControlViewManager.cs create mode 100644 packages/E2ETest/windows/TreeDumpLibrary/TreeDumpLibrary.csproj create mode 100644 packages/E2ETest/windows/TreeDumpLibrary/VisualTreeDumper.cs diff --git a/packages/E2ETest/app/Consts.ts b/packages/E2ETest/app/Consts.ts index c6e0246d04a..fbd72ac50c4 100644 --- a/packages/E2ETest/app/Consts.ts +++ b/packages/E2ETest/app/Consts.ts @@ -31,3 +31,8 @@ export const ACCESSBILITY_TESTPAGE = 'AccessiblityTestPage'; export const DIRECT_MANIPULATION_TESTPAGE = 'DirectManipulationTestPage'; export const MEASURE_IN_WINDOW_BUTTON = 'MeasureInWindow'; export const MEASURE_IN_WINDOW_RESULT = 'MeasureInWindowResult'; + +// Image Test Page +export const IMAGE_TESTPAGE = 'ImageTestPage'; +export const IMAGE_CHANGE_BORDER = 'ChangeBorder'; +export const IMAGE_TREE_DUMP_RESULT = 'TreeDump'; diff --git a/packages/E2ETest/app/ImageTestPage.tsx b/packages/E2ETest/app/ImageTestPage.tsx new file mode 100644 index 00000000000..e27b4911af5 --- /dev/null +++ b/packages/E2ETest/app/ImageTestPage.tsx @@ -0,0 +1,62 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +import {StyleSheet, View, Image, requireNativeComponent} from 'react-native' +import React from 'react'; +import { IMAGE_TREE_DUMP_RESULT } from './Consts'; +const TreeDumpControl = requireNativeComponent('TreeDumpControl'); + +const styles = StyleSheet.create({ + container: { + height:220, + width:450, + }, + imageWithBorder: { + height: '100%', + width: '100%', + borderRadius: 10.0, + borderWidth:10, + borderColor: 'green', + backgroundColor: 'red', + }, + image: { + height: '100%', + width: '100%', + }, + buttonContainer: { + backgroundColor: '#2980b6', + paddingVertical: 15 + }, + buttonText: { + color: '#fff', + textAlign: 'center', + fontWeight: '700' + }, + imageContainer: { + marginTop: 5, + height:200, + width:400, + backgroundColor: 'orange', + }, + treeDumpControl: { + height: 90, + width: 400, + margin: 10, + }, +}); + +export function ImageTestPage() { + return( + + + + + + ); +} \ No newline at end of file diff --git a/packages/E2ETest/app/TestPages.ts b/packages/E2ETest/app/TestPages.ts index ec1f4b1da03..5973e8af428 100644 --- a/packages/E2ETest/app/TestPages.ts +++ b/packages/E2ETest/app/TestPages.ts @@ -13,10 +13,12 @@ import { LOGIN_TESTPAGE, ACCESSBILITY_TESTPAGE, DIRECT_MANIPULATION_TESTPAGE, + IMAGE_TESTPAGE, } from './Consts'; import { LoginTestPage } from './LoginTestPage'; import { AccessibilityTestPage } from './AccessibilityTestPage'; import { DirectManipulationTestPage } from './DirectManipulationPage'; +import { ImageTestPage } from './ImageTestPage'; export interface ITestPage { testId: string; @@ -45,6 +47,11 @@ const TestPages: ITestPage[] = [ description: 'Direct Manipulation Test Page', content: DirectManipulationTestPage, }, + { + testId: IMAGE_TESTPAGE, + description: 'Image Test Page', + content: ImageTestPage, + }, { testId: UNKNOWN_TESTPAGE, description: 'Unknown Page', diff --git a/packages/E2ETest/wdio/pages/HomePage.ts b/packages/E2ETest/wdio/pages/HomePage.ts index 74d297716a4..ae7026e3ef3 100644 --- a/packages/E2ETest/wdio/pages/HomePage.ts +++ b/packages/E2ETest/wdio/pages/HomePage.ts @@ -9,9 +9,11 @@ import { TEXTINPUT_TESTPAGE, LOGIN_TESTPAGE, DIRECT_MANIPULATION_TESTPAGE, + IMAGE_TESTPAGE, } from '../../app/Consts'; import LoginPage from './LoginPage'; import DirectManipulationPage from './DirectManipulationPage'; +import ImageTestPage from './ImageTestPage'; class HomePage extends BasePage { backToHomePage() { @@ -38,6 +40,11 @@ class HomePage extends BasePage { DirectManipulationPage.waitForPageLoaded(); } + clickAndGotoImagePage() { + this.ImagePageButton.click(); + ImageTestPage.waitForPageLoaded(); + } + private get testInputTestPageButton() { return By(TEXTINPUT_TESTPAGE); } @@ -49,6 +56,10 @@ class HomePage extends BasePage { private get directManipulationPageButton() { return By(DIRECT_MANIPULATION_TESTPAGE); } + + private get ImagePageButton() { + return By(IMAGE_TESTPAGE); + } } export default new HomePage(); diff --git a/packages/E2ETest/wdio/pages/ImageTestPage.ts b/packages/E2ETest/wdio/pages/ImageTestPage.ts new file mode 100644 index 00000000000..ca120f7de25 --- /dev/null +++ b/packages/E2ETest/wdio/pages/ImageTestPage.ts @@ -0,0 +1,28 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +import { BasePage, By } from './BasePage'; +import { IMAGE_TREE_DUMP_RESULT } from '../../app/Consts'; + +class ImageTestPage extends BasePage { + backToHomePage() { + this.homeButton.click(); + this.waitForPageLoaded(); + } + + isPageLoaded() { + return super.isPageLoaded() && this.treeDumpResult.isDisplayed(); + } + + getTreeDumpResult() { + return this.treeDumpResult.getText(); + } + + private get treeDumpResult() { + return By(IMAGE_TREE_DUMP_RESULT); + } +} + +export default new ImageTestPage(); diff --git a/packages/E2ETest/wdio/test/Image.spec.ts b/packages/E2ETest/wdio/test/Image.spec.ts new file mode 100644 index 00000000000..fdb4af0ff27 --- /dev/null +++ b/packages/E2ETest/wdio/test/Image.spec.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +import HomePage from '../pages/HomePage'; +import ImageTestPage from '../pages/ImageTestPage'; +import assert from 'assert'; + +beforeAll(() => { + HomePage.backToHomePage(); + HomePage.clickAndGotoImagePage(); +}); + +describe('ImageWithoutBorderTest', () => { + it('ImageWithoutBorderTest Success', () => { + const result = ImageTestPage.getTreeDumpResult(); + assert.ok( + result.includes('TreeDump:Passed'), + 'Dump comparison passed for image without border!' + ); + }); +}); diff --git a/packages/E2ETest/windows/ReactUWPTestApp.sln b/packages/E2ETest/windows/ReactUWPTestApp.sln index 72a6b2edf8a..3be1239de1e 100644 --- a/packages/E2ETest/windows/ReactUWPTestApp.sln +++ b/packages/E2ETest/windows/ReactUWPTestApp.sln @@ -44,6 +44,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative", ".. EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.ReactNative.SharedManaged", "..\..\..\vnext\Microsoft.ReactNative.SharedManaged\Microsoft.ReactNative.SharedManaged.shproj", "{67A1076F-7790-4203-86EA-4402CCB5E782}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TreeDumpLibrary", "TreeDumpLibrary\TreeDumpLibrary.csproj", "{C0A6BD9C-3EE5-4B12-8CE4-CEE95178539C}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution ..\..\..\vnext\Chakra\Chakra.vcxitems*{2d5d43d9-cffc-4c40-b4cd-02efb4e2742b}*SharedItemsImports = 4 @@ -51,6 +53,7 @@ Global ..\..\..\vnext\Microsoft.ReactNative.SharedManaged\Microsoft.ReactNative.SharedManaged.projitems*{67a1076f-7790-4203-86ea-4402ccb5e782}*SharedItemsImports = 13 ..\..\..\vnext\JSI\Shared\JSI.Shared.vcxitems*{a62d504a-16b8-41d2-9f19-e2e86019e5e4}*SharedItemsImports = 4 ..\..\..\vnext\Microsoft.ReactNative.SharedManaged\Microsoft.ReactNative.SharedManaged.projitems*{abbb0407-0e82-486f-94ce-710900fcaadc}*SharedItemsImports = 4 + ..\..\..\vnext\Microsoft.ReactNative.SharedManaged\Microsoft.ReactNative.SharedManaged.projitems*{c0a6bd9c-3ee5-4b12-8ce4-cee95178539c}*SharedItemsImports = 4 EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM = Debug|ARM @@ -163,6 +166,18 @@ Global {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x64.Build.0 = Release|x64 {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x86.ActiveCfg = Release|Win32 {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x86.Build.0 = Release|Win32 + {C0A6BD9C-3EE5-4B12-8CE4-CEE95178539C}.Debug|ARM.ActiveCfg = Debug|ARM + {C0A6BD9C-3EE5-4B12-8CE4-CEE95178539C}.Debug|ARM.Build.0 = Debug|ARM + {C0A6BD9C-3EE5-4B12-8CE4-CEE95178539C}.Debug|x64.ActiveCfg = Debug|x64 + {C0A6BD9C-3EE5-4B12-8CE4-CEE95178539C}.Debug|x64.Build.0 = Debug|x64 + {C0A6BD9C-3EE5-4B12-8CE4-CEE95178539C}.Debug|x86.ActiveCfg = Debug|x86 + {C0A6BD9C-3EE5-4B12-8CE4-CEE95178539C}.Debug|x86.Build.0 = Debug|x86 + {C0A6BD9C-3EE5-4B12-8CE4-CEE95178539C}.Release|ARM.ActiveCfg = Release|ARM + {C0A6BD9C-3EE5-4B12-8CE4-CEE95178539C}.Release|ARM.Build.0 = Release|ARM + {C0A6BD9C-3EE5-4B12-8CE4-CEE95178539C}.Release|x64.ActiveCfg = Release|x64 + {C0A6BD9C-3EE5-4B12-8CE4-CEE95178539C}.Release|x64.Build.0 = Release|x64 + {C0A6BD9C-3EE5-4B12-8CE4-CEE95178539C}.Release|x86.ActiveCfg = Release|x86 + {C0A6BD9C-3EE5-4B12-8CE4-CEE95178539C}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/packages/E2ETest/windows/ReactUWPTestApp/App.xaml.cs b/packages/E2ETest/windows/ReactUWPTestApp/App.xaml.cs index 16d3716cf67..67589bb06e3 100644 --- a/packages/E2ETest/windows/ReactUWPTestApp/App.xaml.cs +++ b/packages/E2ETest/windows/ReactUWPTestApp/App.xaml.cs @@ -9,6 +9,8 @@ using Windows.UI.Xaml.Navigation; using Microsoft.ReactNative; using Windows.UI.Core; +using Windows.UI.ViewManagement; +using Windows.Foundation; namespace ReactUWPTestApp { @@ -42,6 +44,8 @@ public App() #endif PackageProviders.Add(new Microsoft.ReactNative.Managed.ReactPackageProvider()); // Includes any modules in this project + PackageProviders.Add(new TreeDumpLibrary.ReactPackageProvider()); + this.InitializeComponent(); } @@ -54,6 +58,7 @@ protected override void OnLaunched(LaunchActivatedEventArgs e) { base.OnLaunched(e); SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Collapsed; + ApplicationView.GetForCurrentView().TryResizeView(new Size(1024, 768)); } } } diff --git a/packages/E2ETest/windows/ReactUWPTestApp/ReactUWPTestApp.csproj b/packages/E2ETest/windows/ReactUWPTestApp/ReactUWPTestApp.csproj index 37071e39d51..c0f78fafcb5 100644 --- a/packages/E2ETest/windows/ReactUWPTestApp/ReactUWPTestApp.csproj +++ b/packages/E2ETest/windows/ReactUWPTestApp/ReactUWPTestApp.csproj @@ -105,6 +105,9 @@ Designer + + Always + @@ -139,6 +142,10 @@ {f7d32bd0-2749-483e-9a0d-1635ef7e3136} Microsoft.ReactNative + + {c0a6bd9c-3ee5-4b12-8ce4-cee95178539c} + TreeDumpLibrary + diff --git a/packages/E2ETest/windows/ReactUWPTestApp/copyTreeDump.cmd b/packages/E2ETest/windows/ReactUWPTestApp/copyTreeDump.cmd new file mode 100644 index 00000000000..f8dab821abe --- /dev/null +++ b/packages/E2ETest/windows/ReactUWPTestApp/copyTreeDump.cmd @@ -0,0 +1 @@ +copy %LocalAppData%\Packages\ReactUWPTestApp_kc2bncckyf4ap\LocalState\TreeDump\*.out Assets\TreeDump\*.treedump \ No newline at end of file diff --git a/packages/E2ETest/windows/TreeDumpLibrary/ReactPackageProvider.cs b/packages/E2ETest/windows/TreeDumpLibrary/ReactPackageProvider.cs new file mode 100644 index 00000000000..54dccd72e3b --- /dev/null +++ b/packages/E2ETest/windows/TreeDumpLibrary/ReactPackageProvider.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.ReactNative.Bridge; +using Microsoft.ReactNative.Managed; + +namespace TreeDumpLibrary +{ + public sealed class ReactPackageProvider : IReactPackageProvider + { + public void CreatePackage(IReactPackageBuilder packageBuilder) + { + packageBuilder.AddViewManagers(); + } + } +} diff --git a/packages/E2ETest/windows/TreeDumpLibrary/TreeDumpControlViewManager.cs b/packages/E2ETest/windows/TreeDumpLibrary/TreeDumpControlViewManager.cs new file mode 100644 index 00000000000..ce9815ef7db --- /dev/null +++ b/packages/E2ETest/windows/TreeDumpLibrary/TreeDumpControlViewManager.cs @@ -0,0 +1,142 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Diagnostics; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Controls; + +using Microsoft.ReactNative.Managed; +using Microsoft.ReactNative.Bridge; +using Windows.UI.Xaml; +using Windows.UI.ViewManagement; +using System.Threading.Tasks; +using System; +using System.IO; +using Windows.Storage; + +namespace TreeDumpLibrary +{ + internal class TreeDumpControlViewManager : AttributedViewManager + { + enum TreeDumpMatchResult { Disabled, Passed, Failed}; + public override string Name => "TreeDumpControl"; + public TreeDumpControlViewManager(IReactContext reactContext) : base(reactContext) { } + + public override FrameworkElement CreateView() + { + m_textBlock = new TextBlock(); + m_textBlock.TextWrapping = TextWrapping.Wrap; + m_textBlock.LayoutUpdated += async (source, e) => + { + var bounds = ApplicationView.GetForCurrentView().VisibleBounds; + if (bounds.Width != 1024 || bounds.Height != 768) + { + // Dump disabled when window size is not 1024x768! + UpdateResult(TreeDumpMatchResult.Disabled, "Window has been resized, dump comparison is only valid at default launch size: 1024x768!"); + } + else + { + await MatchTreeDumpFromLayoutUpdateAsync(); + } + }; + + m_textBlock.PointerPressed += (soruce, e) => + { + if (m_matchResult != TreeDumpMatchResult.Passed) + { + m_textBlock.Text = m_matchHelpString; + m_textBlock.IsTextSelectionEnabled = true; + } + }; + + return m_textBlock; + } + + [ViewManagerProperty("dumpID")] + public void SetDumpID(TextBlock view, string value) + { + m_dumpID = value; + } + + private async Task MatchTreeDumpFromLayoutUpdateAsync() + { + DependencyObject parent = VisualTreeHelper.GetParent(m_textBlock); + while (parent != null) + { + var name = ((FrameworkElement)parent).Name; + if (name == "RNRootView") + { + String dumpText = VisualTreeDumper.DumpToXML(parent, m_textBlock /* exclude */); + if (dumpText != m_dumpExpectedText) + { + await MatchDump(dumpText); + } + } + parent = VisualTreeHelper.GetParent(parent); + } + } + + private async Task MatchDump(string dumpText) + { + if (m_dumpExpectedText == null) + { + try + { + var file = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFileAsync(@"Assets\TreeDump\" + m_dumpID + ".treedump"); + m_dumpExpectedText = await Windows.Storage.FileIO.ReadTextAsync(file); + } + catch (IOException) + { + UpdateResult(TreeDumpMatchResult.Failed, "Tree dump master file not found in testapp package!"); + } + } + + if (m_dumpExpectedText != dumpText) + { + StorageFolder storageFolder = ApplicationData.Current.LocalFolder; + string fileName = "TreeDump\\" + m_dumpID + ".out"; + try + { + StorageFile outFile = await storageFolder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting); + await Windows.Storage.FileIO.WriteTextAsync(outFile, dumpText); + UpdateResult(TreeDumpMatchResult.Failed, "Tree dump file does not match master! See output at " + outFile.Path); + } + catch (IOException) + { + UpdateResult(TreeDumpMatchResult.Failed, "Can't write dump output file:" + fileName); + } + } + else + { + UpdateResult(TreeDumpMatchResult.Passed, ""); + } + } + + private void UpdateResult(TreeDumpMatchResult result, string helpText) + { + if (m_matchResult != result) + { + switch (result) + { + case TreeDumpMatchResult.Disabled: + m_textBlock.Text = "TreeDump:Disabled, click to see more!"; + break; + case TreeDumpMatchResult.Failed: + m_textBlock.Text = "TreeDump:Failed, click to see more!"; + break; + case TreeDumpMatchResult.Passed: + m_textBlock.Text = "TreeDump:Passed"; + break; + } + m_matchResult = result; + m_matchHelpString = helpText; + } + } + + private TextBlock m_textBlock = null; + private string m_dumpID = "UnknownTest"; + private string m_dumpExpectedText; + private TreeDumpMatchResult m_matchResult = TreeDumpMatchResult.Disabled; + private string m_matchHelpString = "No help text"; + } +} diff --git a/packages/E2ETest/windows/TreeDumpLibrary/TreeDumpLibrary.csproj b/packages/E2ETest/windows/TreeDumpLibrary/TreeDumpLibrary.csproj new file mode 100644 index 00000000000..35a6c334c05 --- /dev/null +++ b/packages/E2ETest/windows/TreeDumpLibrary/TreeDumpLibrary.csproj @@ -0,0 +1,120 @@ + + + + + Debug + AnyCPU + {C0A6BD9C-3EE5-4B12-8CE4-CEE95178539C} + winmdobj + Properties + TreeDumpLibrary + TreeDumpLibrary + en-US + UAP + 10.0.18362.0 + 10.0.15063.0 + 14 + 512 + {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + false + false + + + x86 + true + bin\x86\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + false + prompt + + + x86 + bin\x86\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + false + prompt + + + ARM + true + bin\ARM\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + false + prompt + + + ARM + bin\ARM\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + false + prompt + + + x64 + true + bin\x64\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + false + prompt + + + x64 + bin\x64\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + false + prompt + + + PackageReference + + + $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), 'node_modules\react-native-windows\package.json'))\node_modules\react-native-windows\ + + + + + + + + + 6.2.8 + + + + + {f7d32bd0-2749-483e-9a0d-1635ef7e3136} + Microsoft.ReactNative + False + + + + + + + + 14.0 + + + + \ No newline at end of file diff --git a/packages/E2ETest/windows/TreeDumpLibrary/VisualTreeDumper.cs b/packages/E2ETest/windows/TreeDumpLibrary/VisualTreeDumper.cs new file mode 100644 index 00000000000..8a41e2df498 --- /dev/null +++ b/packages/E2ETest/windows/TreeDumpLibrary/VisualTreeDumper.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Media; + +namespace TreeDumpLibrary +{ + public sealed class VisualTreeDumper + { + class Visitor + { + private DefaultVisualTreeLogger _logger; + private int _indent; + private DefaultFilter _filter; + private DefaultPropertyValueTranslator _translator; + public Visitor(DefaultFilter filter, DefaultPropertyValueTranslator translator, DefaultVisualTreeLogger logger) + { + _indent = 0; + _filter = filter; + _translator = translator; + _logger = logger; + } + public void EndVisitNode(DependencyObject obj) + { + _indent--; + _logger.EndNode(_indent, obj.GetType().FullName, obj); + } + + public void BeginVisitNode(DependencyObject obj) + { + _logger.BeginNode(_indent, obj.GetType().FullName, obj); + _indent++; + } + + public override string ToString() + { + return _logger.ToString(); + } + + public bool ShouldVisitPropertiesForNode(DependencyObject node) + { + return (node as UIElement) != null; + } + + public bool ShouldVisitProperty(PropertyInfo propertyInfo) + { + return _filter.ShouldVisitProperty(propertyInfo.Name); + } + + public void VisitProperty(string propertyName, object value) + { + var v = _translator.PropertyValueToString(propertyName, value); + if (_filter.ShouldVisitPropertyValue(v)) + { + _logger.LogProperty(_indent + 1, propertyName, v); + } + } + } + + public static string DumpToXML(DependencyObject root, DependencyObject excludedNode) + { + + Visitor visitor = new Visitor(new DefaultFilter(), + new DefaultPropertyValueTranslator(), + new DefaultVisualTreeLogger()); + WalkThroughTree(root, excludedNode, visitor); + + return visitor.ToString(); + } + + private static void WalkThroughProperties(DependencyObject node, Visitor visitor) + { + if (visitor.ShouldVisitPropertiesForNode(node)) + { + var properties = node.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); + foreach (var property in properties) + { + if (visitor.ShouldVisitProperty(property)) + { + Object value = null; + + try + { + value = property.GetValue(obj: node, index: null); + } + catch (Exception) + { + value = "Exception when read " + property.Name; + } + visitor.VisitProperty(property.Name, value); + } + } + } + } + private static void WalkThroughTree(DependencyObject node, DependencyObject excludedNode, Visitor visitor) + { + if (node != null) + { + visitor.BeginVisitNode(node); + + WalkThroughProperties(node, visitor); + for (int i = 0; i < VisualTreeHelper.GetChildrenCount(node); i++) + { + var child = VisualTreeHelper.GetChild(node, i); + if (child != excludedNode) + { + WalkThroughTree(child, excludedNode, visitor); + } + } + + visitor.EndVisitNode(node); + } + } + } + public sealed class DefaultFilter + { + private List _propertyNameWhiteList = new List {"Background", "Foreground", "Padding", "Margin", "RenderSize", "Visibility", "CornerRadius", "BorderThickness", + "Width", "Height", "BorderBrush", "VerticalAlignment", "HorizontalAlignment", "Opacity", "Clip", "ActualOffset"}; + + public bool ShouldVisitPropertyValue(string propertyValue) + { + return !string.IsNullOrEmpty(propertyValue) && !propertyValue.Equals("NaN") && !propertyValue.StartsWith("Exception"); + } + + public bool ShouldVisitProperty(string propertyName) + { + return (_propertyNameWhiteList.Contains(propertyName)); + } + } + public sealed class DefaultPropertyValueTranslator + { + public string PropertyValueToString(string propertyName, object propertyObject) + { + if (propertyObject == null) + { + return "[NULL]"; + } + + var brush = propertyObject as SolidColorBrush; + if (brush != null) + { + return brush.Color.ToString(); + } + return propertyObject.ToString(); + } + } + public sealed class DefaultVisualTreeLogger + { + public void BeginNode(int indent, string nodeName, DependencyObject obj) + { + AppendLogger(indent, string.Format("[{0}]", nodeName)); + } + + public void EndNode(int indent, string nodeName, DependencyObject obj) + { + } + + public void LogProperty(int indent, string propertyName, object propertyValue) + { + AppendLogger(indent, string.Format("{0}={1}", propertyName, propertyValue)); + } + + public override string ToString() + { + return _logger.ToString(); + } + + private StringBuilder _logger = new StringBuilder(); + private void AppendLogger(int indent, string s) + { + _logger.AppendLine(s.PadLeft(2 * indent + s.Length)); + } + } +} diff --git a/vnext/ReactUWP/Views/ReactControl.cpp b/vnext/ReactUWP/Views/ReactControl.cpp index 80754f3d4f9..b9d9cbaf988 100644 --- a/vnext/ReactUWP/Views/ReactControl.cpp +++ b/vnext/ReactUWP/Views/ReactControl.cpp @@ -357,6 +357,10 @@ void ReactControl::PrepareXamlRootView(XamlView const &rootView) { m_xamlRootView = newRootView; } else m_xamlRootView = rootView; + + if (m_xamlRootView.as().Name().empty()){ + m_xamlRootView.as().Name(L"RNRootView"); + } } void ReactControl::EnsureFocusSafeHarbor() { diff --git a/yarn.lock b/yarn.lock index 08bff67eea1..689f322206f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9952,10 +9952,10 @@ react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.9.0.tgz#21ca9561399aad0ff1a7701c01683e8ca981edcb" integrity sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw== -"react-native@https://github.com/microsoft/react-native/archive/v0.60.0-microsoft.24.tar.gz": - version "0.60.0-microsoft.24" - uid ce0f59e0884b5dacf76fa8a452249fefe336e152 - resolved "https://github.com/microsoft/react-native/archive/v0.60.0-microsoft.24.tar.gz#ce0f59e0884b5dacf76fa8a452249fefe336e152" +"react-native@https://github.com/microsoft/react-native/archive/v0.60.0-microsoft.28.tar.gz": + version "0.60.0-microsoft.28" + uid fad0f343cf2ee7c277f89b86f770efa71872839e + resolved "https://github.com/microsoft/react-native/archive/v0.60.0-microsoft.28.tar.gz#fad0f343cf2ee7c277f89b86f770efa71872839e" dependencies: "@babel/core" "^7.4.0" "@babel/generator" "^7.4.0" From c25021ae0edf82a3272a66357d9e4437e1899977 Mon Sep 17 00:00:00 2001 From: Di Da Date: Fri, 6 Dec 2019 15:57:12 -0800 Subject: [PATCH 02/24] ReactControl.cpp --- vnext/ReactUWP/Views/ReactControl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vnext/ReactUWP/Views/ReactControl.cpp b/vnext/ReactUWP/Views/ReactControl.cpp index b9d9cbaf988..702999747d5 100644 --- a/vnext/ReactUWP/Views/ReactControl.cpp +++ b/vnext/ReactUWP/Views/ReactControl.cpp @@ -358,7 +358,7 @@ void ReactControl::PrepareXamlRootView(XamlView const &rootView) { } else m_xamlRootView = rootView; - if (m_xamlRootView.as().Name().empty()){ + if (m_xamlRootView.as().Name().empty()) { m_xamlRootView.as().Name(L"RNRootView"); } } From 6c2916f829fb8a9c0a526cc0cfeb405581a553ca Mon Sep 17 00:00:00 2001 From: Di Da Date: Fri, 6 Dec 2019 15:58:05 -0800 Subject: [PATCH 03/24] first dump --- .../TreeDump/ImageWithoutBorder.treedump | 237 ++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 packages/E2ETest/windows/ReactUWPTestApp/Assets/TreeDump/ImageWithoutBorder.treedump diff --git a/packages/E2ETest/windows/ReactUWPTestApp/Assets/TreeDump/ImageWithoutBorder.treedump b/packages/E2ETest/windows/ReactUWPTestApp/Assets/TreeDump/ImageWithoutBorder.treedump new file mode 100644 index 00000000000..2140772973a --- /dev/null +++ b/packages/E2ETest/windows/ReactUWPTestApp/Assets/TreeDump/ImageWithoutBorder.treedump @@ -0,0 +1,237 @@ +[Windows.UI.Xaml.Controls.Grid] + Padding=0,0,0,0 + CornerRadius=0,0,0,0 + BorderThickness=0,0,0,0 + BorderBrush=[NULL] + Background=[NULL] + VerticalAlignment=Stretch + Margin=0,0,0,0 + HorizontalAlignment=Stretch + Opacity=1 + Clip=[NULL] + Visibility=Visible + RenderSize=1024,768 + ActualOffset=<0, 0, 0> + [react.uwp.ViewPanel] + CornerRadius=0,0,0,0 + BorderThickness=0,0,0,0 + BorderBrush=#00000000 + Background=[NULL] + Width=1024 + VerticalAlignment=Stretch + Margin=0,0,0,0 + HorizontalAlignment=Stretch + Height=768 + Opacity=1 + Clip=[NULL] + Visibility=Visible + RenderSize=1024,768 + ActualOffset=<0, 0, 0> + [react.uwp.ViewPanel] + CornerRadius=0,0,0,0 + BorderThickness=0,0,0,0 + BorderBrush=#00000000 + Background=[NULL] + Width=1024 + VerticalAlignment=Stretch + Margin=0,0,0,0 + HorizontalAlignment=Stretch + Height=768 + Opacity=1 + Clip=[NULL] + Visibility=Visible + RenderSize=1024,768 + ActualOffset=<0, 0, 0> + [react.uwp.ViewPanel] + CornerRadius=0,0,0,0 + BorderThickness=0,0,0,0 + BorderBrush=#00000000 + Background=[NULL] + Width=1024 + VerticalAlignment=Stretch + Margin=0,0,0,0 + HorizontalAlignment=Stretch + Height=768 + Opacity=1 + Clip=[NULL] + Visibility=Visible + RenderSize=1024,768 + ActualOffset=<0, 0, 0> + [react.uwp.ViewPanel] + CornerRadius=0,0,0,0 + BorderThickness=0,0,0,1 + BorderBrush=#00000000 + Background=[NULL] + Width=1024 + VerticalAlignment=Stretch + Margin=0,0,0,0 + HorizontalAlignment=Stretch + Height=41 + Opacity=1 + Clip=[NULL] + Visibility=Visible + RenderSize=1024,41 + ActualOffset=<0, 0, 0> + [react.uwp.ViewPanel] + CornerRadius=0,0,0,0 + BorderThickness=0,0,0,0 + BorderBrush=#00000000 + Background=[NULL] + Width=1024 + VerticalAlignment=Stretch + Margin=0,0,0,0 + HorizontalAlignment=Stretch + Height=40 + Opacity=1 + Clip=[NULL] + Visibility=Visible + RenderSize=1024,40 + ActualOffset=<0, 0, 0> + [react.uwp.ViewPanel] + CornerRadius=0,0,0,0 + BorderThickness=0,0,0,0 + BorderBrush=#00000000 + Background=[NULL] + Width=1024 + VerticalAlignment=Stretch + Margin=0,0,0,0 + HorizontalAlignment=Stretch + Height=26 + Opacity=1 + Clip=[NULL] + Visibility=Visible + RenderSize=1024,26 + ActualOffset=<0, 7, 0> + [Windows.UI.Xaml.Controls.TextBlock] + Padding=0,0,0,0 + Foreground=#FF000000 + Width=1024 + VerticalAlignment=Stretch + Margin=0,0,0,0 + HorizontalAlignment=Stretch + Height=26 + Opacity=1 + Clip=[NULL] + Visibility=Visible + RenderSize=1024,26 + ActualOffset=<0, 0, 0> + [react.uwp.ViewControl] + Padding=0,0,0,0 + Foreground=#FF000000 + BorderThickness=0,0,0,0 + BorderBrush=[NULL] + Background=[NULL] + CornerRadius=0,0,0,0 + Width=55 + VerticalAlignment=Stretch + Margin=0,0,0,0 + HorizontalAlignment=Stretch + Height=40 + Opacity=1 + Clip=[NULL] + Visibility=Visible + RenderSize=55,40 + ActualOffset=<0, 0, 0> + [Windows.UI.Xaml.Controls.ContentPresenter] + Foreground=#FF000000 + Padding=0,0,0,0 + CornerRadius=0,0,0,0 + BorderThickness=0,0,0,0 + BorderBrush=[NULL] + Background=[NULL] + VerticalAlignment=Stretch + Margin=0,0,0,0 + HorizontalAlignment=Stretch + Opacity=1 + Clip=[NULL] + Visibility=Visible + RenderSize=55,40 + ActualOffset=<0, 0, 0> + [react.uwp.ViewPanel] + CornerRadius=0,0,0,0 + BorderThickness=0,0,0,0 + BorderBrush=#00000000 + Background=[NULL] + Width=55 + VerticalAlignment=Stretch + Margin=0,0,0,0 + HorizontalAlignment=Stretch + Height=40 + Opacity=1 + Clip=[NULL] + Visibility=Visible + RenderSize=55,40 + ActualOffset=<0, 0, 0> + [Windows.UI.Xaml.Controls.Border] + Padding=0,0,0,0 + CornerRadius=2,2,2,2 + BorderThickness=0,0,0,0 + BorderBrush=[NULL] + Background=#FF2196F3 + Width=55 + VerticalAlignment=Stretch + Margin=0,0,0,0 + HorizontalAlignment=Stretch + Height=35 + Opacity=1 + Clip=[NULL] + Visibility=Visible + RenderSize=55,35 + ActualOffset=<0, 0, 0> + [react.uwp.ViewPanel] + CornerRadius=2,2,2,2 + BorderThickness=0,0,0,0 + BorderBrush=#00000000 + Background=[NULL] + VerticalAlignment=Stretch + Margin=0,0,0,0 + HorizontalAlignment=Stretch + Opacity=1 + Clip=[NULL] + Visibility=Visible + RenderSize=55,35 + ActualOffset=<0, 0, 0> + [Windows.UI.Xaml.Controls.TextBlock] + Padding=8,8,8,8 + Foreground=#FFFFFFFF + Width=55 + VerticalAlignment=Stretch + Margin=0,0,0,0 + HorizontalAlignment=Stretch + Height=35 + Opacity=1 + Clip=[NULL] + Visibility=Visible + RenderSize=55,35 + ActualOffset=<0, 0, 0> + [react.uwp.ViewPanel] + CornerRadius=0,0,0,0 + BorderThickness=0,0,0,0 + BorderBrush=#00000000 + Background=[NULL] + Width=450 + VerticalAlignment=Stretch + Margin=0,0,0,0 + HorizontalAlignment=Stretch + Height=220 + Opacity=1 + Clip=[NULL] + Visibility=Visible + RenderSize=450,220 + ActualOffset=<0, 41, 0> + [react.uwp.ViewPanel] + CornerRadius=0,0,0,0 + BorderThickness=0,0,0,0 + BorderBrush=#00000000 + Background=#FFFFA500 + Width=400 + VerticalAlignment=Stretch + Margin=0,0,0,0 + HorizontalAlignment=Stretch + Height=200 + Opacity=1 + Clip=[NULL] + Visibility=Visible + RenderSize=400,200 + ActualOffset=<0, 5, 0> + [Windows.UI.Xaml.DependencyObject] From 71fba93e87694ffa47900b11aa99aa38017354ae Mon Sep 17 00:00:00 2001 From: Di Da Date: Mon, 9 Dec 2019 11:04:36 -0800 Subject: [PATCH 04/24] Update plus Image border fix and tests --- packages/E2ETest/app/Consts.ts | 3 +- packages/E2ETest/app/ImageTestPage.tsx | 26 +- packages/E2ETest/wdio/pages/ImageTestPage.ts | 12 +- packages/E2ETest/wdio/test/Image.spec.ts | 9 + .../Assets/TreeDump/ImageWithBorder.treedump | 299 ++++++++++++++++++ .../TreeDump/ImageWithoutBorder.treedump | 128 ++++++-- .../ReactUWPTestApp/ReactUWPTestApp.csproj | 5 +- .../TreeDumpControlViewManager.cs | 63 ++-- .../TreeDumpLibrary/VisualTreeDumper.cs | 4 +- packages/playground/Samples/image.tsx | 23 +- vnext/ReactUWP/Utils/PropertyUtils.h | 35 +- .../ReactUWP/Views/Image/ImageViewManager.cpp | 50 ++- vnext/ReactUWP/Views/Image/ImageViewManager.h | 4 +- 13 files changed, 558 insertions(+), 103 deletions(-) create mode 100644 packages/E2ETest/windows/ReactUWPTestApp/Assets/TreeDump/ImageWithBorder.treedump diff --git a/packages/E2ETest/app/Consts.ts b/packages/E2ETest/app/Consts.ts index fbd72ac50c4..a4cc93097d7 100644 --- a/packages/E2ETest/app/Consts.ts +++ b/packages/E2ETest/app/Consts.ts @@ -4,6 +4,7 @@ export const APP_NAME = 'ReactUWPTestApp'; export const SEARCH_BUTTON = 'SearchButton'; export const HOME_BUTTON = '_HomeButton'; export const REACT_CONTROL_ERROR_TEST_ID = 'ReactControlErrorMessage'; +export const TREE_DUMP_RESULT = 'TreeDump'; // UnknownTestPage export const UNKNOWN_TESTPAGE = 'UnknownTestPage'; @@ -35,4 +36,4 @@ export const MEASURE_IN_WINDOW_RESULT = 'MeasureInWindowResult'; // Image Test Page export const IMAGE_TESTPAGE = 'ImageTestPage'; export const IMAGE_CHANGE_BORDER = 'ChangeBorder'; -export const IMAGE_TREE_DUMP_RESULT = 'TreeDump'; +export const SHOW_IMAGE_BORDER = 'BorderButton'; diff --git a/packages/E2ETest/app/ImageTestPage.tsx b/packages/E2ETest/app/ImageTestPage.tsx index e27b4911af5..a6851d11031 100644 --- a/packages/E2ETest/app/ImageTestPage.tsx +++ b/packages/E2ETest/app/ImageTestPage.tsx @@ -3,9 +3,9 @@ * Licensed under the MIT License. */ -import {StyleSheet, View, Image, requireNativeComponent} from 'react-native' -import React from 'react'; -import { IMAGE_TREE_DUMP_RESULT } from './Consts'; +import {StyleSheet, View, Image, Button, requireNativeComponent} from 'react-native' +import React, { useState } from 'react'; +import { TREE_DUMP_RESULT, SHOW_IMAGE_BORDER } from './Consts'; const TreeDumpControl = requireNativeComponent('TreeDumpControl'); const styles = StyleSheet.create({ @@ -37,26 +37,34 @@ const styles = StyleSheet.create({ imageContainer: { marginTop: 5, height:200, - width:400, + width:450, backgroundColor: 'orange', }, treeDumpControl: { - height: 90, - width: 400, + height: 150, + width: 450, margin: 10, }, }); export function ImageTestPage() { + const [imageWithBorder, setImageBorder] = useState(false); + const onOressBorder = () => { + var previousImageBorderState = imageWithBorder; + setImageBorder(!previousImageBorderState); + } return( - - + +