diff --git a/src/ElectronNET.IntegrationTests/ElectronFixture.cs b/src/ElectronNET.IntegrationTests/ElectronFixture.cs new file mode 100644 index 0000000..68a1ec9 --- /dev/null +++ b/src/ElectronNET.IntegrationTests/ElectronFixture.cs @@ -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 + { + } +} \ No newline at end of file diff --git a/src/ElectronNET.IntegrationTests/ElectronNET.IntegrationTests.csproj b/src/ElectronNET.IntegrationTests/ElectronNET.IntegrationTests.csproj new file mode 100644 index 0000000..f13ae15 --- /dev/null +++ b/src/ElectronNET.IntegrationTests/ElectronNET.IntegrationTests.csproj @@ -0,0 +1,39 @@ + + + + + true + + + + + + net8.0 + enable + enable + false + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + false + + + + + diff --git a/src/ElectronNET.IntegrationTests/GlobalUsings.cs b/src/ElectronNET.IntegrationTests/GlobalUsings.cs new file mode 100644 index 0000000..7fef4b0 --- /dev/null +++ b/src/ElectronNET.IntegrationTests/GlobalUsings.cs @@ -0,0 +1,2 @@ +global using Xunit; +global using FluentAssertions; \ No newline at end of file diff --git a/src/ElectronNET.IntegrationTests/Properties/electron-builder.json b/src/ElectronNET.IntegrationTests/Properties/electron-builder.json new file mode 100644 index 0000000..41f483c --- /dev/null +++ b/src/ElectronNET.IntegrationTests/Properties/electron-builder.json @@ -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" + } + ] + } +} \ No newline at end of file diff --git a/src/ElectronNET.IntegrationTests/Tests/AppTests.cs b/src/ElectronNET.IntegrationTests/Tests/AppTests.cs new file mode 100644 index 0000000..3e77d27 --- /dev/null +++ b/src/ElectronNET.IntegrationTests/Tests/AppTests.cs @@ -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(); + } + } +} \ No newline at end of file diff --git a/src/ElectronNET.IntegrationTests/Tests/BrowserViewTests.cs b/src/ElectronNET.IntegrationTests/Tests/BrowserViewTests.cs new file mode 100644 index 0000000..754c9b0 --- /dev/null +++ b/src/ElectronNET.IntegrationTests/Tests/BrowserViewTests.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/src/ElectronNET.IntegrationTests/Tests/BrowserWindowTests.cs b/src/ElectronNET.IntegrationTests/Tests/BrowserWindowTests.cs new file mode 100644 index 0000000..4007778 --- /dev/null +++ b/src/ElectronNET.IntegrationTests/Tests/BrowserWindowTests.cs @@ -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(); + 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); + } + } +} \ No newline at end of file diff --git a/src/ElectronNET.IntegrationTests/Tests/ClipboardTests.cs b/src/ElectronNET.IntegrationTests/Tests/ClipboardTests.cs new file mode 100644 index 0000000..1f7d74f --- /dev/null +++ b/src/ElectronNET.IntegrationTests/Tests/ClipboardTests.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/src/ElectronNET.IntegrationTests/Tests/CookiesTests.cs b/src/ElectronNET.IntegrationTests/Tests/CookiesTests.cs new file mode 100644 index 0000000..284b381 --- /dev/null +++ b/src/ElectronNET.IntegrationTests/Tests/CookiesTests.cs @@ -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(); + } + } +} \ No newline at end of file diff --git a/src/ElectronNET.IntegrationTests/Tests/GlobalShortcutTests.cs b/src/ElectronNET.IntegrationTests/Tests/GlobalShortcutTests.cs new file mode 100644 index 0000000..3256426 --- /dev/null +++ b/src/ElectronNET.IntegrationTests/Tests/GlobalShortcutTests.cs @@ -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(); + Electron.GlobalShortcut.Register(accel, () => tcs.TrySetResult(true)); + var isRegistered = await Electron.GlobalShortcut.IsRegisteredAsync(accel); + isRegistered.Should().BeTrue(); + Electron.GlobalShortcut.Unregister(accel); + } + } +} \ No newline at end of file diff --git a/src/ElectronNET.IntegrationTests/Tests/HostHookTests.cs b/src/ElectronNET.IntegrationTests/Tests/HostHookTests.cs new file mode 100644 index 0000000..254f7aa --- /dev/null +++ b/src/ElectronNET.IntegrationTests/Tests/HostHookTests.cs @@ -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("create-excel-file", "."); + result.Should().NotBeNull(); + } + } +} \ No newline at end of file diff --git a/src/ElectronNET.IntegrationTests/Tests/IpcMainTests.cs b/src/ElectronNET.IntegrationTests/Tests/IpcMainTests.cs new file mode 100644 index 0000000..9e49f46 --- /dev/null +++ b/src/ElectronNET.IntegrationTests/Tests/IpcMainTests.cs @@ -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(); + 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"); + } + } +} \ No newline at end of file diff --git a/src/ElectronNET.IntegrationTests/Tests/MenuTests.cs b/src/ElectronNET.IntegrationTests/Tests/MenuTests.cs new file mode 100644 index 0000000..511860b --- /dev/null +++ b/src/ElectronNET.IntegrationTests/Tests/MenuTests.cs @@ -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(); + } + } +} \ No newline at end of file diff --git a/src/ElectronNET.IntegrationTests/Tests/MultiEventRegistrationTests.cs b/src/ElectronNET.IntegrationTests/Tests/MultiEventRegistrationTests.cs new file mode 100644 index 0000000..2561aec --- /dev/null +++ b/src/ElectronNET.IntegrationTests/Tests/MultiEventRegistrationTests.cs @@ -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 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}"); + } + } + } +} \ No newline at end of file diff --git a/src/ElectronNET.IntegrationTests/Tests/NativeImageTests.cs b/src/ElectronNET.IntegrationTests/Tests/NativeImageTests.cs new file mode 100644 index 0000000..35d7825 --- /dev/null +++ b/src/ElectronNET.IntegrationTests/Tests/NativeImageTests.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/src/ElectronNET.IntegrationTests/Tests/NativeThemeTests.cs b/src/ElectronNET.IntegrationTests/Tests/NativeThemeTests.cs new file mode 100644 index 0000000..aeffee8 --- /dev/null +++ b/src/ElectronNET.IntegrationTests/Tests/NativeThemeTests.cs @@ -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(); + } + } +} \ No newline at end of file diff --git a/src/ElectronNET.IntegrationTests/Tests/NotificationTests.cs b/src/ElectronNET.IntegrationTests/Tests/NotificationTests.cs new file mode 100644 index 0000000..5e3439b --- /dev/null +++ b/src/ElectronNET.IntegrationTests/Tests/NotificationTests.cs @@ -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(); + } + } +} \ No newline at end of file diff --git a/src/ElectronNET.IntegrationTests/Tests/ProcessTests.cs b/src/ElectronNET.IntegrationTests/Tests/ProcessTests.cs new file mode 100644 index 0000000..edfb986 --- /dev/null +++ b/src/ElectronNET.IntegrationTests/Tests/ProcessTests.cs @@ -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(); + } + + } +} \ No newline at end of file diff --git a/src/ElectronNET.IntegrationTests/Tests/ScreenTests.cs b/src/ElectronNET.IntegrationTests/Tests/ScreenTests.cs new file mode 100644 index 0000000..31521fd --- /dev/null +++ b/src/ElectronNET.IntegrationTests/Tests/ScreenTests.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/src/ElectronNET.IntegrationTests/Tests/SessionTests.cs b/src/ElectronNET.IntegrationTests/Tests/SessionTests.cs new file mode 100644 index 0000000..3114087 --- /dev/null +++ b/src/ElectronNET.IntegrationTests/Tests/SessionTests.cs @@ -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()); + 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"); + } + } +} \ No newline at end of file diff --git a/src/ElectronNET.IntegrationTests/Tests/ShellTests.cs b/src/ElectronNET.IntegrationTests/Tests/ShellTests.cs new file mode 100644 index 0000000..1ce3fb5 --- /dev/null +++ b/src/ElectronNET.IntegrationTests/Tests/ShellTests.cs @@ -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 + } + } +} \ No newline at end of file diff --git a/src/ElectronNET.IntegrationTests/Tests/ThumbarButtonTests.cs b/src/ElectronNET.IntegrationTests/Tests/ThumbarButtonTests.cs new file mode 100644 index 0000000..4961310 --- /dev/null +++ b/src/ElectronNET.IntegrationTests/Tests/ThumbarButtonTests.cs @@ -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(); + 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(); + } + } +} \ No newline at end of file diff --git a/src/ElectronNET.IntegrationTests/Tests/TrayTests.cs b/src/ElectronNET.IntegrationTests/Tests/TrayTests.cs new file mode 100644 index 0000000..fdbd9f4 --- /dev/null +++ b/src/ElectronNET.IntegrationTests/Tests/TrayTests.cs @@ -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(); + } + } +} \ No newline at end of file diff --git a/src/ElectronNET.IntegrationTests/Tests/WebContentsTests.cs b/src/ElectronNET.IntegrationTests/Tests/WebContentsTests.cs new file mode 100644 index 0000000..066557e --- /dev/null +++ b/src/ElectronNET.IntegrationTests/Tests/WebContentsTests.cs @@ -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,

PDF Test

Electron.NET

"; + 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,

Print Test

"; + await this.fx.MainWindow.WebContents.LoadURLAsync(html); + var ok = await this.fx.MainWindow.WebContents.PrintAsync(new PrintOptions { Silent = true, PrintBackground = true }); + ok.Should().BeTrue(); + } + } +} \ No newline at end of file diff --git a/src/ElectronNET.sln b/src/ElectronNET.sln index 55de11a..3f46f97 100644 --- a/src/ElectronNET.sln +++ b/src/ElectronNET.sln @@ -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}