Browse Source

Merge branch 'master' into fixes/treeview-tab-navigation-nre

pull/4969/head
Dariusz Komosiński 6 years ago
committed by GitHub
parent
commit
5637b9bc76
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      .editorconfig
  2. 4
      build.ps1
  3. 2
      build.sh
  4. 16
      nukebuild/Build.cs
  5. 2
      nukebuild/BuildParameters.cs
  6. 84
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs
  7. 41
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/FramePresenter.tsx
  8. 11
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/InputEventMessageBase.ts
  9. 9
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/InputModifiers.ts
  10. 6
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/MouseButton.ts
  11. 39
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/MouseEventHelpers.ts
  12. 13
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerEventMessageBase.ts
  13. 12
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerMovedEventMessage.ts
  14. 17
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerPressedEventMessage.ts
  15. 17
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerReleasedEventMessage.ts
  16. 17
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/ScrollEventMessage.ts
  17. 6
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/PreviewerServerConnection.ts
  18. 23
      src/Avalonia.ReactiveUI/ReactiveUserControl.cs
  19. 31
      src/Avalonia.ReactiveUI/ReactiveWindow.cs
  20. 55
      src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs
  21. 61
      tests/Avalonia.DesignerSupport.Tests/DesignerSupportTests.cs
  22. 3
      tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/.gitignore
  23. 94
      tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/Models/InputEventTests.ts
  24. 2414
      tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json
  25. 26
      tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json
  26. 12
      tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/tsconfig.json
  27. 71
      tests/Avalonia.ReactiveUI.UnitTests/ReactiveUserControlTest.cs

3
.editorconfig

@ -156,6 +156,9 @@ indent_size = 2
[*.{props,targets,config,nuspec}]
indent_size = 2
[*.json]
indent_size = 2
# Shell scripts
[*.sh]
end_of_line = lf

4
build.ps1

@ -43,7 +43,7 @@ if (Test-Path $DotNetGlobalFile) {
}
# If dotnet is installed locally, and expected version is not set or installation matches the expected version
if ((Get-Command "dotnet" -ErrorAction SilentlyContinue) -ne $null -and `
if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and `
(!(Test-Path variable:DotNetVersion) -or $(& dotnet --version) -eq $DotNetVersion)) {
$env:DOTNET_EXE = (Get-Command "dotnet").Path
}
@ -53,7 +53,7 @@ else {
# Download install script
$DotNetInstallFile = "$TempDirectory\dotnet-install.ps1"
md -force $TempDirectory > $null
mkdir -force $TempDirectory > $null
(New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile)
# Install by channel or version

2
build.sh

@ -47,7 +47,7 @@ if [ -f "$DOTNET_GLOBAL_FILE" ]; then
fi
# If dotnet is installed locally, and expected version is not set or installation matches the expected version
if [[ -x "$(command -v dotnet)" && (-z ${DOTNET_VERSION+x} || $(dotnet --version) == "$DOTNET_VERSION") ]]; then
if [[ -x "$(command -v dotnet)" && (-z ${DOTNET_VERSION+x} || $(dotnet --version) == "$DOTNET_VERSION") || "$SKIP_DOTNET_DOWNLOAD" == "1" ]]; then
export DOTNET_EXE="$(command -v dotnet)"
else
DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix"

16
nukebuild/Build.cs

@ -233,6 +233,21 @@ partial class Build : NukeBuild
}
}
Target RunHtmlPreviewerTests => _ => _
.DependsOn(CompileHtmlPreviewer)
.OnlyWhenStatic(() => !(Parameters.SkipPreviewer || Parameters.SkipTests))
.Executes(() =>
{
var webappTestDir = RootDirectory / "tests" / "Avalonia.DesignerSupport.Tests" / "Remote" / "HtmlTransport" / "webapp";
NpmTasks.NpmInstall(c => c
.SetWorkingDirectory(webappTestDir)
.SetArgumentConfigurator(a => a.Add("--silent")));
NpmTasks.NpmRun(c => c
.SetWorkingDirectory(webappTestDir)
.SetCommand("test"));
});
Target RunCoreLibsTests => _ => _
.OnlyWhenStatic(() => !Parameters.SkipTests)
.DependsOn(Compile)
@ -332,6 +347,7 @@ partial class Build : NukeBuild
.DependsOn(RunCoreLibsTests)
.DependsOn(RunRenderTests)
.DependsOn(RunDesignerTests)
.DependsOn(RunHtmlPreviewerTests)
.DependsOn(RunLeakTests);
Target Package => _ => _

