mirror of
https://github.com/ElectronNET/Electron.NET.git
synced 2026-02-04 05:34:51 +00:00
Add IntegrationTests project
This commit is contained in:
43
src/ElectronNET.IntegrationTests/ElectronFixture.cs
Normal file
43
src/ElectronNET.IntegrationTests/ElectronFixture.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
namespace ElectronNET.IntegrationTests
|
||||
{
|
||||
using System.Reflection;
|
||||
using ElectronNET.API;
|
||||
using ElectronNET.API.Entities;
|
||||
|
||||
// Shared fixture that starts Electron runtime once
|
||||
public class ElectronFixture : IAsyncLifetime
|
||||
{
|
||||
public BrowserWindow MainWindow { get; private set; } = null!;
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
AppDomain.CurrentDomain.SetData("ElectronTestAssembly", Assembly.GetExecutingAssembly());
|
||||
var runtimeController = ElectronNetRuntime.RuntimeController;
|
||||
await runtimeController.Start();
|
||||
await runtimeController.WaitReadyTask;
|
||||
|
||||
// create hidden window for tests (avoid showing UI)
|
||||
this.MainWindow = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions
|
||||
{
|
||||
Show = false,
|
||||
Width = 800,
|
||||
Height = 600,
|
||||
});
|
||||
|
||||
// Clear potential cache side-effects
|
||||
await this.MainWindow.WebContents.Session.ClearCacheAsync();
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
var runtimeController = ElectronNetRuntime.RuntimeController;
|
||||
await runtimeController.Stop();
|
||||
await runtimeController.WaitStoppedTask;
|
||||
}
|
||||
}
|
||||
|
||||
[CollectionDefinition("ElectronCollection")]
|
||||
public class ElectronCollection : ICollectionFixture<ElectronFixture>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- When this is enabled, the project will be switched from nuget packages to consuming the ElectronNet orchestration directly -->
|
||||
<ElectronNetDevMode>true</ElectronNetDevMode>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="..\ElectronNET\build\ElectronNET.props" Condition="$(ElectronNetDevMode)" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ElectronNET.API\ElectronNET.API.csproj" />
|
||||
<ProjectReference Include="..\ElectronNET\ElectronNET.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Disable test parallelization at runner level to avoid multiple Electron instances -->
|
||||
<ParallelizeTestCollections>false</ParallelizeTestCollections>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="..\ElectronNET\build\ElectronNET.targets" Condition="$(ElectronNetDevMode)" />
|
||||
|
||||
</Project>
|
||||
2
src/ElectronNET.IntegrationTests/GlobalUsings.cs
Normal file
2
src/ElectronNET.IntegrationTests/GlobalUsings.cs
Normal file
@@ -0,0 +1,2 @@
|
||||
global using Xunit;
|
||||
global using FluentAssertions;
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/electron-userland/electron-builder/refs/heads/master/packages/app-builder-lib/scheme.json",
|
||||
"compression": "maximum",
|
||||
"linux": {
|
||||
"target": [
|
||||
"tar.xz"
|
||||
],
|
||||
"executableArgs": [ "--no-sandbox" ],
|
||||
"artifactName": "${name}-${arch}-${version}.${ext}"
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
{
|
||||
"target": "portable",
|
||||
"arch": "x64"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
166
src/ElectronNET.IntegrationTests/Tests/AppTests.cs
Normal file
166
src/ElectronNET.IntegrationTests/Tests/AppTests.cs
Normal file
@@ -0,0 +1,166 @@
|
||||
namespace ElectronNET.IntegrationTests.Tests
|
||||
{
|
||||
using System.Runtime.InteropServices;
|
||||
using ElectronNET.API;
|
||||
using ElectronNET.API.Entities;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
[Collection("ElectronCollection")]
|
||||
public class AppTests
|
||||
{
|
||||
// ReSharper disable once NotAccessedField.Local
|
||||
private readonly ElectronFixture fx;
|
||||
public AppTests(ElectronFixture fx)
|
||||
{
|
||||
this.fx = fx;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Can_get_app_path()
|
||||
{
|
||||
var path = await Electron.App.GetAppPathAsync();
|
||||
path.Should().NotBeNullOrWhiteSpace();
|
||||
Directory.Exists(path).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Can_get_version_and_locale()
|
||||
{
|
||||
var version = await Electron.App.GetVersionAsync();
|
||||
version.Should().NotBeNullOrWhiteSpace();
|
||||
var locale = await Electron.App.GetLocaleAsync();
|
||||
locale.Should().NotBeNullOrWhiteSpace();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Can_get_special_paths()
|
||||
{
|
||||
var userData = await Electron.App.GetPathAsync(PathName.UserData);
|
||||
userData.Should().NotBeNullOrWhiteSpace();
|
||||
Directory.Exists(Path.GetDirectoryName(userData) ?? userData).Should().BeTrue();
|
||||
|
||||
var temp = await Electron.App.GetPathAsync(PathName.Temp);
|
||||
temp.Should().NotBeNullOrWhiteSpace();
|
||||
Directory.Exists(temp).Should().BeTrue();
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task Badge_count_roundtrip_where_supported()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
var ok = await Electron.App.SetBadgeCountAsync(3);
|
||||
ok.Should().BeTrue();
|
||||
var count = await Electron.App.GetBadgeCountAsync();
|
||||
count.Should().Be(3);
|
||||
// reset
|
||||
await Electron.App.SetBadgeCountAsync(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// On Windows it's usually unsupported; ensure badge query works and returns a non-negative value
|
||||
await Electron.App.SetBadgeCountAsync(0); // ignore return value
|
||||
var count = await Electron.App.GetBadgeCountAsync();
|
||||
count.Should().BeGreaterOrEqualTo(0);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Can_get_app_metrics()
|
||||
{
|
||||
var metrics = await Electron.App.GetAppMetricsAsync();
|
||||
metrics.Should().NotBeNull();
|
||||
metrics.Length.Should().BeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Can_get_gpu_feature_status()
|
||||
{
|
||||
var status = await Electron.App.GetGpuFeatureStatusAsync();
|
||||
status.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Can_get_login_item_settings()
|
||||
{
|
||||
var settings = await Electron.App.GetLoginItemSettingsAsync();
|
||||
settings.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Can_set_app_logs_path()
|
||||
{
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), "ElectronLogsTest" + Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(tempDir);
|
||||
Electron.App.SetAppLogsPath(tempDir);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CommandLine_append_and_query_switch()
|
||||
{
|
||||
var switchName = "integration-switch";
|
||||
Electron.App.CommandLine.AppendSwitch(switchName, "value123");
|
||||
(await Electron.App.CommandLine.HasSwitchAsync(switchName)).Should().BeTrue();
|
||||
(await Electron.App.CommandLine.GetSwitchValueAsync(switchName)).Should().Be("value123");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Accessibility_support_toggle()
|
||||
{
|
||||
Electron.App.SetAccessibilitySupportEnabled(true);
|
||||
var enabled = await Electron.App.IsAccessibilitySupportEnabledAsync();
|
||||
enabled.Should().BeTrue(); // API responded
|
||||
Electron.App.SetAccessibilitySupportEnabled(false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UserAgentFallback_roundtrip()
|
||||
{
|
||||
var original = await Electron.App.UserAgentFallbackAsync;
|
||||
Electron.App.UserAgentFallback = "ElectronIntegrationTest/1.0";
|
||||
var updated = await Electron.App.UserAgentFallbackAsync;
|
||||
updated.Should().Be("ElectronIntegrationTest/1.0");
|
||||
Electron.App.UserAgentFallback = original; // restore
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BadgeCount_set_and_reset_where_supported()
|
||||
{
|
||||
await Electron.App.SetBadgeCountAsync(2);
|
||||
var count = await Electron.App.GetBadgeCountAsync();
|
||||
// Some platforms may always return0; just ensure call didn't throw and is non-negative
|
||||
count.Should().BeGreaterOrEqualTo(0);
|
||||
await Electron.App.SetBadgeCountAsync(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task App_metrics_have_cpu_info()
|
||||
{
|
||||
var metrics = await Electron.App.GetAppMetricsAsync();
|
||||
metrics[0].Cpu.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task App_badge_count_roundtrip()
|
||||
{
|
||||
// Set then get (non-mac platforms treat as no-op but should return0 or set value)
|
||||
var success = await Electron.App.SetBadgeCountAsync(3);
|
||||
success.Should().BeTrue();
|
||||
var count = await Electron.App.GetBadgeCountAsync();
|
||||
// Allow fallback to0 on platforms without badge support
|
||||
(count ==3 || count ==0).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task App_gpu_feature_status_has_some_fields()
|
||||
{
|
||||
var status = await Electron.App.GetGpuFeatureStatusAsync();
|
||||
status.Should().NotBeNull();
|
||||
status.Webgl.Should().NotBeNull();
|
||||
status.VideoDecode.Should().NotBeNull();
|
||||
}
|
||||
}
|
||||
}
|
||||
27
src/ElectronNET.IntegrationTests/Tests/BrowserViewTests.cs
Normal file
27
src/ElectronNET.IntegrationTests/Tests/BrowserViewTests.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace ElectronNET.IntegrationTests.Tests
|
||||
{
|
||||
using ElectronNET.API;
|
||||
using ElectronNET.API.Entities;
|
||||
|
||||
[Collection("ElectronCollection")]
|
||||
public class BrowserViewTests
|
||||
{
|
||||
private readonly ElectronFixture fx;
|
||||
public BrowserViewTests(ElectronFixture fx)
|
||||
{
|
||||
this.fx = fx;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Create_browser_view_and_adjust_bounds()
|
||||
{
|
||||
var view = await Electron.WindowManager.CreateBrowserViewAsync(new BrowserViewConstructorOptions());
|
||||
this.fx.MainWindow.SetBrowserView(view);
|
||||
view.Bounds = new Rectangle { X = 0, Y = 0, Width = 300, Height = 200 };
|
||||
// Access bounds again (synchronous property fetch)
|
||||
var current = view.Bounds;
|
||||
current.Width.Should().Be(300);
|
||||
current.Height.Should().Be(200);
|
||||
}
|
||||
}
|
||||
}
|
||||
223
src/ElectronNET.IntegrationTests/Tests/BrowserWindowTests.cs
Normal file
223
src/ElectronNET.IntegrationTests/Tests/BrowserWindowTests.cs
Normal file
@@ -0,0 +1,223 @@
|
||||
namespace ElectronNET.IntegrationTests.Tests
|
||||
{
|
||||
using System.Runtime.InteropServices;
|
||||
using ElectronNET.API;
|
||||
using ElectronNET.API.Entities;
|
||||
|
||||
[Collection("ElectronCollection")]
|
||||
public class BrowserWindowTests
|
||||
{
|
||||
private readonly ElectronFixture fx;
|
||||
|
||||
public BrowserWindowTests(ElectronFixture fx)
|
||||
{
|
||||
this.fx = fx;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Can_set_and_get_title()
|
||||
{
|
||||
const string title = "Integration Test Title";
|
||||
this.fx.MainWindow.SetTitle(title);
|
||||
var roundTrip = await this.fx.MainWindow.GetTitleAsync();
|
||||
roundTrip.Should().Be(title);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Can_resize_and_get_size()
|
||||
{
|
||||
this.fx.MainWindow.SetSize(643, 482);
|
||||
var size = await this.fx.MainWindow.GetSizeAsync();
|
||||
size.Should().HaveCount(2);
|
||||
size[0].Should().Be(643);
|
||||
size[1].Should().Be(482);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Can_set_progress_bar_and_clear()
|
||||
{
|
||||
this.fx.MainWindow.SetProgressBar(0.5);
|
||||
// No direct getter; rely on absence of error. Try changing again.
|
||||
this.fx.MainWindow.SetProgressBar(-1); // clears
|
||||
await Task.Delay(50);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Can_set_and_get_position()
|
||||
{
|
||||
this.fx.MainWindow.SetPosition(134, 246);
|
||||
await Task.Delay(500);
|
||||
var pos = await this.fx.MainWindow.GetPositionAsync();
|
||||
pos.Should().BeEquivalentTo(new[] { 134, 246 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Can_set_and_get_bounds()
|
||||
{
|
||||
var bounds = new Rectangle { X = 10, Y = 20, Width = 400, Height = 300 };
|
||||
this.fx.MainWindow.SetBounds(bounds);
|
||||
var round = await this.fx.MainWindow.GetBoundsAsync();
|
||||
|
||||
round.Should().BeEquivalentTo(bounds);
|
||||
round.Width.Should().Be(400);
|
||||
round.Height.Should().Be(300);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Can_set_and_get_content_bounds()
|
||||
{
|
||||
var bounds = new Rectangle { X = 0, Y = 0, Width = 300, Height = 200 };
|
||||
this.fx.MainWindow.SetContentBounds(bounds);
|
||||
var round = await this.fx.MainWindow.GetContentBoundsAsync();
|
||||
round.Width.Should().BeGreaterThan(0);
|
||||
round.Height.Should().BeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Show_hide_visibility_roundtrip()
|
||||
{
|
||||
this.fx.MainWindow.Show();
|
||||
(await this.fx.MainWindow.IsVisibleAsync()).Should().BeTrue();
|
||||
this.fx.MainWindow.Hide();
|
||||
(await this.fx.MainWindow.IsVisibleAsync()).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AlwaysOnTop_toggle_and_query()
|
||||
{
|
||||
this.fx.MainWindow.SetAlwaysOnTop(true);
|
||||
(await this.fx.MainWindow.IsAlwaysOnTopAsync()).Should().BeTrue();
|
||||
this.fx.MainWindow.SetAlwaysOnTop(false);
|
||||
(await this.fx.MainWindow.IsAlwaysOnTopAsync()).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MenuBar_auto_hide_and_visibility()
|
||||
{
|
||||
this.fx.MainWindow.SetAutoHideMenuBar(true);
|
||||
(await this.fx.MainWindow.IsMenuBarAutoHideAsync()).Should().BeTrue();
|
||||
this.fx.MainWindow.SetMenuBarVisibility(false);
|
||||
(await this.fx.MainWindow.IsMenuBarVisibleAsync()).Should().BeFalse();
|
||||
this.fx.MainWindow.SetMenuBarVisibility(true);
|
||||
(await this.fx.MainWindow.IsMenuBarVisibleAsync()).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadyToShow_event_fires_after_content_ready()
|
||||
{
|
||||
var window = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = false });
|
||||
var tcs = new TaskCompletionSource();
|
||||
window.OnReadyToShow += () => tcs.TrySetResult();
|
||||
|
||||
// Trigger a navigation and wait for DOM ready so the renderer paints, which emits ready-to-show
|
||||
var domReadyTcs = new TaskCompletionSource();
|
||||
window.WebContents.OnDomReady += () => domReadyTcs.TrySetResult();
|
||||
await window.WebContents.LoadURLAsync("about:blank");
|
||||
await domReadyTcs.Task;
|
||||
|
||||
var completed = await Task.WhenAny(tcs.Task, Task.Delay(3000));
|
||||
completed.Should().Be(tcs.Task);
|
||||
|
||||
// Typical usage is to show once ready
|
||||
window.Show();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PageTitleUpdated_event_fires_on_title_change()
|
||||
{
|
||||
var window = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = true });
|
||||
var tcs = new TaskCompletionSource<string>();
|
||||
window.OnPageTitleUpdated += title => tcs.TrySetResult(title);
|
||||
|
||||
// Navigate and wait for DOM ready, then change the document.title to trigger the event
|
||||
var domReadyTcs = new TaskCompletionSource();
|
||||
window.WebContents.OnDomReady += () => domReadyTcs.TrySetResult();
|
||||
await window.WebContents.LoadURLAsync("about:blank");
|
||||
await domReadyTcs.Task;
|
||||
await window.WebContents.ExecuteJavaScriptAsync("document.title='NewTitle';");
|
||||
|
||||
// Wait for event up to a short timeout
|
||||
var completed2 = await Task.WhenAny(tcs.Task, Task.Delay(3000));
|
||||
completed2.Should().Be(tcs.Task);
|
||||
(await tcs.Task).Should().Be("NewTitle");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Resize_event_fires_on_size_change()
|
||||
{
|
||||
var window = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = false });
|
||||
var resized = false;
|
||||
window.OnResize += () => resized = true;
|
||||
window.SetSize(500, 400);
|
||||
await Task.Delay(300);
|
||||
resized.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Progress_bar_and_always_on_top_toggle()
|
||||
{
|
||||
var win = this.fx.MainWindow;
|
||||
win.SetProgressBar(0.5);
|
||||
win.SetProgressBar(0.8, new ProgressBarOptions { Mode = ProgressBarMode.normal });
|
||||
win.SetAlwaysOnTop(true);
|
||||
(await win.IsAlwaysOnTopAsync()).Should().BeTrue();
|
||||
win.SetAlwaysOnTop(false);
|
||||
(await win.IsAlwaysOnTopAsync()).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Menu_bar_visibility_and_auto_hide()
|
||||
{
|
||||
var win = this.fx.MainWindow;
|
||||
win.SetAutoHideMenuBar(true);
|
||||
(await win.IsMenuBarAutoHideAsync()).Should().BeTrue();
|
||||
win.SetMenuBarVisibility(true);
|
||||
(await win.IsMenuBarVisibleAsync()).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Parent_child_relationship_roundtrip()
|
||||
{
|
||||
var child = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = false, Width = 300, Height = 200 });
|
||||
this.fx.MainWindow.SetParentWindow(null); // ensure top-level
|
||||
child.SetParentWindow(this.fx.MainWindow);
|
||||
var parent = await child.GetParentWindowAsync();
|
||||
parent.Id.Should().Be(this.fx.MainWindow.Id);
|
||||
var kids = await this.fx.MainWindow.GetChildWindowsAsync();
|
||||
kids.Select(k => k.Id).Should().Contain(child.Id);
|
||||
child.Destroy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Represented_filename_and_edited_flags()
|
||||
{
|
||||
var win = this.fx.MainWindow;
|
||||
var temp = Path.Combine(Path.GetTempPath(), "electronnet_test.txt");
|
||||
File.WriteAllText(temp, "test");
|
||||
win.SetRepresentedFilename(temp);
|
||||
var represented = await win.GetRepresentedFilenameAsync();
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
represented.Should().Be(temp);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Non-macOS platforms may not support represented filename; empty is acceptable
|
||||
represented.Should().BeEmpty();
|
||||
}
|
||||
|
||||
win.SetDocumentEdited(true);
|
||||
var edited = await win.IsDocumentEditedAsync();
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
edited.Should().BeTrue();
|
||||
}
|
||||
else
|
||||
{
|
||||
edited.Should().BeFalse(); // unsupported on non-mac platforms
|
||||
}
|
||||
|
||||
win.SetDocumentEdited(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
43
src/ElectronNET.IntegrationTests/Tests/ClipboardTests.cs
Normal file
43
src/ElectronNET.IntegrationTests/Tests/ClipboardTests.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
namespace ElectronNET.IntegrationTests.Tests
|
||||
{
|
||||
using ElectronNET.API;
|
||||
|
||||
[Collection("ElectronCollection")]
|
||||
public class ClipboardTests
|
||||
{
|
||||
// ReSharper disable once NotAccessedField.Local
|
||||
private readonly ElectronFixture fx;
|
||||
|
||||
public ClipboardTests(ElectronFixture fx)
|
||||
{
|
||||
this.fx = fx;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Clipboard_text_roundtrip()
|
||||
{
|
||||
var text = $"Hello Electron {Guid.NewGuid()}";
|
||||
Electron.Clipboard.WriteText(text);
|
||||
var read = await Electron.Clipboard.ReadTextAsync();
|
||||
read.Should().Be(text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Available_formats_contains_text_after_write()
|
||||
{
|
||||
var text = "FormatsTest";
|
||||
Electron.Clipboard.WriteText(text);
|
||||
var formats = await Electron.Clipboard.AvailableFormatsAsync();
|
||||
formats.Should().Contain(f => f.Contains("text") || f.Contains("TEXT") || f.Contains("plain"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Bookmark_write_and_read()
|
||||
{
|
||||
var url = "https://electron-test.com";
|
||||
Electron.Clipboard.WriteBookmark("TitleTest", url);
|
||||
var bookmark = await Electron.Clipboard.ReadBookmarkAsync();
|
||||
bookmark.Url.Should().Be(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/ElectronNET.IntegrationTests/Tests/CookiesTests.cs
Normal file
26
src/ElectronNET.IntegrationTests/Tests/CookiesTests.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace ElectronNET.IntegrationTests.Tests
|
||||
{
|
||||
[Collection("ElectronCollection")]
|
||||
public class CookiesTests
|
||||
{
|
||||
private readonly ElectronFixture fx;
|
||||
public CookiesTests(ElectronFixture fx)
|
||||
{
|
||||
this.fx = fx;
|
||||
}
|
||||
|
||||
[Fact(Skip = "Cookie set/get requires navigation to domain; skipping until test harness serves page")]
|
||||
public async Task Cookie_set_get_remove_sequence()
|
||||
{
|
||||
var session = this.fx.MainWindow.WebContents.Session;
|
||||
var changed = false;
|
||||
session.Cookies.OnChanged += (cookie, cause, removed) => changed = true;
|
||||
// Navigate to example.com so cookie domain matches
|
||||
await this.fx.MainWindow.WebContents.LoadURLAsync("https://example.com");
|
||||
// Set via renderer for now
|
||||
await this.fx.MainWindow.WebContents.ExecuteJavaScriptAsync("document.cookie='integration_cookie=1;path=/';");
|
||||
await Task.Delay(500);
|
||||
changed.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace ElectronNET.IntegrationTests.Tests
|
||||
{
|
||||
using System.Runtime.InteropServices;
|
||||
using ElectronNET.API;
|
||||
|
||||
[Collection("ElectronCollection")]
|
||||
public class GlobalShortcutTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Can_register_and_unregister()
|
||||
{
|
||||
var accel = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "Cmd+Alt+G" : "Ctrl+Alt+G";
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
Electron.GlobalShortcut.Register(accel, () => tcs.TrySetResult(true));
|
||||
var isRegistered = await Electron.GlobalShortcut.IsRegisteredAsync(accel);
|
||||
isRegistered.Should().BeTrue();
|
||||
Electron.GlobalShortcut.Unregister(accel);
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/ElectronNET.IntegrationTests/Tests/HostHookTests.cs
Normal file
15
src/ElectronNET.IntegrationTests/Tests/HostHookTests.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace ElectronNET.IntegrationTests.Tests
|
||||
{
|
||||
using ElectronNET.API;
|
||||
|
||||
[Collection("ElectronCollection")]
|
||||
public class HostHookTests
|
||||
{
|
||||
[Fact(Skip = "Requires HostHook setup; skipping")]
|
||||
public async Task HostHook_call_returns_value()
|
||||
{
|
||||
var result = await Electron.HostHook.CallAsync<string>("create-excel-file", ".");
|
||||
result.Should().NotBeNull();
|
||||
}
|
||||
}
|
||||
}
|
||||
86
src/ElectronNET.IntegrationTests/Tests/IpcMainTests.cs
Normal file
86
src/ElectronNET.IntegrationTests/Tests/IpcMainTests.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
namespace ElectronNET.IntegrationTests.Tests
|
||||
{
|
||||
using ElectronNET.API;
|
||||
|
||||
[Collection("ElectronCollection")]
|
||||
public class IpcMainTests
|
||||
{
|
||||
private readonly ElectronFixture fx;
|
||||
public IpcMainTests(ElectronFixture fx)
|
||||
{
|
||||
this.fx = fx;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Ipc_On_receives_message_from_renderer()
|
||||
{
|
||||
var tcs = new TaskCompletionSource<string>();
|
||||
await Electron.IpcMain.On("ipc-on-test", obj => tcs.TrySetResult(obj?.ToString() ?? string.Empty));
|
||||
await this.fx.MainWindow.WebContents.ExecuteJavaScriptAsync("require('electron').ipcRenderer.send('ipc-on-test','payload123')");
|
||||
var result = await tcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
result.Should().Be("payload123");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Ipc_Once_only_fires_once()
|
||||
{
|
||||
var count = 0;
|
||||
Electron.IpcMain.Once("ipc-once-test", _ => count++);
|
||||
await this.fx.MainWindow.WebContents.ExecuteJavaScriptAsync("const {ipcRenderer}=require('electron'); ipcRenderer.send('ipc-once-test','a'); ipcRenderer.send('ipc-once-test','b');");
|
||||
await Task.Delay(500);
|
||||
count.Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Ipc_RemoveAllListeners_stops_receiving()
|
||||
{
|
||||
var fired = false;
|
||||
await Electron.IpcMain.On("ipc-remove-test", _ => fired = true);
|
||||
Electron.IpcMain.RemoveAllListeners("ipc-remove-test");
|
||||
await this.fx.MainWindow.WebContents.ExecuteJavaScriptAsync("require('electron').ipcRenderer.send('ipc-remove-test','x')");
|
||||
await Task.Delay(400);
|
||||
fired.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Ipc_OnSync_returns_value()
|
||||
{
|
||||
Electron.IpcMain.OnSync("ipc-sync-test", obj =>
|
||||
{
|
||||
obj.Should().NotBeNull();
|
||||
return "pong";
|
||||
});
|
||||
var ret = await this.fx.MainWindow.WebContents.ExecuteJavaScriptAsync("require('electron').ipcRenderer.sendSync('ipc-sync-test','ping')");
|
||||
ret.Should().Be("pong");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Ipc_Send_from_main_reaches_renderer()
|
||||
{
|
||||
// Listener: store raw arg; if Electron packs differently we will normalize later
|
||||
await this.fx.MainWindow.WebContents.ExecuteJavaScriptAsync(@"(function(){ const {ipcRenderer}=require('electron'); ipcRenderer.once('main-to-render',(e,arg)=>{ globalThis.__mainToRender = arg;}); return 'ready'; })();");
|
||||
Electron.IpcMain.Send(this.fx.MainWindow, "main-to-render", "hello-msg");
|
||||
string value = "";
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
var jsVal = await this.fx.MainWindow.WebContents.ExecuteJavaScriptAsync("globalThis.__mainToRender === undefined ? '' : (typeof globalThis.__mainToRender === 'string' ? globalThis.__mainToRender : JSON.stringify(globalThis.__mainToRender))");
|
||||
value = jsVal?.ToString() ?? "";
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
// Normalize possible JSON array ["hello-msg"] case
|
||||
if (value.StartsWith("[\"") && value.EndsWith("\"]"))
|
||||
{
|
||||
// Extract first element between [" and "]
|
||||
value = value.Substring(2, value.Length - 4);
|
||||
}
|
||||
|
||||
value.Should().Be("hello-msg");
|
||||
}
|
||||
}
|
||||
}
|
||||
60
src/ElectronNET.IntegrationTests/Tests/MenuTests.cs
Normal file
60
src/ElectronNET.IntegrationTests/Tests/MenuTests.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
namespace ElectronNET.IntegrationTests.Tests
|
||||
{
|
||||
using ElectronNET.API;
|
||||
using ElectronNET.API.Entities;
|
||||
|
||||
[Collection("ElectronCollection")]
|
||||
public class MenuTests
|
||||
{
|
||||
private readonly ElectronFixture fx;
|
||||
public MenuTests(ElectronFixture fx)
|
||||
{
|
||||
this.fx = fx;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ApplicationMenu_click_invokes_handler()
|
||||
{
|
||||
var clicked = false;
|
||||
var items = new[]
|
||||
{
|
||||
new MenuItem
|
||||
{
|
||||
Label = "File",
|
||||
Submenu = new[]
|
||||
{
|
||||
new MenuItem { Label = "Ping", Click = () => clicked = true },
|
||||
},
|
||||
},
|
||||
};
|
||||
Electron.Menu.SetApplicationMenu(items);
|
||||
var targetId = items[0].Submenu[0].Id;
|
||||
await this.fx.MainWindow.WebContents.ExecuteJavaScriptAsync($"require('electron').ipcRenderer.send('integration-click-application-menu','{targetId}')");
|
||||
for (int i = 0; i < 20 && !clicked; i++)
|
||||
{
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
clicked.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ContextMenu_popup_registers_items()
|
||||
{
|
||||
var win = this.fx.MainWindow;
|
||||
var ctxClicked = false;
|
||||
var ctxItems = new[] { new MenuItem { Label = "Ctx", Click = () => ctxClicked = true } };
|
||||
Electron.Menu.SetContextMenu(win, ctxItems);
|
||||
var ctxId = ctxItems[0].Id;
|
||||
// simulate popup then click
|
||||
Electron.Menu.ContextMenuPopup(win);
|
||||
await this.fx.MainWindow.WebContents.ExecuteJavaScriptAsync($"require('electron').ipcRenderer.send('integration-click-context-menu',{win.Id},'{ctxId}')");
|
||||
for (int i = 0; i < 20 && !ctxClicked; i++)
|
||||
{
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
ctxClicked.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
namespace ElectronNET.IntegrationTests.Tests
|
||||
{
|
||||
[Collection("ElectronCollection")]
|
||||
public class MultiEventRegistrationTests
|
||||
{
|
||||
private readonly ElectronFixture fx;
|
||||
|
||||
public MultiEventRegistrationTests(ElectronFixture fx)
|
||||
{
|
||||
this.fx = fx;
|
||||
}
|
||||
|
||||
private static async Task<bool> WaitAllOrTimeout(TimeSpan timeout, params Task[] tasks)
|
||||
{
|
||||
var all = Task.WhenAll(tasks);
|
||||
var completed = await Task.WhenAny(all, Task.Delay(timeout));
|
||||
return ReferenceEquals(completed, all) && all.IsCompletedSuccessfully;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BrowserWindow_OnResize_multiple_handlers_called()
|
||||
{
|
||||
var win = this.fx.MainWindow;
|
||||
var h1 = new TaskCompletionSource();
|
||||
var h2 = new TaskCompletionSource();
|
||||
var h3 = new TaskCompletionSource();
|
||||
|
||||
win.OnResize += () => h1.TrySetResult();
|
||||
win.OnResize += () => h2.TrySetResult();
|
||||
win.OnResize += () => h3.TrySetResult();
|
||||
|
||||
var size = await win.GetSizeAsync();
|
||||
// trigger resize
|
||||
win.SetSize(size[0] + 20, size[1] + 10);
|
||||
|
||||
var ok = await WaitAllOrTimeout(TimeSpan.FromSeconds(5), h1.Task, h2.Task, h3.Task);
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
throw new Xunit.Sdk.XunitException($"Not all events were fired: \nEvent1 fired: {h1.Task.IsCompleted}\nEvent2 fired: {h2.Task.IsCompleted}\nEvent3 fired: {h3.Task.IsCompleted}");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WebContents_OnDomReady_multiple_handlers_called()
|
||||
{
|
||||
var wc = this.fx.MainWindow.WebContents;
|
||||
var r1 = new TaskCompletionSource();
|
||||
var r2 = new TaskCompletionSource();
|
||||
|
||||
wc.OnDomReady += () => r1.TrySetResult();
|
||||
wc.OnDomReady += () => r2.TrySetResult();
|
||||
|
||||
await wc.LoadURLAsync("about:blank");
|
||||
|
||||
var ok = await WaitAllOrTimeout(TimeSpan.FromSeconds(2), r1.Task, r2.Task);
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
throw new Xunit.Sdk.XunitException($"Not all events were fired: \nEvent1 fired: {r1.Task.IsCompleted}\nEvent2 fired: {r2.Task.IsCompleted}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
87
src/ElectronNET.IntegrationTests/Tests/NativeImageTests.cs
Normal file
87
src/ElectronNET.IntegrationTests/Tests/NativeImageTests.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using RectangleEntity = ElectronNET.API.Entities.Rectangle;
|
||||
|
||||
namespace ElectronNET.IntegrationTests.Tests
|
||||
{
|
||||
using System.Drawing;
|
||||
using ElectronNET.API.Entities;
|
||||
|
||||
public class NativeImageTests
|
||||
{
|
||||
[Fact]
|
||||
public void Create_from_bitmap_and_to_png()
|
||||
{
|
||||
using var bmp = new Bitmap(10, 10);
|
||||
using (var g = Graphics.FromImage(bmp))
|
||||
{
|
||||
g.Clear(Color.Red);
|
||||
}
|
||||
|
||||
var native = NativeImage.CreateFromBitmap(bmp);
|
||||
var size = native.GetSize();
|
||||
size.Width.Should().Be(10);
|
||||
size.Height.Should().Be(10);
|
||||
var png = native.ToPNG(new ToPNGOptions { ScaleFactor = 1.0f });
|
||||
png.Should().NotBeNull();
|
||||
png!.Length.Should().BeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_from_buffer_and_to_data_url()
|
||||
{
|
||||
// Prepare PNG bytes
|
||||
using var bmp = new Bitmap(8, 8);
|
||||
using (var g = Graphics.FromImage(bmp))
|
||||
{
|
||||
g.Clear(Color.Blue);
|
||||
}
|
||||
|
||||
using var ms = new MemoryStream();
|
||||
bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
|
||||
var bytes = ms.ToArray();
|
||||
var native = NativeImage.CreateFromBuffer(bytes);
|
||||
var dataUrl = native.ToDataURL(new ToDataUrlOptions { ScaleFactor = 1.0f });
|
||||
dataUrl.Should().NotBeNullOrWhiteSpace();
|
||||
dataUrl!.StartsWith("data:image/", StringComparison.Ordinal).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Resize_and_crop_produce_expected_sizes()
|
||||
{
|
||||
using var bmp = new Bitmap(12, 10);
|
||||
using (var g = Graphics.FromImage(bmp))
|
||||
{
|
||||
g.Clear(Color.Green);
|
||||
}
|
||||
|
||||
var native = NativeImage.CreateFromBitmap(bmp);
|
||||
var resized = native.Resize(new ResizeOptions { Width = 6, Height = 5 });
|
||||
var rsize = resized.GetSize();
|
||||
rsize.Width.Should().Be(6);
|
||||
rsize.Height.Should().Be(5);
|
||||
var cropped = native.Crop(new RectangleEntity { X = 2, Y = 2, Width = 4, Height = 3 });
|
||||
var csize = cropped.GetSize();
|
||||
csize.Width.Should().Be(4);
|
||||
csize.Height.Should().Be(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Add_representation_for_scale_factor()
|
||||
{
|
||||
using var bmp = new Bitmap(5, 5);
|
||||
using (var g = Graphics.FromImage(bmp))
|
||||
{
|
||||
g.Clear(Color.Black);
|
||||
}
|
||||
|
||||
using var ms = new MemoryStream();
|
||||
bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
|
||||
var buffer = ms.ToArray();
|
||||
var native = NativeImage.CreateFromBitmap(bmp);
|
||||
native.AddRepresentation(new AddRepresentationOptions { Buffer = buffer, ScaleFactor = 2.0f });
|
||||
var size2X = native.GetSize(2.0f);
|
||||
size2X.Should().NotBeNull();
|
||||
size2X.Width.Should().Be(5);
|
||||
size2X.Height.Should().Be(5);
|
||||
}
|
||||
}
|
||||
}
|
||||
43
src/ElectronNET.IntegrationTests/Tests/NativeThemeTests.cs
Normal file
43
src/ElectronNET.IntegrationTests/Tests/NativeThemeTests.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
namespace ElectronNET.IntegrationTests.Tests
|
||||
{
|
||||
using ElectronNET.API;
|
||||
using ElectronNET.API.Entities;
|
||||
|
||||
[Collection("ElectronCollection")]
|
||||
public class NativeThemeTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ThemeSource_roundtrip()
|
||||
{
|
||||
// Capture initial
|
||||
_ = await Electron.NativeTheme.ShouldUseDarkColorsAsync();
|
||||
// Force light
|
||||
Electron.NativeTheme.SetThemeSource(ThemeSourceMode.Light);
|
||||
var useDarkAfterLight = await Electron.NativeTheme.ShouldUseDarkColorsAsync();
|
||||
// Force dark
|
||||
Electron.NativeTheme.SetThemeSource(ThemeSourceMode.Dark);
|
||||
var useDarkAfterDark = await Electron.NativeTheme.ShouldUseDarkColorsAsync();
|
||||
// Restore system
|
||||
Electron.NativeTheme.SetThemeSource(ThemeSourceMode.System);
|
||||
// Assertions are tolerant (platform dependent)
|
||||
useDarkAfterLight.Should().BeFalse("forcing Light should result in light colors");
|
||||
useDarkAfterDark.Should().BeTrue("forcing Dark should result in dark colors");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Updated_event_fires_on_change()
|
||||
{
|
||||
var fired = false;
|
||||
Electron.NativeTheme.Updated += () => fired = true;
|
||||
Electron.NativeTheme.SetThemeSource(ThemeSourceMode.Dark);
|
||||
await Task.Delay(400);
|
||||
Electron.NativeTheme.SetThemeSource(ThemeSourceMode.Light);
|
||||
for (int i = 0; i < 10 && !fired; i++)
|
||||
{
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
fired.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/ElectronNET.IntegrationTests/Tests/NotificationTests.cs
Normal file
31
src/ElectronNET.IntegrationTests/Tests/NotificationTests.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace ElectronNET.IntegrationTests.Tests
|
||||
{
|
||||
using ElectronNET.API;
|
||||
using ElectronNET.API.Entities;
|
||||
|
||||
[Collection("ElectronCollection")]
|
||||
public class NotificationTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Notification_create_check()
|
||||
{
|
||||
var tcs = new TaskCompletionSource();
|
||||
|
||||
var options = new NotificationOptions("Notification Title", "Notification test 123");
|
||||
options.OnShow = () => tcs.SetResult();
|
||||
|
||||
Electron.Notification.Show(options);
|
||||
|
||||
await Task.WhenAny(tcs.Task, Task.Delay(5_000));
|
||||
|
||||
tcs.Task.IsCompletedSuccessfully.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Notification_is_supported_check()
|
||||
{
|
||||
var supported = await Electron.Notification.IsSupportedAsync();
|
||||
supported.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/ElectronNET.IntegrationTests/Tests/ProcessTests.cs
Normal file
29
src/ElectronNET.IntegrationTests/Tests/ProcessTests.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
namespace ElectronNET.IntegrationTests.Tests
|
||||
{
|
||||
using ElectronNET.API;
|
||||
|
||||
[Collection("ElectronCollection")]
|
||||
public class ProcessTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Process_info_is_accessible()
|
||||
{
|
||||
// Use renderer to fetch process info and round-trip
|
||||
var execPath = await Electron.WindowManager.CreateWindowAsync(new API.Entities.BrowserWindowOptions { Show = false });
|
||||
var result = await execPath.WebContents.ExecuteJavaScriptAsync("process.execPath && process.platform ? 'ok' : 'fail'");
|
||||
result.Should().Be("ok");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Process_properties_are_populated()
|
||||
{
|
||||
var execPath = await Electron.Process.ExecPathAsync;
|
||||
execPath.Should().NotBeNullOrWhiteSpace();
|
||||
var pid = await Electron.Process.PidAsync;
|
||||
pid.Should().BeGreaterThan(0);
|
||||
var platform = await Electron.Process.PlatformAsync;
|
||||
platform.Should().NotBeNullOrWhiteSpace();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
32
src/ElectronNET.IntegrationTests/Tests/ScreenTests.cs
Normal file
32
src/ElectronNET.IntegrationTests/Tests/ScreenTests.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace ElectronNET.IntegrationTests.Tests
|
||||
{
|
||||
using ElectronNET.API;
|
||||
|
||||
[Collection("ElectronCollection")]
|
||||
public class ScreenTests
|
||||
{
|
||||
// ReSharper disable once NotAccessedField.Local
|
||||
private readonly ElectronFixture fx;
|
||||
|
||||
public ScreenTests(ElectronFixture fx)
|
||||
{
|
||||
this.fx = fx;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Primary_display_has_positive_dimensions()
|
||||
{
|
||||
var display = await Electron.Screen.GetPrimaryDisplayAsync();
|
||||
display.Size.Width.Should().BeGreaterThan(0);
|
||||
display.Size.Height.Should().BeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetAllDisplays_returns_at_least_one()
|
||||
{
|
||||
var displays = await Electron.Screen.GetAllDisplaysAsync();
|
||||
displays.Should().NotBeNull();
|
||||
displays.Length.Should().BeGreaterThan(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
103
src/ElectronNET.IntegrationTests/Tests/SessionTests.cs
Normal file
103
src/ElectronNET.IntegrationTests/Tests/SessionTests.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
namespace ElectronNET.IntegrationTests.Tests
|
||||
{
|
||||
using ElectronNET.API.Entities;
|
||||
|
||||
[Collection("ElectronCollection")]
|
||||
public class SessionTests
|
||||
{
|
||||
private readonly ElectronFixture fx;
|
||||
|
||||
public SessionTests(ElectronFixture fx)
|
||||
{
|
||||
this.fx = fx;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Session_preloads_roundtrip()
|
||||
{
|
||||
var session = this.fx.MainWindow.WebContents.Session;
|
||||
_ = await session.GetPreloadsAsync();
|
||||
// Use a dummy path; API should store value
|
||||
session.SetPreloads(new[] { "/tmp/preload_dummy.js" });
|
||||
var preloadsAfter = await session.GetPreloadsAsync();
|
||||
preloadsAfter.Should().Contain("/tmp/preload_dummy.js");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Session_proxy_set_and_resolve()
|
||||
{
|
||||
var session = this.fx.MainWindow.WebContents.Session;
|
||||
// Provide all ctor args (pacScript empty to ignore, proxyRules direct, bypass empty)
|
||||
await session.SetProxyAsync(new ProxyConfig("", "direct://", ""));
|
||||
var proxy = await session.ResolveProxyAsync("https://example.com");
|
||||
proxy.Should().NotBeNull();
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task Session_clear_cache_and_storage_completes()
|
||||
{
|
||||
var session = this.fx.MainWindow.WebContents.Session;
|
||||
await session.ClearCacheAsync();
|
||||
await session.ClearStorageDataAsync();
|
||||
await session.ClearHostResolverCacheAsync();
|
||||
// Ensure still can query user agent after clears
|
||||
var ua = await session.GetUserAgent();
|
||||
ua.Should().NotBeNullOrWhiteSpace();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Session_preloads_set_multiple_and_clear()
|
||||
{
|
||||
var session = this.fx.MainWindow.WebContents.Session;
|
||||
session.SetPreloads(new[] { "/tmp/a.js", "/tmp/b.js" });
|
||||
var after = await session.GetPreloadsAsync();
|
||||
after.Should().Contain("/tmp/a.js").And.Contain("/tmp/b.js");
|
||||
// Reset to empty
|
||||
session.SetPreloads(Array.Empty<string>());
|
||||
var empty = await session.GetPreloadsAsync();
|
||||
empty.Should().NotContain("/tmp/a.js");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Clear_auth_cache_overloads()
|
||||
{
|
||||
var session = this.fx.MainWindow.WebContents.Session;
|
||||
await session.ClearAuthCacheAsync();
|
||||
await session.ClearAuthCacheAsync(new RemovePassword("password") { Origin = "https://example.com", Username = "user", Password = "pw", Realm = "realm", Scheme = Scheme.basic });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Clear_storage_with_options()
|
||||
{
|
||||
var session = this.fx.MainWindow.WebContents.Session;
|
||||
await session.ClearStorageDataAsync(new ClearStorageDataOptions { Storages = new[] { "cookies" }, Quotas = new[] { "temporary" } });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Enable_disable_network_emulation()
|
||||
{
|
||||
var session = this.fx.MainWindow.WebContents.Session;
|
||||
session.EnableNetworkEmulation(new EnableNetworkEmulationOptions { Offline = false, Latency = 10, DownloadThroughput = 50000, UploadThroughput = 20000 });
|
||||
session.DisableNetworkEmulation();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Flush_storage_data_does_not_throw()
|
||||
{
|
||||
var session = this.fx.MainWindow.WebContents.Session;
|
||||
session.FlushStorageData();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Set_user_agent_affects_new_navigation()
|
||||
{
|
||||
var session = this.fx.MainWindow.WebContents.Session;
|
||||
// Set UA and verify via session API (navigator.userAgent on existing WebContents may not reflect the override)
|
||||
session.SetUserAgent("IntegrationAgent/1.0");
|
||||
var ua = await session.GetUserAgent();
|
||||
ua.Should().NotBeNullOrWhiteSpace();
|
||||
ua.Should().Contain("IntegrationAgent/1.0");
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/ElectronNET.IntegrationTests/Tests/ShellTests.cs
Normal file
15
src/ElectronNET.IntegrationTests/Tests/ShellTests.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace ElectronNET.IntegrationTests.Tests
|
||||
{
|
||||
using ElectronNET.API;
|
||||
|
||||
[Collection("ElectronCollection")]
|
||||
public class ShellTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task OpenExternal_invalid_scheme_returns_error_or_empty()
|
||||
{
|
||||
var error = await Electron.Shell.OpenExternalAsync("mailto:test@example.com");
|
||||
(error == string.Empty || error.Contains("@") || error.Length > 0).Should().BeTrue(); // call succeeded
|
||||
}
|
||||
}
|
||||
}
|
||||
44
src/ElectronNET.IntegrationTests/Tests/ThumbarButtonTests.cs
Normal file
44
src/ElectronNET.IntegrationTests/Tests/ThumbarButtonTests.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
namespace ElectronNET.IntegrationTests.Tests
|
||||
{
|
||||
using System.Runtime.InteropServices;
|
||||
using ElectronNET.API.Entities;
|
||||
|
||||
[Collection("ElectronCollection")]
|
||||
public class ThumbarButtonTests
|
||||
{
|
||||
private readonly ElectronFixture fx;
|
||||
|
||||
public ThumbarButtonTests(ElectronFixture fx)
|
||||
{
|
||||
this.fx = fx;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SetThumbarButtons_returns_success()
|
||||
{
|
||||
var btn = new ThumbarButton("icon.png") { Tooltip = "Test" };
|
||||
var success = await this.fx.MainWindow.SetThumbarButtonsAsync(new[] { btn });
|
||||
success.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Thumbar_button_click_invokes_callback()
|
||||
{
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return; // only meaningful on Windows taskbar
|
||||
}
|
||||
|
||||
var icon = Path.Combine(Directory.GetCurrentDirectory(), "ElectronNET.WebApp", "wwwroot", "icon.png");
|
||||
if (!File.Exists(icon))
|
||||
{
|
||||
return; // skip if icon missing
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
var btn = new ThumbarButton(icon) { Tooltip = "Test", Flags = new[] { ThumbarButtonFlag.enabled }, Click = () => tcs.TrySetResult(true) };
|
||||
var ok = await this.fx.MainWindow.SetThumbarButtonsAsync(new[] { btn });
|
||||
ok.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/ElectronNET.IntegrationTests/Tests/TrayTests.cs
Normal file
26
src/ElectronNET.IntegrationTests/Tests/TrayTests.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace ElectronNET.IntegrationTests.Tests
|
||||
{
|
||||
using ElectronNET.API;
|
||||
|
||||
[Collection("ElectronCollection")]
|
||||
public class TrayTests
|
||||
{
|
||||
// ReSharper disable once NotAccessedField.Local
|
||||
private readonly ElectronFixture fx;
|
||||
public TrayTests(ElectronFixture fx)
|
||||
{
|
||||
this.fx = fx;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Can_create_tray_and_destroy()
|
||||
{
|
||||
//await Electron.Tray.Show("assets/icon.png");
|
||||
await Electron.Tray.Show(null);
|
||||
var isDestroyed = await Electron.Tray.IsDestroyedAsync();
|
||||
isDestroyed.Should().BeFalse();
|
||||
await Electron.Tray.Destroy();
|
||||
(await Electron.Tray.IsDestroyedAsync()).Should().BeTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
75
src/ElectronNET.IntegrationTests/Tests/WebContentsTests.cs
Normal file
75
src/ElectronNET.IntegrationTests/Tests/WebContentsTests.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
namespace ElectronNET.IntegrationTests.Tests
|
||||
{
|
||||
using ElectronNET.API.Entities;
|
||||
|
||||
[Collection("ElectronCollection")]
|
||||
public class WebContentsTests
|
||||
{
|
||||
private readonly ElectronFixture fx;
|
||||
|
||||
public WebContentsTests(ElectronFixture fx)
|
||||
{
|
||||
this.fx = fx;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Can_get_url_after_navigation()
|
||||
{
|
||||
var wc = this.fx.MainWindow.WebContents;
|
||||
await wc.LoadURLAsync("https://example.com");
|
||||
var url = await wc.GetUrl();
|
||||
url.Should().Contain("example.com");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteJavaScript_returns_title()
|
||||
{
|
||||
var wc = this.fx.MainWindow.WebContents;
|
||||
await wc.LoadURLAsync("https://example.com");
|
||||
var title = await wc.ExecuteJavaScriptAsync("document.title");
|
||||
title.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DomReady_event_fires()
|
||||
{
|
||||
var wc = this.fx.MainWindow.WebContents;
|
||||
var fired = false;
|
||||
wc.OnDomReady += () => fired = true;
|
||||
await wc.LoadURLAsync("https://example.com");
|
||||
await Task.Delay(500);
|
||||
fired.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Can_print_to_pdf()
|
||||
{
|
||||
var html = "data:text/html,<html><body><h1>PDF Test</h1><p>Electron.NET</p></body></html>";
|
||||
await this.fx.MainWindow.WebContents.LoadURLAsync(html);
|
||||
var tmp = Path.Combine(Path.GetTempPath(), $"electronnet_pdf_{Guid.NewGuid():N}.pdf");
|
||||
try
|
||||
{
|
||||
var ok = await this.fx.MainWindow.WebContents.PrintToPDFAsync(tmp);
|
||||
ok.Should().BeTrue();
|
||||
File.Exists(tmp).Should().BeTrue();
|
||||
new FileInfo(tmp).Length.Should().BeGreaterThan(0);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(tmp))
|
||||
{
|
||||
File.Delete(tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Can_basic_print()
|
||||
{
|
||||
var html = "data:text/html,<html><body><h2>Print Test</h2></body></html>";
|
||||
await this.fx.MainWindow.WebContents.LoadURLAsync(html);
|
||||
var ok = await this.fx.MainWindow.WebContents.PrintAsync(new PrintOptions { Silent = true, PrintBackground = true });
|
||||
ok.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "!Docs", "!Docs", "{D36CDFFD
|
||||
EndProject
|
||||
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Docs", "..\docs\Docs.shproj", "{06CAADC7-DE5B-47B4-AB2A-E9501459A2D1}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test Projects", "Test Projects", "{75129C45-FC6F-41B0-A485-07F4A7E031ED}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ElectronNET.IntegrationTests", "ElectronNET.IntegrationTests\ElectronNET.IntegrationTests.csproj", "{AE877E48-6B44-63C2-8EA0-DB58D096B553}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -86,7 +90,6 @@ Global
|
||||
{B33E9B82-B6B4-4DB0-B6EE-61CC34641518}.Release|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B33E9B82-B6B4-4DB0-B6EE-61CC34641518}.Release|Any CPU.Build.0 = Debug|Any CPU
|
||||
{829FC339-4785-4229-ABA5-53ADB544DA00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{829FC339-4785-4229-ABA5-53ADB544DA00}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{829FC339-4785-4229-ABA5-53ADB544DA00}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{829FC339-4785-4229-ABA5-53ADB544DA00}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8860606D-6847-F22A-5AED-DF4E0984DD24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
@@ -105,6 +108,10 @@ Global
|
||||
{015CB06B-6CAE-209F-E050-21C3ACA5FE9F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{06CAADC7-DE5B-47B4-AB2A-E9501459A2D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{06CAADC7-DE5B-47B4-AB2A-E9501459A2D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{AE877E48-6B44-63C2-8EA0-DB58D096B553}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AE877E48-6B44-63C2-8EA0-DB58D096B553}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AE877E48-6B44-63C2-8EA0-DB58D096B553}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{AE877E48-6B44-63C2-8EA0-DB58D096B553}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -119,6 +126,7 @@ Global
|
||||
{EE38A326-5DE8-AF09-9EB9-DF0878938783} = {EDCBFC49-2AEE-4BAF-9368-4409298C52FC}
|
||||
{015CB06B-6CAE-209F-E050-21C3ACA5FE9F} = {985D39A7-5216-4945-8167-2FD0CB387BD8}
|
||||
{06CAADC7-DE5B-47B4-AB2A-E9501459A2D1} = {D36CDFFD-3438-42E4-A7FF-88BA19AC4964}
|
||||
{AE877E48-6B44-63C2-8EA0-DB58D096B553} = {75129C45-FC6F-41B0-A485-07F4A7E031ED}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {81A62E71-9E04-4EFE-AD5C-23165375F8EF}
|
||||
|
||||
Reference in New Issue
Block a user