2
nukebuild/BuildParameters.cs

@ -62,7 +62,7 @@ public partial class Build
public AbsolutePath ZipTargetControlCatalogDesktopDir { get; }
public BuildParameters(Build b)
public BuildParameters(Build b)
{
// ARGUMENTS
Configuration = b.Configuration ?? "Release";

84
src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
@ -9,6 +10,7 @@ using System.Threading;
using System.Threading.Tasks;
using Avalonia.Remote.Protocol;
using Avalonia.Remote.Protocol.Viewport;
using InputProtocol = Avalonia.Remote.Protocol.Input;
namespace Avalonia.DesignerSupport.Remote.HtmlTransport
{
@ -115,14 +117,11 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport
while (true)
{
var msg = await socket.ReceiveMessage().ConfigureAwait(false);
if(msg == null)
return;
if (msg.IsText)
if(msg != null && msg.IsText)
{
var s = Encoding.UTF8.GetString(msg.Data);
var parts = s.Split(':');
if (parts[0] == "frame-received")
_onMessage?.Invoke(this, new FrameReceivedMessage { SequenceId = long.Parse(parts[1]) });
var message = ParseMessage(msg.AsString());
if (message != null)
_onMessage?.Invoke(this, message);
}
}
}
@ -181,7 +180,6 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport
_pendingSocket?.Dispose();
_simpleServer.Dispose();
}
public Task Send(object data)
{
@ -264,5 +262,75 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport
_onException?.Invoke(this, ex);
}
#endregion
private static object ParseMessage(string message)
{
var parts = message.Split(':');
var key = parts[0];
if (key.Equals("frame-received", StringComparison.OrdinalIgnoreCase))
{
return new FrameReceivedMessage { SequenceId = long.Parse(parts[1]) };
}
else if (key.Equals("pointer-released", StringComparison.OrdinalIgnoreCase))
{
return new InputProtocol.PointerReleasedEventMessage
{
Modifiers = ParseInputModifiers(parts[1]),
X = ParseDouble(parts[2]),
Y = ParseDouble(parts[3]),
Button = ParseMouseButton(parts[4]),
};
}
else if (key.Equals("pointer-pressed", StringComparison.OrdinalIgnoreCase))
{
return new InputProtocol.PointerPressedEventMessage
{
Modifiers = ParseInputModifiers(parts[1]),
X = ParseDouble(parts[2]),
Y = ParseDouble(parts[3]),
Button = ParseMouseButton(parts[4]),
};
}
else if (key.Equals("pointer-moved", StringComparison.OrdinalIgnoreCase))
{
return new InputProtocol.PointerMovedEventMessage
{
Modifiers = ParseInputModifiers(parts[1]),
X = ParseDouble(parts[2]),
Y = ParseDouble(parts[3]),
};
}
else if (key.Equals("scroll", StringComparison.OrdinalIgnoreCase))
{
return new InputProtocol.ScrollEventMessage
{
Modifiers = ParseInputModifiers(parts[1]),
X = ParseDouble(parts[2]),
Y = ParseDouble(parts[3]),
DeltaX = ParseDouble(parts[4]),
DeltaY = ParseDouble(parts[5]),
};
}
return null;
}
private static InputProtocol.InputModifiers[] ParseInputModifiers(string modifiersText) =>
string.IsNullOrWhiteSpace(modifiersText)
? null
: modifiersText
.Split(',')
.Select(x => (InputProtocol.InputModifiers)Enum.Parse(
typeof(InputProtocol.InputModifiers), x, true))
.ToArray();
private static InputProtocol.MouseButton ParseMouseButton(string buttonText) =>
string.IsNullOrWhiteSpace(buttonText)
? InputProtocol.MouseButton.None
: (InputProtocol.MouseButton)Enum.Parse(
typeof(InputProtocol.MouseButton), buttonText, true);
private static double ParseDouble(string text) =>
double.Parse(text, NumberStyles.Float, CultureInfo.InvariantCulture);
}
}

41
src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/FramePresenter.tsx

@ -1,5 +1,9 @@
import {PreviewerFrame, PreviewerServerConnection} from "src/PreviewerServerConnection";
import * as React from "react";
import {PreviewerFrame, PreviewerServerConnection} from "src/PreviewerServerConnection";
import {PointerPressedEventMessage} from "src/Models/Input/PointerPressedEventMessage";
import {PointerReleasedEventMessage} from "src/Models/Input/PointerReleasedEventMessage";
import {PointerMovedEventMessage} from "src/Models/Input/PointerMovedEventMessage";
import {ScrollEventMessage} from "src/Models/Input/ScrollEventMessage";
interface PreviewerPresenterProps {
conn: PreviewerServerConnection;
@ -15,6 +19,11 @@ export class PreviewerPresenter extends React.Component<PreviewerPresenterProps>
this.componentDidUpdate({
conn: null!
}, this.state);
this.handleMouseDown = this.handleMouseDown.bind(this);
this.handleMouseUp = this.handleMouseUp.bind(this);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.handleWheel = this.handleWheel.bind(this);
}
componentDidMount(): void {
@ -51,7 +60,35 @@ export class PreviewerPresenter extends React.Component<PreviewerPresenterProps>
}
}
handleMouseDown(e: React.MouseEvent) {
e.preventDefault();
const pointerPressedEventMessage = new PointerPressedEventMessage(e);
this.props.conn.sendMouseEvent(pointerPressedEventMessage);
}
handleMouseUp(e: React.MouseEvent) {
e.preventDefault();
const pointerReleasedEventMessage = new PointerReleasedEventMessage(e);
this.props.conn.sendMouseEvent(pointerReleasedEventMessage);
}
handleMouseMove(e: React.MouseEvent) {
e.preventDefault();
const pointerMovedEventMessage = new PointerMovedEventMessage(e);
this.props.conn.sendMouseEvent(pointerMovedEventMessage);
}
handleWheel(e: React.WheelEvent) {
e.preventDefault();
const scrollEventMessage = new ScrollEventMessage(e);
this.props.conn.sendMouseEvent(scrollEventMessage);
}
render() {
return <canvas ref={this.canvasRef}/>
return <canvas ref={this.canvasRef}
onMouseDown={this.handleMouseDown}
onMouseUp={this.handleMouseUp}
onMouseMove={this.handleMouseMove}
onWheel={this.handleWheel} />
}
}

11
src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/InputEventMessageBase.ts

@ -0,0 +1,11 @@
import * as React from "react";
import {InputModifiers} from "./InputModifiers";
import {getModifiers} from "./MouseEventHelpers";
export abstract class InputEventMessageBase {
public readonly modifiers : Array<InputModifiers>;
protected constructor(e: React.MouseEvent) {
this.modifiers = getModifiers(e);
}
}

9
src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/InputModifiers.ts

@ -0,0 +1,9 @@
export enum InputModifiers {
Alt,
Control,
Shift,
Windows,
LeftMouseButton,
RightMouseButton,
MiddleMouseButton,
}

6
src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/MouseButton.ts

@ -0,0 +1,6 @@
export enum MouseButton {
None,
Left,
Right,
Middle,
}

39
src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/MouseEventHelpers.ts

@ -0,0 +1,39 @@
import * as React from "react";
import {InputModifiers} from "./InputModifiers";
import {MouseButton} from "./MouseButton";
export function getModifiers(e: React.MouseEvent): Array<InputModifiers> {
let modifiers : Array<InputModifiers> = [];
if (e.altKey)
modifiers.push(InputModifiers.Alt);
if (e.ctrlKey)
modifiers.push(InputModifiers.Control);
if (e.shiftKey)
modifiers.push(InputModifiers.Shift);
if (e.metaKey)
modifiers.push(InputModifiers.Windows);
if (e.buttons != 0) {
if ((e.buttons & 1) != 0)
modifiers.push(InputModifiers.LeftMouseButton);
if ((e.buttons & 2) != 0)
modifiers.push(InputModifiers.RightMouseButton);
if ((e.buttons & 4) != 0)
modifiers.push(InputModifiers.MiddleMouseButton);
}
return modifiers;
}
export function getMouseButton(e: React.MouseEvent) : MouseButton {
if (e.button == 0) {
return MouseButton.Left;
} else if (e.button == 1) {
return MouseButton.Middle;
} else if (e.button == 2) {
return MouseButton.Right;
} else {
return MouseButton.None;
}
}

13
src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerEventMessageBase.ts

@ -0,0 +1,13 @@
import * as React from "react";
import {InputEventMessageBase} from "./InputEventMessageBase";
export abstract class PointerEventMessageBase extends InputEventMessageBase {
public readonly x: number;
public readonly y: number;
protected constructor(e: React.MouseEvent) {
super(e);
this.x = e.clientX;
this.y = e.clientY;
}
}

12
src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerMovedEventMessage.ts

@ -0,0 +1,12 @@
import * as React from "react";
import {PointerEventMessageBase} from "./PointerEventMessageBase";
export class PointerMovedEventMessage extends PointerEventMessageBase {
constructor(e: React.MouseEvent) {
super(e);
}
public toString = () : string => {
return `pointer-moved:${this.modifiers}:${this.x}:${this.y}`;
}
}

17
src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerPressedEventMessage.ts

@ -0,0 +1,17 @@
import * as React from "react";
import {PointerEventMessageBase} from "./PointerEventMessageBase";
import {MouseButton} from "./MouseButton";
import {getMouseButton} from "./MouseEventHelpers";
export class PointerPressedEventMessage extends PointerEventMessageBase {
public readonly button: MouseButton
constructor(e: React.MouseEvent) {
super(e);
this.button = getMouseButton(e);
}
public toString = () : string => {
return `pointer-pressed:${this.modifiers}:${this.x}:${this.y}:${this.button}`;
}
}

17
src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerReleasedEventMessage.ts

@ -0,0 +1,17 @@
import * as React from "react";
import {PointerEventMessageBase} from "./PointerEventMessageBase";
import {MouseButton} from "./MouseButton";
import {getMouseButton} from "./MouseEventHelpers";
export class PointerReleasedEventMessage extends PointerEventMessageBase {
public readonly button: MouseButton
constructor(e: React.MouseEvent) {
super(e);
this.button = getMouseButton(e);
}
public toString = () : string => {
return `pointer-released:${this.modifiers}:${this.x}:${this.y}:${this.button}`;
}
}

17
src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/ScrollEventMessage.ts

@ -0,0 +1,17 @@
import * as React from "react";
import {PointerEventMessageBase} from "./PointerEventMessageBase";
export class ScrollEventMessage extends PointerEventMessageBase {
public readonly deltaX: number;
public readonly deltaY: number;
constructor(e: React.WheelEvent) {
super(e);
this.deltaX = -e.deltaX;
this.deltaY = -e.deltaY;
}
public toString = () : string => {
return `scroll:${this.modifiers}:${this.x}:${this.y}:${this.deltaX}:${this.deltaY}`;
}
}

6
src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/PreviewerServerConnection.ts

@ -1,3 +1,5 @@
import { InputEventMessageBase } from "src/Models/Input/InputEventMessageBase";
export interface PreviewerFrame {
data: ImageData;
dpiX: number;
@ -28,6 +30,10 @@ export class PreviewerServerConnection {
this.handlers.delete(listener);
}
public sendMouseEvent(message: InputEventMessageBase) {
this.conn.send(message.toString());
}
constructor(uri: string) {
this.currentFrame = null;
var conn = this.conn = new WebSocket(uri);

23
src/Avalonia.ReactiveUI/ReactiveUserControl.cs

@ -28,11 +28,7 @@ namespace Avalonia.ReactiveUI
// This WhenActivated block calls ViewModel's WhenActivated
// block if the ViewModel implements IActivatableViewModel.
this.WhenActivated(disposables => { });
this.ObservableForProperty(x => x.ViewModel, false, true)
.Subscribe(args => DataContext = args.Value);
this.ObservableForProperty(x => x.DataContext, false, true)
.Subscribe(args => ViewModel = args.Value as TViewModel);
this.GetObservable(ViewModelProperty).Subscribe(OnViewModelChanged);
}
/// <summary>
@ -49,5 +45,22 @@ namespace Avalonia.ReactiveUI
get => ViewModel;
set => ViewModel = (TViewModel)value;
}
protected override void OnDataContextChanged(EventArgs e)
{
ViewModel = DataContext as TViewModel;
}
private void OnViewModelChanged(object value)
{
if (value == null)
{
ClearValue(DataContextProperty);
}
else if (DataContext != value)
{
DataContext = value;
}
}
}
}

31
src/Avalonia.ReactiveUI/ReactiveWindow.cs

@ -28,11 +28,8 @@ namespace Avalonia.ReactiveUI
// This WhenActivated block calls ViewModel's WhenActivated
// block if the ViewModel implements IActivatableViewModel.
this.WhenActivated(disposables => { });
this.ObservableForProperty(x => x.ViewModel, false, true)
.Subscribe(args => DataContext = args.Value);
this.ObservableForProperty(x => x.DataContext, false, true)
.Subscribe(args => ViewModel = args.Value as TViewModel);
this.GetObservable(DataContextProperty).Subscribe(OnDataContextChanged);
this.GetObservable(ViewModelProperty).Subscribe(OnViewModelChanged);
}
/// <summary>
@ -49,5 +46,29 @@ namespace Avalonia.ReactiveUI
get => ViewModel;
set => ViewModel = (TViewModel)value;
}
private void OnDataContextChanged(object value)
{
if (value is TViewModel viewModel)
{
ViewModel = viewModel;
}
else
{
ViewModel = null;
}
}
private void OnViewModelChanged(object value)
{
if (value == null)
{
ClearValue(DataContextProperty);
}
else if (DataContext != value)
{
DataContext = value;
}
}
}
}

55
src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs

@ -26,6 +26,7 @@ namespace Avalonia.Markup.Xaml.XamlIl
{
#if !RUNTIME_XAML_CECIL
private static SreTypeSystem _sreTypeSystem;
private static Type _ignoresAccessChecksFromAttribute;
private static ModuleBuilder _sreBuilder;
private static IXamlType _sreContextType;
private static XamlLanguageTypeMappings _sreMappings;
@ -94,8 +95,60 @@ namespace Avalonia.Markup.Xaml.XamlIl
_sreTypeSystem.CreateTypeBuilder(
_sreBuilder.DefineType("XamlIlContext")), _sreTypeSystem, _sreMappings,
_sreEmitMappings);
if (_ignoresAccessChecksFromAttribute == null)
_ignoresAccessChecksFromAttribute = EmitIgnoresAccessCheckAttributeDefinition(_sreBuilder);
}
static Type EmitIgnoresAccessCheckAttributeDefinition(ModuleBuilder builder)
{
var tb = builder.DefineType("System.Runtime.CompilerServices.IgnoresAccessChecksToAttribute",
TypeAttributes.Class | TypeAttributes.Public, typeof(Attribute));
var field = tb.DefineField("_name", typeof(string), FieldAttributes.Private);
var propGet = tb.DefineMethod("get_AssemblyName", MethodAttributes.Public, typeof(string),
Array.Empty<Type>());
var propGetIl = propGet.GetILGenerator();
propGetIl.Emit(OpCodes.Ldarg_0);
propGetIl.Emit(OpCodes.Ldfld, field);
propGetIl.Emit(OpCodes.Ret);
var prop = tb.DefineProperty("AssemblyName", PropertyAttributes.None, typeof(string), Array.Empty<Type>());
prop.SetGetMethod(propGet);
var ctor = tb.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard,
new[] { typeof(string) });
var ctorIl = ctor.GetILGenerator();
ctorIl.Emit(OpCodes.Ldarg_0);
ctorIl.Emit(OpCodes.Ldarg_1);
ctorIl.Emit(OpCodes.Stfld, field);
ctorIl.Emit(OpCodes.Ldarg_0);
ctorIl.Emit(OpCodes.Call, typeof(Attribute)
.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.First(x => x.GetParameters().Length == 0));
ctorIl.Emit(OpCodes.Ret);
tb.SetCustomAttribute(new CustomAttributeBuilder(
typeof(AttributeUsageAttribute).GetConstructor(new[] { typeof(AttributeTargets) }),
new object[] { AttributeTargets.Assembly },
new[] { typeof(AttributeUsageAttribute).GetProperty("AllowMultiple") },
new object[] { true }));
return tb.CreateTypeInfo();
}
static void EmitIgnoresAccessCheckToAttribute(AssemblyName assemblyName)
{
var name = assemblyName.Name;
if(string.IsNullOrWhiteSpace(name))
return;
var key = assemblyName.GetPublicKey();
if (key != null && key.Length != 0)
name += ", PublicKey=" + BitConverter.ToString(key).Replace("-", "").ToUpperInvariant();
_sreAsm.SetCustomAttribute(new CustomAttributeBuilder(
_ignoresAccessChecksFromAttribute.GetConstructors()[0],
new object[] { name }));
}
static object LoadSre(string xaml, Assembly localAssembly, object rootInstance, Uri uri, bool isDesignMode)
{
@ -118,6 +171,8 @@ namespace Avalonia.Markup.Xaml.XamlIl
{
InitializeSre();
if (localAssembly?.GetName() != null)
EmitIgnoresAccessCheckToAttribute(localAssembly.GetName());
var asm = localAssembly == null ? null : _sreTypeSystem.GetAssembly(localAssembly);
var tb = _sreBuilder.DefineType("Builder_" + Guid.NewGuid().ToString("N") + "_" + uri);
var clrPropertyBuilder = tb.DefineNestedType("ClrProperties_" + Guid.NewGuid().ToString("N"));

61
tests/Avalonia.DesignerSupport.Tests/DesignerSupportTests.cs

@ -11,6 +11,7 @@ using System.Threading;
using System.Threading.Tasks;
using Avalonia.Remote.Protocol;
using Avalonia.Remote.Protocol.Designer;
using Avalonia.Remote.Protocol.Viewport;
using Xunit;
using Xunit.Extensions;
@ -31,19 +32,38 @@ namespace Avalonia.DesignerSupport.Tests
@"..\..\..\..\..\tests/Avalonia.DesignerSupport.TestApp/bin/$BUILD/netcoreapp3.1/",
"Avalonia.DesignerSupport.TestApp",
"Avalonia.DesignerSupport.TestApp.dll",
@"..\..\..\..\..\tests\Avalonia.DesignerSupport.TestApp\MainWindow.xaml"),
@"..\..\..\..\..\tests\Avalonia.DesignerSupport.TestApp\MainWindow.xaml",
"win32"),
InlineData(
@"..\..\..\..\..\samples\ControlCatalog.NetCore\bin\$BUILD\netcoreapp3.1\",
"ControlCatalog.NetCore",
"ControlCatalog.dll",
@"..\..\..\..\..\samples\ControlCatalog\MainWindow.xaml")]
@"..\..\..\..\..\samples\ControlCatalog\MainWindow.xaml",
"win32"),
InlineData(
@"..\..\..\..\..\tests/Avalonia.DesignerSupport.TestApp/bin/$BUILD/netcoreapp3.1/",
"Avalonia.DesignerSupport.TestApp",
"Avalonia.DesignerSupport.TestApp.dll",
@"..\..\..\..\..\tests\Avalonia.DesignerSupport.TestApp\MainWindow.xaml",
"avalonia-remote"),
InlineData(
@"..\..\..\..\..\samples\ControlCatalog.NetCore\bin\$BUILD\netcoreapp3.1\",
"ControlCatalog.NetCore",
"ControlCatalog.dll",
@"..\..\..\..\..\samples\ControlCatalog\MainWindow.xaml",
"avalonia-remote")]
public async Task Designer_In_Win32_Mode_Should_Provide_Valid_Hwnd(
string outputDir,
string executableName,
string assemblyName,
string xamlFile)
string xamlFile,
string method)
{
Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows));
outputDir = Path.GetFullPath(outputDir.Replace('\\', Path.DirectorySeparatorChar));
xamlFile = Path.GetFullPath(xamlFile.Replace('\\', Path.DirectorySeparatorChar));
if (method == "win32")
Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows));
var xaml = File.ReadAllText(xamlFile);
string buildType;
@ -56,6 +76,8 @@ namespace Avalonia.DesignerSupport.Tests
var sessionId = Guid.NewGuid();
long handle = 0;
bool success = false;
string error = null;
var resultMessageReceivedToken = new CancellationTokenSource();
@ -71,6 +93,18 @@ namespace Avalonia.DesignerSupport.Tests
if (msg is StartDesignerSessionMessage start)
{
Assert.Equal(sessionId, Guid.Parse(start.SessionId));
if (method == "avalonia-remote")
{
await conn.Send(new ClientSupportedPixelFormatsMessage
{
Formats = new[] { PixelFormat.Rgba8888 }
});
await conn.Send(new ClientViewportAllocatedMessage
{
DpiX = 96, DpiY = 96, Width = 1024, Height = 768
});
}
await conn.Send(new UpdateXamlMessage
{
AssemblyPath = Path.Combine(outputDir, assemblyName),
@ -80,8 +114,14 @@ namespace Avalonia.DesignerSupport.Tests
else if (msg is UpdateXamlResultMessage result)
{
if (result.Error != null)
{
error = result.Error;
outputHelper.WriteLine(result.Error);
handle = result.Handle != null ? long.Parse(result.Handle) : 0;
}
else
success = true;
if (method == "win32")
handle = result.Handle != null ? long.Parse(result.Handle) : 0;
resultMessageReceivedToken.Cancel();
conn.Dispose();
}
@ -91,7 +131,7 @@ namespace Avalonia.DesignerSupport.Tests
var cmdline =
$"exec --runtimeconfig \"{outputDir}{executableName}.runtimeconfig.json\" --depsfile \"{outputDir}{executableName}.deps.json\" "
+ $" \"{DesignerAppPath.Replace("$BUILD", buildType)}\" "
+ $"--transport tcp-bson://127.0.0.1:{port}/ --session-id {sessionId} --method win32 \"{outputDir}{executableName}.dll\"";
+ $"--transport tcp-bson://127.0.0.1:{port}/ --session-id {sessionId} --method {method} \"{outputDir}{executableName}.dll\"";
using (var proc = new Process
{
@ -128,10 +168,15 @@ namespace Avalonia.DesignerSupport.Tests
}
proc.WaitForExit();
var stdout = proc.StandardOutput.ReadToEnd();
var stderr = proc.StandardError.ReadToEnd();
Assert.True(cancelled,
$"Message Not Received.\n" + proc.StandardOutput.ReadToEnd() + "\n" +
proc.StandardError.ReadToEnd());
Assert.NotEqual(0, handle);
stderr + "\n" + stdout);
Assert.True(success, error);
if (method == "win32")
Assert.NotEqual(0, handle);
}
}

3
tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/.gitignore

@ -0,0 +1,3 @@
build
node_modules
.nyc_output

94
tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/Models/InputEventTests.ts

@ -0,0 +1,94 @@
import { describe } from 'mocha';
import { expect } from 'chai';
import { Mock } from "moq.ts";
import { MouseEvent, WheelEvent } from "react";
import { InputModifiers } from "../../../../../../src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/InputModifiers";
import { MouseButton } from "../../../../../../src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/MouseButton";
import { PointerMovedEventMessage } from "../../../../../../src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerMovedEventMessage";
import { PointerPressedEventMessage } from "../../../../../../src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerPressedEventMessage";
import { PointerReleasedEventMessage } from "../../../../../../src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerReleasedEventMessage";
import { ScrollEventMessage } from "../../../../../../src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/ScrollEventMessage";
import { getModifiers, getMouseButton } from '../../../../../../src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/MouseEventHelpers';
describe("Input event tests", () => {
describe("Helpers", () => {
it("getModifiers", () => {
const event = new Mock<MouseEvent>()
.setup(x => x.altKey).returns(false)
.setup(x => x.ctrlKey).returns(true)
.setup(x => x.shiftKey).returns(false)
.setup(x => x.metaKey).returns(false)
.setup(x => x.buttons).returns(1)
.object()
var actual = getModifiers(event)
expect(actual)
.eql([InputModifiers.Control, InputModifiers.LeftMouseButton])
})
it("getMouseButton", () => {
const event = new Mock<MouseEvent>()
.setup(x => x.button).returns(1)
.object()
var actual = getMouseButton(event)
expect(actual)
.equal(MouseButton.Middle)
})
})
describe("Messages", () => {
const x = .3
const y = .42
const modifiers = "0,1,2,3,4,5,6"
const button = "1"
const deltaX = -3.
const deltaY = -3.
const mouseEvent = new Mock<MouseEvent>()
.setup(x => x.altKey).returns(true)
.setup(x => x.ctrlKey).returns(true)
.setup(x => x.shiftKey).returns(true)
.setup(x => x.metaKey).returns(true)
.setup(x => x.buttons).returns(7)
.setup(x => x.button).returns(0)
.setup(x => x.clientX).returns(x)
.setup(x => x.clientY).returns(y)
.object()
it("PointerMovedEventMessage", () => {
const message = new PointerMovedEventMessage(mouseEvent)
expect(message.toString())
.equal(`pointer-moved:${modifiers}:${x}:${y}`)
})
it("PointerPressedEventMessage", () => {
const message = new PointerPressedEventMessage(mouseEvent)
expect(message.toString())
.equal(`pointer-pressed:${modifiers}:${x}:${y}:${button}`)
})
it("PointerReleasedEventMessage", () => {
const message = new PointerReleasedEventMessage(mouseEvent)
expect(message.toString())
.equal(`pointer-released:${modifiers}:${x}:${y}:${button}`)
})
it("ScrollEventMessage", () => {
const wheelEvent = new Mock<WheelEvent>()
.setup(x => x.altKey).returns(true)
.setup(x => x.ctrlKey).returns(true)
.setup(x => x.shiftKey).returns(true)
.setup(x => x.metaKey).returns(true)
.setup(x => x.buttons).returns(7)
.setup(x => x.clientX).returns(x)
.setup(x => x.clientY).returns(y)
.setup(x => x.deltaX).returns(-deltaX)
.setup(x => x.deltaY).returns(-deltaY)
.object()
const message = new ScrollEventMessage(wheelEvent)
expect(message.toString())
.equal(`scroll:${modifiers}:${x}:${y}:${deltaX}:${deltaY}`)
})
})
})

2414
tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json

File diff suppressed because it is too large

26
tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json

@ -0,0 +1,26 @@
{
"name": "simple-test",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "mocha -r ts-node/register ./**/*Tests.ts",
"coverage": "nyc -r text -e .ts -x \"./*Tests.ts\" npm run test"
},
"type": "module",
"keywords": [],
"author": "",
"license": "MIT",
"dependencies": {},
"devDependencies": {
"@types/chai": "4.2.12",
"@types/mocha": "8.0.3",
"@types/react": "^16.3.14",
"chai": "^4.2.0",
"mocha": "^8.1.3",
"moq.ts": "^6.4.0",
"nyc": "^15.1.0",
"react": "^16.3.2",
"ts-node": "^9.0.0",
"typescript": "^4.0.2"
}
}

12
tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/tsconfig.json

@ -0,0 +1,12 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"sourceMap": true,
"strict": true,
"esModuleInterop": true
},
"exclude": [
"node_modules"
]
}

71
tests/Avalonia.ReactiveUI.UnitTests/ReactiveUserControlTest.cs

@ -1,4 +1,5 @@
using System.Reactive.Disposables;
using Avalonia.Controls;
using Avalonia.UnitTests;
using ReactiveUI;
using Splat;
@ -100,5 +101,75 @@ namespace Avalonia.ReactiveUI.UnitTests
Assert.NotNull(view.DataContext);
Assert.False(view.ViewModel.IsActive);
}
[Fact]
public void Should_Inherit_DataContext()
{
var vm1 = new ExampleViewModel();
var vm2 = new ExampleViewModel();
var view = new ExampleView();
var root = new TestRoot(view);
Assert.Null(view.DataContext);
Assert.Null(view.ViewModel);
root.DataContext = vm1;
Assert.Same(vm1, view.DataContext);
Assert.Same(vm1, view.ViewModel);
root.DataContext = null;
Assert.Null(view.DataContext);
Assert.Null(view.ViewModel);
root.DataContext = vm2;
Assert.Same(vm2, view.DataContext);
Assert.Same(vm2, view.ViewModel);
}
[Fact]
public void Should_Not_Overlap_Change_Notifications()
{
var vm1 = new ExampleViewModel();
var vm2 = new ExampleViewModel();
var view1 = new ExampleView();
var view2 = new ExampleView();
Assert.Null(view1.DataContext);
Assert.Null(view2.DataContext);
Assert.Null(view1.ViewModel);
Assert.Null(view2.ViewModel);
view1.DataContext = vm1;
Assert.Same(vm1, view1.DataContext);
Assert.Same(vm1, view1.ViewModel);
Assert.Null(view2.DataContext);
Assert.Null(view2.ViewModel);
view2.DataContext = vm2;
Assert.Same(vm1, view1.DataContext);
Assert.Same(vm1, view1.ViewModel);
Assert.Same(vm2, view2.DataContext);
Assert.Same(vm2, view2.ViewModel);
view1.ViewModel = null;
Assert.Null(view1.DataContext);
Assert.Null(view1.ViewModel);
Assert.Same(vm2, view2.DataContext);
Assert.Same(vm2, view2.ViewModel);
view2.ViewModel = null;
Assert.Null(view1.DataContext);
Assert.Null(view2.DataContext);
Assert.Null(view1.ViewModel);
Assert.Null(view2.ViewModel);
}
}
}

Loading…
Cancel
Save