Compare commits

...

52 Commits

Author SHA1 Message Date
Florian Rappl
cb20fbad25 Merge pull request #991 from ElectronNET/develop
Release 0.3.1
2025-12-16 19:00:07 +01:00
Florian Rappl
89cdf2f55b Merge pull request #992 from softworkz/submit_rerun
Try fix job re-run
2025-12-16 18:53:23 +01:00
softworkz
1c62c1f132 Try fix job re-run 2025-12-16 16:02:43 +01:00
Florian Rappl
d3e3188681 Prepare for 0.3.1 2025-12-16 15:51:39 +01:00
Florian Rappl
4f95043309 Merge pull request #990 from softworkz/submit_packageid
Use ElectronPackageId instead of PackageId
2025-12-16 15:48:43 +01:00
softworkz
13f1203ccc Use ElectronPackageId instead of PackageId 2025-12-16 14:54:20 +01:00
Florian Rappl
bdfbcd5b77 Merge pull request #986 from ElectronNET/develop
Release 0.3.0
2025-12-14 22:13:10 +01:00
Florian Rappl
19e785f53f Fixed whitespace 2025-12-14 15:30:21 +01:00
Florian Rappl
0b453177b5 Added contributing guide 2025-12-14 15:20:15 +01:00
Florian Rappl
8ed7f27722 Make sure the electron-host-hook strays unobtrusive 2025-12-14 00:49:34 +01:00
Florian Rappl
321b8cd9e5 Merge branch 'develop' of https://github.com/ElectronNET/Electron.NET into develop 2025-12-14 00:36:59 +01:00
Florian Rappl
b326f863d8 Updated changelog 2025-12-14 00:36:53 +01:00
Florian Rappl
f148fe6a14 Merge pull request #983 from markatosi/develop
Wiki Package building macOS sections added
2025-12-14 00:32:37 +01:00
Florian Rappl
515d325731 Fixed startup in VS Code 2025-12-13 23:43:12 +01:00
markatosi
f32a40fe17 Add macOS profiles for ASP.NET and Console apps
Added profiles for ASP.NET and Console applications on macOS.
2025-12-13 12:18:38 -10:00
Florian Rappl
c53a5a9163 Merge pull request #982 from markatosi/develop
Change Example code for AutoMenuHide
2025-12-13 12:28:41 +01:00
markatosi
8d4cdddc46 Conditionally set AutoHideMenuBar for Windows/Linux 2025-12-12 13:45:56 -10:00
Florian Rappl
70e8f85123 Merge pull request #977 from softworkz/submit_revertnet6
Combine and separate workflows
2025-12-12 07:23:26 +01:00
softworkz
5a7cbd972f Combine and separate workflows
- Separate between PR and Push execution (with and without secrets)
- Turn tests into re-usable workflows and call them from the main ones
2025-12-12 02:15:44 +01:00
softworkz
33da428c45 Fix build after PR merge
Re-adding .net6 caused the collection expression to fail
2025-12-12 01:39:02 +01:00
softworkz
f1b4766360 Add automatic retry for failed test jobs 2025-12-12 00:51:13 +01:00
softworkz
e070759645 integration-tests: Add random delay 2025-12-12 00:49:16 +01:00
Florian Rappl
49eaa5e129 Merge pull request #973 from davidroth/set-menu-items
Add dynamic tray menu update functionality
2025-12-11 22:58:06 +01:00
Florian Rappl
b3f5a3c52b Merge pull request #976 from softworkz/submit_revertnet6
Partially revert "Drop .NET 6.0, default to .NET 10.0 and update dependencies"
2025-12-11 22:57:40 +01:00
softworkz
0d732ce99e Partially revert "Drop .NET 6.0, default to .NET 10.0 and update dependencies" 2025-12-11 22:35:45 +01:00
David Roth
218b99808b Add dynamic tray menu update functionality
Introduced a `SetMenuItems` method in `Tray.cs` to enable updating the tray's context menu dynamically. This method clears existing menu items, adds new ones, and registers click handlers. Added XML documentation for the method.
2025-12-11 18:04:31 +01:00
Florian Rappl
7f507a6d86 Merge pull request #975 from davidroth/missing-usings
Fix build by adding necessary using directives for async and DI setup.
2025-12-11 10:55:17 +01:00
Florian Rappl
81b3793966 Merge pull request #974 from davidroth/dotnet-targets
Drop .NET 6.0, default to .NET 10.0 and update dependencies
2025-12-11 10:54:44 +01:00
David Roth
1baa5c6200 Drop .NET 6.0, default to .NET 10.0 and update dependencies
Updated all projects to target .NET 10.0, removing support for .NET 6.0. Upgraded key dependencies, including:
- `Microsoft.Build` and `Microsoft.Build.Tasks.Core` to 18.0.2.
- `Nuke.Common` to 10.1.0.
- `System.Drawing.Common` to 10.0.1.
- `System.Text.Json` to 10.0.1.

Removed `NoWarn` configurations for .NET 6.0 in `ElectronNET.AspNet.csproj`. Reformatted the `Copy` task in `ElectronNET.Build.csproj` for readability. Updated `.pubxml` files and other project files to reflect the framework upgrade.
2025-12-11 07:25:07 +01:00
David Roth
5f76804065 Fix buidl by adding necessary using directives for async and DI setup. 2025-12-11 07:23:30 +01:00
Florian Rappl
70ffebf39d Updated changelog 2025-12-10 23:03:57 +01:00
Florian Rappl
0a54735f35 Merge branch 'main' of https://github.com/ElectronNET/Electron.NET into develop 2025-12-10 22:54:56 +01:00
Florian Rappl
1ac371b3da Merge pull request #962 from softworkz/submit_testupd
Collection of test fixes
2025-12-09 15:23:33 +01:00
Florian Rappl
e854451043 Merge pull request #968 from adityashirsatrao007/feat/host-hook-sample
feat: Add ElectronHostHook sample application (#967)
2025-12-09 10:21:36 +01:00
Aditya
331c2f548c fix: address review comments (protocol, config, startup) 2025-12-07 23:08:09 +05:30
Aditya
b00adcbd38 feat: Add ElectronHostHook sample application (#967) 2025-12-07 22:57:35 +05:30
Florian Rappl
ed6d24dbb1 Merge pull request #964 from niteshsinghal85/custom_main_example
Add custom_main.js guide to documentation
2025-12-07 18:08:26 +01:00
Nitesh Singhal
bbc7c79d5a Merge branch 'custom_main_example' of https://github.com/niteshsinghal85/Electron.NET into custom_main_example 2025-12-07 18:17:26 +05:30
Nitesh Singhal
2389ae32bd Update Custom_main.md to clarify usage of custom_main.js 2025-12-07 18:16:48 +05:30
Nitesh Singhal
42fecbdc98 Update docs/_Sidebar.md
Co-authored-by: softworkz <4985349+softworkz@users.noreply.github.com>
2025-12-07 18:10:16 +05:30
softworkz
95e02e2655 Introduce common base class for tests 2025-12-07 11:56:32 +01:00
softworkz
153625ba51 Introduce IntegrationFactAttribute 2025-12-07 11:56:26 +01:00
softworkz
c97f914e7a Introduce TimeSpan extensions 2025-12-07 11:20:43 +01:00
Nitesh Singhal
d95b41fbae Add custom_main.js guide to documentation 2025-12-07 15:00:28 +05:30
softworkz
2b2b26e13b GetSetUserAgent 2025-12-06 21:08:47 +01:00
softworkz
9bb2adca78 Use 3s timeout for GetUserAgentAsync and updat test 2025-12-06 20:53:15 +01:00
softworkz
88d2daacb1 WebContentsTests: Experiment!
Re-enable some tests for CI as experiment
2025-12-06 20:35:51 +01:00
softworkz
c90f003519 GetSetZoomLevel: Don't use shared window
Because we need a visible window and the main window
visibility must not be mutated by tests
2025-12-06 20:11:27 +01:00
softworkz
daa9f399e9 ScreenTests: Remove invalid constraints
Negative values for cursor position are not invalid
2025-12-06 20:09:05 +01:00
softworkz
6246b44d68 ReadyToShow_event_fires: Destroy window 2025-12-06 20:08:01 +01:00
softworkz
347c1ef0e4 Show_hide_visibility_roundtrip: Don't use shared window 2025-12-06 20:06:34 +01:00
softworkz
7c8eeef225 GetPrintersAsync: Increase timeout 2025-12-06 20:04:23 +01:00
77 changed files with 1388 additions and 513 deletions

137
.github/CONTRIBUTING.md vendored Normal file
View File

@@ -0,0 +1,137 @@
# Contributing
## Project Scope
The Electron.NET project ultimately tries to provide a framework for developing cross-platform client applications on the basis of .NET and Electron. Anything that is related to this goal will be considered. The project aims to be as close to Electron with .NET as a basis as possible. If your contribution does not reflect that goal, the chances of accepting it are limited.
## Code License
This is an open source project falling under the [MIT License](../LICENSE). By using, distributing, or contributing to this project, you accept and agree that all code within the Electron.NET project and its libraries are licensed under MIT license.
## Becoming a Contributor
Usually appointing someone as a contributor follows this process:
1. An individual contributes actively via discussions (reporting bugs, giving feedback to existing or opening new issues) and / or pull requests
2. The individual is either directly asked, invited or asks for contributor rights on the project
3. The individual uses the contribution rights to sustain or increase the active contributions
Every contributor might have to sign the contributor's license agreement (CLA) to establish a legal trust between the project and its contributors.
## Working on Electron.NET
### Issue Discussion
Discussion of issues should be placed transparently in the issue tracker here on GitHub.
* [General issues, bugs, new features](https://github.com/ElectronNET/Electron.NET/issues)
* [General discussions, help, exchange of ideas](https://github.com/ElectronNET/Electron.NET/discussions)
### Modifying the code
Electron.NET and its libraries uses features from the latest versions of C# (e.g., C# 10). You will therefore need a C# compiler that is up for the job.
1. Fork and clone the repo.
2. First try to build the ElectronNET.Core library and see if you get the tests running.
3. You will be required to resolve some dependencies via NuGet.
The build system of Electron.NET uses NUKE.
### Code Conventions
Most parts in the Electron.NET project are fairly straight forward. Among these are:
* Always use statement blocks for control statements, e.g., in a for-loop, if-condition, ...
* You may use a simple (throw) statement in case of enforcing contracts on argument
* Be explicit about modifiers (some files follow an older convention of the code base, but we settled on the explicit style)
### Development Workflow
1. If no issue already exists for the work you'll be doing, create one to document the problem(s) being solved and self-assign.
2. Otherwise please let us know that you are working on the problem. Regular status updates (e.g. "still in progress", "no time anymore", "practically done", "pull request issued") are highly welcome.
3. Create a new branch—please don't work in the `main` branch directly. It is reserved for releases. We recommend naming the branch to match the issue being addressed (`feature/#777` or `issue-777`).
4. Add failing tests for the change you want to make. Tests are crucial and should be taken from W3C (or other specification).
5. Fix stuff. Always go from edge case to edge case.
6. All tests should pass now. Also your new implementation should not break existing tests.
7. Update the documentation to reflect any changes. (or document such changes in the original issue)
8. Push to your fork or push your issue-specific branch to the main repository, then submit a pull request against `develop`.
Just to illustrate the git workflow for Electron.NET a little bit more we've added the following graphs.
Initially, Electron.NET starts at the `main` branch. This branch should contain the latest stable (or released) version.
Here we now created a new branch called `develop`. This is the development branch.
Now active work is supposed to be done. Therefore a new branch should be created. Let's create one:
```sh
git checkout -b feature/#777
```
There may be many of these feature branches. Most of them are also pushed to the server for discussion or synchronization.
```sh
git push -u origin feature/#777
```
Now feature branches may be closed when they are done. Here we simply merge with the feature branch(es). For instance the following command takes the `feature/#777` branch from the server and merges it with the `develop` branch.
```sh
git checkout develop
git pull
git pull origin feature/#777
git push
```
Finally, we may have all the features that are needed to release a new version of Electron.NET. Here we tag the release. For instance for the 1.0 release we use `v1.0`.
```sh
git checkout main
git merge develop
git tag v1.0
```
(The last part is automatically performed by our CI system. Don't tag manually.)
### Versioning
The rules of [semver](http://semver.org/) don't necessarily apply here, but we will try to stay quite close to them.
Prior to version 1.0.0 we use the following scheme:
1. MINOR versions for reaching a feature milestone potentially combined with dramatic API changes
2. PATCH versions for refinements (e.g. performance improvements, bug fixes)
After releasing version 1.0.0 the scheme changes to become:
1. MAJOR versions at maintainers' discretion following significant changes to the codebase (e.g., API changes)
2. MINOR versions for backwards-compatible enhancements (e.g., performance improvements)
3. PATCH versions for backwards-compatible bug fixes (e.g., spec compliance bugs, support issues)
#### Code style
Regarding code style like indentation and whitespace, **follow the conventions you see used in the source already.** In general most of the [C# coding guidelines from Microsoft](https://msdn.microsoft.com/en-us/library/ff926074.aspx) are followed. This project prefers type inference with `var` to explicitly stating (redundant) information.
It is also important to keep a certain `async`-flow and to always use `ConfigureAwait(false)` in conjunction with an `await` expression.
## Backwards Compatibility
We always try to remain backwards compatible beyond the currently supported versions of .NET.
For instance, in December 2025 there have been activity to remove .NET 6 support from the codebase. We rejected this. Key points:
1. We have absolutely no need to drop `.net6` support. It doesn't hurt us in any way.
2. Many are still using `.net6`, including Electron.NET (non-Core) users. It doesn't make sense to force them to update two things at the same time (.NET + Electron.NET).
3. We MUST NOT and NEVER update `Microsoft.Build.Utilities.Core`. This will make Electron.NET stop working on older Visual Studio and MSBuild versions. There's are also no reasons to update it in the first place.
It's important to note that the Microsoft label of "Out of support" on .NET has almost no practical meaning. We've rarely (if ever) seen any bugs fixed in the same .NET version which mattered. The bugs that all new .NET versions have are much worse than mature .NET versions which are declared as "out of support". Keep in mind that the LTS matters most for active development / ongoing supported projects. If, e.g., a TV has been released a decade ago it most likely won't be patched. Still, you might want to deploy applications to it, which then naturally would involve being based on "out of support" versions of the framework.
TL;DR: Unless there is a technical reason (e.g., a crucial new API not being available) we should not drop "out of support" .NET versions. At the time of writing (December 2025) the minimum supported .NET version remains at `.net6`.
## Timeline
**All of this information is related to ElectronNET.Core pre-v1!**
We pretty much release whenever we have something new (i.e., do fixes such as a 0.1.1, or add new features, such as a 0.2.0) quite quickly.
We will go for a 1.0.0 release of this as early as ~mid of January 2026 (unless we find some critical things or want to extend the beta phase for ElectronNET.Core). This should be sufficient time to get some user input and have enough experience to call it stable.

View File

@@ -1,33 +1,25 @@
name: CI
name: Build and Publish
on: [push, pull_request]
on: [push]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
concurrency:
group: build-publish-${{ github.ref }}
cancel-in-progress: true
jobs:
# linux:
# runs-on: ubuntu-latest
# timeout-minutes: 10
Integration-Tests:
uses: ./.github/workflows/integration-tests.yml
name: '1'
# steps:
# - uses: actions/checkout@v4
# - name: Setup dotnet
# uses: actions/setup-dotnet@v4
# with:
# dotnet-version: |
# 6.0.x
# 8.0.x
# 10.0.x
# - name: Build
# run: ./build.sh
windows:
Publish:
needs: [Integration-Tests]
runs-on: windows-latest
timeout-minutes: 10
name: '2 / Publish'
steps:
- uses: actions/checkout@v4

39
.github/workflows/PR Validation.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
name: PR Validation
on: [pull_request]
concurrency:
group: pr-validation-${{ github.ref }}
cancel-in-progress: true
jobs:
Whitespace-Check:
uses: ./.github/workflows/trailing-whitespace-check.yml
secrets: inherit
name: '1'
Tests:
needs: Whitespace-Check
uses: ./.github/workflows/integration-tests.yml
secrets: inherit
name: '2'
build:
needs: [Whitespace-Check, Tests]
runs-on: windows-latest
timeout-minutes: 10
name: '3 / Build'
steps:
- uses: actions/checkout@v4
- name: Setup dotnet
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
6.0.x
8.0.x
10.0.x
- name: Build
run: .\build.ps1

View File

@@ -1,10 +1,7 @@
name: Tests
name: Tests
on:
push:
branches: [ develop, main ]
pull_request:
branches: [ develop, main ]
workflow_call:
concurrency:
group: integration-tests-${{ github.ref }}
@@ -12,7 +9,7 @@ concurrency:
jobs:
tests:
name: Integration Tests (${{ matrix.os }} / Electron ${{ matrix.electronVersion }})
name: ${{ matrix.os }} API-${{ matrix.electronVersion }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
@@ -45,6 +42,13 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Random delay (0-20 seconds)
shell: bash
run: |
DELAY=$((RANDOM % 21))
echo "Waiting for $DELAY seconds..."
sleep $DELAY
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:

View File

@@ -2,7 +2,7 @@ name: Create PR Comments
on:
workflow_run:
workflows: ["Tests"]
workflows: [ "PR Validation" ]
types: [completed]
permissions:
@@ -14,7 +14,7 @@ jobs:
pr-comment:
name: Post Test Result as PR comment
runs-on: ubuntu-24.04
if: github.event.workflow_run.event == 'pull_request'
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion != 'cancelled'
steps:
- name: Download CTRF artifact

50
.github/workflows/retry-test-jobs.yml vendored Normal file
View File

@@ -0,0 +1,50 @@
name: Tests auto-rerun
on:
workflow_run:
workflows: [ "PR Validation", "Build and Publish" ]
types: [ completed ]
jobs:
rerun-failed-matrix-jobs-once:
if: >
${{
github.event.workflow_run.conclusion == 'failure' &&
github.event.workflow_run.run_attempt == 1
}}
runs-on: ubuntu-24.04
permissions:
actions: write
contents: read
steps:
- name: Decide whether to rerun (only if matrix jobs failed)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
RUN_ID: ${{ github.event.workflow_run.id }}
run: |
echo "Inspecting jobs of workflow run $RUN_ID in $REPO"
jobs_json="$(gh api -R $REPO repos/$REPO/actions/runs/$RUN_ID/jobs)"
echo "Jobs and conclusions:"
echo "$jobs_json" | jq '.jobs[] | {name: .name, conclusion: .conclusion}'
failed_matrix_jobs=$(echo "$jobs_json" | jq '
[ .jobs[]
| select(.conclusion == "failure"
and (.name | contains(" API-")))
]
| length
')
echo "Failed Integration Tests matrix jobs: $failed_matrix_jobs"
if [ "$failed_matrix_jobs" -gt 0 ]; then
echo "Detected failing Integration Tests jobs re-running failed jobs for this run."
gh run rerun -R $REPO "$RUN_ID" --failed
else
echo "Only non-matrix jobs (like Test Results) failed not auto-rerunning."
fi

View File

@@ -1,11 +1,10 @@
name: Trailing Whitespace Check
name: Whitespace Check
on:
pull_request:
types: [opened, synchronize, reopened]
workflow_call:
jobs:
check-trailing-whitespace:
check-whitespace:
runs-on: ubuntu-latest
permissions:
contents: read

View File

@@ -1,10 +1,30 @@
# 0.3.1
## ElectronNET.Core
- Fixed issue transforming the project ID (#989, #990) @softworkz
# 0.3.0
## ElectronNET.Core
- Updated infrastructure (#937, #939) @softworkz
- Updated all model classes to Electron API 39.2 (#949) @softworkz
- Fixed output path for `electron-builder` (#942) @softworkz
- Fixed floating point display resolution (#944) @softworkz
- Fixed error in case of missing electron-host-hook (#978)
- Fixed previous API break using exposed `JsonElement` objects (#938) @softworkz
- Fixed and improved several test cases (#962) @softworkz
- Fixed startup of Electron.NET from VS Code Debug Adapter (#952)
- Fixed the `BrowserWindowOptions` (#945) @softworkz
- Fixed example for `AutoMenuHide` to reflect platform capabilities (#982) @markatosi
- Added several migration checks for publishing (#966) @softworkz
- Added more test runners for E2E tests (#950, #951) @agracio
- Added dynamic updates for tray menu (#973) @davidroth
- Added matrix tests with 6 runners and 2 electron version (#948) @softworkz
- Added additional APIs for WebContents (#958) @agracio
- Added documentation for MacOS package publish (#983) @markatosi
- Added sample application for `ElectronHostHook` (#967) @adityashirsatrao007
# 0.2.0

View File

@@ -133,9 +133,10 @@ builder.UseElectron(args, async () =>
{
var options = new BrowserWindowOptions {
Show = false,
AutoHideMenuBar = true,
IsRunningBlazor = true, // <-- crucial
};
if (OperatingSystem.IsWindows() || OperatingSystem.IsLinux())
options.AutoHideMenuBar = true;
var browserWindow = await Electron.WindowManager.CreateWindowAsync(options);
browserWindow.OnReadyToShow += () => browserWindow.Show();
});

View File

@@ -41,7 +41,7 @@ A `package.json` file **is allowed** in the `ElectronHostHook` folder if you're
```xml
<PropertyGroup Label="ElectronNetCommon">
<PackageId>my-electron-app</PackageId>
<ElectronPackageId>my-electron-app</ElectronPackageId>
<Title>My Electron App</Title>
<Version>1.0.0</Version>
<Description>My awesome Electron.NET application</Description>

View File

@@ -54,7 +54,7 @@ Add the Electron.NET configuration to your `.csproj` file:
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ElectronNET.Core" Version="0.3.0" />
<PackageReference Include="ElectronNET.Core" Version="0.3.1" />
</ItemGroup>
```

View File

@@ -25,7 +25,7 @@ These are the current default values when you don't make any changes:
<ElectronSingleInstance>true</ElectronSingleInstance>
<ElectronSplashScreen></ElectronSplashScreen>
<ElectronIcon></ElectronIcon>
<PackageId>$(MSBuildProjectName.Replace(".", "-").ToLower())</PackageId>
<ElectronPackageId>$(MSBuildProjectName.Replace(".", "-").ToLower())</ElectronPackageId>
<ElectronBuilderJson>electron-builder.json</ElectronBuilderJson>
<Title>$(MSBuildProjectName)</Title>
</PropertyGroup>
@@ -38,15 +38,15 @@ Since electron builder still expects a `package.json` file to exist, ElectronNET
```json
{
"name": "$(PackageId)",
"name": "$(ElectronPackageId)",
"productName": "$(ElectronTitle)",
"build": {
"appId": "$(PackageId)",
"appId": "$(ElectronPackageId)",
"linux": {
"desktop": {
"entry": { "Name": "$(Title)" }
},
"executableName": "$(PackageId)"
"executableName": "$(ElectronPackageId)"
},
"deb": {
"desktop": {

74
docs/Using/Custom_main.md Normal file
View File

@@ -0,0 +1,74 @@
# Using custom_main.js
This guide explains how to include and use a `custom_main.js` file in your Electron.NET application for advanced Electron/Node.js customization.
## Why use custom_main.js?
- Register custom protocol handlers (e.g., `myapp://`) — protocols must be registered before the app is fully initialized
- Integrate Node.js modules (e.g., telemetry, OS APIs)
- Control startup logic (abort, environment checks)
- Set up IPC messaging or preload scripts
## Step-by-Step Process
### 1. Create the custom_main.js file
Place your custom logic in `electron/custom_main.js`:
```javascript
module.exports.onStartup = function(host) {
// Example: Register a global shortcut for opening dev tools
const { app, globalShortcut, BrowserWindow } = require('electron');
app.on('ready', () => {
const ret = globalShortcut.register('Control+Shift+I', () => {
BrowserWindow.getAllWindows().forEach(win => win.webContents.openDevTools());
console.log('Ctrl+Shift+I is pressed: DevTools opened!');
});
});
app.on('will-quit', () => {
globalShortcut.unregisterAll();
});
return true;
};
```
### 2. Configure your .csproj to copy custom_main.js to output
Add this to your `.csproj` file:
```xml
<ItemGroup>
<None Update="electron\custom_main.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>.electron\custom_main.js</TargetPath>
</None>
</ItemGroup>
```
### 3. Build and run your app
Use the standard build/run commands:
```powershell
dotnet build
dotnet run
```
Electron.NET will automatically load and execute your `custom_main.js` before initializing the .NET backend.
## Advanced Usage
Use environment variables to control features:
```javascript
const env = process.env.ASPNETCORE_ENVIRONMENT || 'Production';
if (env === 'Development') { /* enable dev features */ }
```
## Notes
- `custom_main.js` must use CommonJS syntax (`module.exports.onStartup = ...`).
- Place the file in your source directory and copy it to `.electron` using `.csproj`.
- Electron.NET will abort startup if `onStartup` returns `false`.
### Complete example is available here [ElectronNetSampleApp](https://github.com/niteshsinghal85/ElectronNetSampleApp)

View File

@@ -62,6 +62,52 @@ Add publish profiles to `Properties/PublishProfiles/`:
</Project>
```
#### ASP.NET Application Profile (macOS Apple Silicon ARM64)
**osx-arm64.pubxml:**
```xml
<?xml version="1.0" encoding="utf-8"?>
<Project>
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<DeleteExistingFiles>true</DeleteExistingFiles>
<PublishProvider>FileSystem</PublishProvider>
<PublishUrl>publish\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\</PublishUrl>
<WebPublishMethod>FileSystem</WebPublishMethod>
<_TargetId>Folder</_TargetId>
<TargetFramework>net10.0</TargetFramework>
<RuntimeIdentifier>osx-arm64</RuntimeIdentifier>
<ProjectGuid>48eff821-2f4d-60cc-aa44-be0f1d6e5f35</ProjectGuid>
<SelfContained>true</SelfContained>
</PropertyGroup>
</Project>
```
#### ASP.NET Application Profile (macOS Intel x64)
**osx-x64.pubxml:**
```xml
<?xml version="1.0" encoding="utf-8"?>
<Project>
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<DeleteExistingFiles>true</DeleteExistingFiles>
<PublishProvider>FileSystem</PublishProvider>
<PublishUrl>publish\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\</PublishUrl>
<WebPublishMethod>FileSystem</WebPublishMethod>
<_TargetId>Folder</_TargetId>
<TargetFramework>net10.0</TargetFramework>
<RuntimeIdentifier>osx-x64</RuntimeIdentifier>
<ProjectGuid>48eff821-2f4d-60cc-aa44-be0f1d6e5f35</ProjectGuid>
<SelfContained>true</SelfContained>
</PropertyGroup>
</Project>
```
#### Console Application Profile (Windows)
**win-x64.pubxml:**
@@ -103,6 +149,46 @@ Add publish profiles to `Properties/PublishProfiles/`:
</Project>
```
#### Console Application Profile (macOS Apple Silicon ARM64)
**osx-arm64.pubxml:**
```xml
<?xml version="1.0" encoding="utf-8"?>
<Project>
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<PublishDir>publish\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<TargetFramework>net10.0</TargetFramework>
<RuntimeIdentifier>osx-arm64</RuntimeIdentifier>
<SelfContained>false</SelfContained>
<PublishSingleFile>false</PublishSingleFile>
</PropertyGroup>
</Project>
```
#### Console Application Profile (macOS Intel x64)
**osx-x64.pubxml:**
```xml
<?xml version="1.0" encoding="utf-8"?>
<Project>
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<PublishDir>publish\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<TargetFramework>net10.0</TargetFramework>
<RuntimeIdentifier>osx-x64</RuntimeIdentifier>
<SelfContained>false</SelfContained>
<PublishSingleFile>false</PublishSingleFile>
</PropertyGroup>
</Project>
```
### Step 2: Configure Electron Builder
ElectronNET.Core automatically adds a default `electron-builder.json` file under `Properties\electron-builder.json`.

View File

@@ -24,6 +24,7 @@
- [Startup-Methods](Using/Startup-Methods.md)
- [Debugging](Using/Debugging.md)
- [Package Building](Using/Package-Building.md)
- [Adding a `custom_main.js`](Using/Custom_main.md)
# API Reference

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<RootNamespace></RootNamespace>
<NoWarn>CS0649;CS0169</NoWarn>
<NukeRootDirectory>..</NukeRootDirectory>
@@ -11,9 +11,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build" Version="17.11.48" />
<PackageReference Include="Microsoft.Build.Tasks.Core" Version="17.11.48" />
<PackageReference Include="Nuke.Common" Version="9.0.4" />
<PackageReference Include="Microsoft.Build" Version="18.0.2" />
<PackageReference Include="Microsoft.Build.Tasks.Core" Version="18.0.2" />
<PackageReference Include="Nuke.Common" Version="10.1.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -31,7 +31,7 @@ namespace ElectronNET.API
CamelCase,
}
private const int InvocationTimeout = 1000;
private static readonly TimeSpan InvocationTimeout = 1000.ms();
private readonly string objectName;
private readonly ConcurrentDictionary<string, Invocator> invocators;
@@ -120,7 +120,7 @@ namespace ElectronNET.API
return this.InvokeAsyncWithTimeout<T>(InvocationTimeout, arg, callerName);
}
protected Task<T> InvokeAsyncWithTimeout<T>(int invocationTimeout, object arg = null, [CallerMemberName] string callerName = null)
protected Task<T> InvokeAsyncWithTimeout<T>(TimeSpan invocationTimeout, object arg = null, [CallerMemberName] string callerName = null)
{
Debug.Assert(callerName != null, nameof(callerName) + " != null");
@@ -245,7 +245,7 @@ namespace ElectronNET.API
private readonly Task<T> tcsTask;
private TaskCompletionSource<T> tcs;
public Invocator(ApiBase apiBase, string callerName, int timeoutMs, object arg = null)
public Invocator(ApiBase apiBase, string callerName, TimeSpan timeout, object arg = null)
{
this.tcs = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
this.tcsTask = this.tcs.Task;
@@ -306,7 +306,7 @@ namespace ElectronNET.API
_ = apiBase.Id >= 0 ? BridgeConnector.Socket.Emit(messageName, apiBase.Id) : BridgeConnector.Socket.Emit(messageName);
}
System.Threading.Tasks.Task.Delay(timeoutMs).ContinueWith(_ =>
System.Threading.Tasks.Task.Delay(timeout).ContinueWith(_ =>
{
if (this.tcs != null)
{
@@ -314,7 +314,7 @@ namespace ElectronNET.API
{
if (this.tcs != null)
{
var ex = new TimeoutException($"No response after {timeoutMs:D}ms trying to retrieve value {apiBase.objectName}.{callerName}()");
var ex = new TimeoutException($"No response after {timeout:D}ms trying to retrieve value {apiBase.objectName}.{callerName}()");
this.tcs.TrySetException(ex);
this.tcs = null;
}

View File

@@ -220,11 +220,33 @@ namespace ElectronNET.API
_items.Clear();
_items.AddRange(menuItems);
RegisterMenuItemClickedHandler();
}
/// <summary>
/// Sets the tray menu items.
/// </summary>
/// <remarks>Calling this method updates the context menu with the specified items. Any previously
/// set menu items will be replaced.</remarks>
/// <param name="menuItems">An array of <see cref="MenuItem"/> objects representing the menu items to display in the tray menu.
/// Cannot be null.</param>
public async Task SetMenuItems(MenuItem[] menuItems)
{
menuItems.AddMenuItemsId();
await BridgeConnector.Socket.Emit("set-contextMenu", new object[] { menuItems }).ConfigureAwait(false);
_items.Clear();
_items.AddRange(menuItems);
RegisterMenuItemClickedHandler();
}
private void RegisterMenuItemClickedHandler()
{
BridgeConnector.Socket.Off("trayMenuItemClicked");
BridgeConnector.Socket.On<string>("trayMenuItemClicked", (id) =>
{
MenuItem menuItem = _items.GetMenuItem(id);
menuItem?.Click();
menuItem?.Click?.Invoke();
});
}
@@ -343,4 +365,4 @@ namespace ElectronNET.API
public async Task Once<T>(string eventName, Action<T> action)
=> await Events.Instance.Once(ModuleName, eventName, action).ConfigureAwait(false);
}
}
}

View File

@@ -1,6 +1,7 @@
using ElectronNET.API.Entities;
using System;
using System.Threading.Tasks;
using ElectronNET.API.Entities;
using ElectronNET.Common;
// ReSharper disable InconsistentNaming
@@ -173,7 +174,7 @@ public class WebContents : ApiBase
/// Get system printers.
/// </summary>
/// <returns>printers</returns>
public Task<PrinterInfo[]> GetPrintersAsync() => this.InvokeAsyncWithTimeout<PrinterInfo[]>(5_000);
public Task<PrinterInfo[]> GetPrintersAsync() => this.InvokeAsyncWithTimeout<PrinterInfo[]>(8.seconds());
/// <summary>
/// Prints window's web page.
@@ -388,7 +389,7 @@ public class WebContents : ApiBase
/// Returns string - The user agent for this web page.
/// </summary>
/// <returns></returns>
public Task<string> GetUserAgentAsync() => InvokeAsync<string>();
public Task<string> GetUserAgentAsync() => InvokeAsyncWithTimeout<string>(3.seconds());
/// <summary>
/// Overrides the user agent for this web page.

View File

@@ -0,0 +1,74 @@
// <copyright file="TimeSpanExtensions.cs" company="Emby LLC">
// Copyright © Emby LLC. All rights reserved.
// </copyright>
namespace ElectronNET.Common
{
using System;
using System.Diagnostics.CodeAnalysis;
/// <summary>
/// The TimeSpanExtensions class.
/// </summary>
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "OK")]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "OK")]
[SuppressMessage("ReSharper", "StyleCop.SA1300", Justification = "OK")]
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "OK")]
internal static class TimeSpanExtensions
{
public static TimeSpan ms(this int value)
{
return TimeSpan.FromMilliseconds(value);
}
public static TimeSpan ms(this long value)
{
return TimeSpan.FromMilliseconds(value);
}
public static TimeSpan seconds(this int value)
{
return TimeSpan.FromSeconds(value);
}
public static TimeSpan minutes(this int value)
{
return TimeSpan.FromMinutes(value);
}
public static TimeSpan hours(this int value)
{
return TimeSpan.FromHours(value);
}
public static TimeSpan days(this int value)
{
return TimeSpan.FromDays(value);
}
public static TimeSpan ms(this double value)
{
return TimeSpan.FromMilliseconds(value);
}
public static TimeSpan seconds(this double value)
{
return TimeSpan.FromSeconds(value);
}
public static TimeSpan minutes(this double value)
{
return TimeSpan.FromMinutes(value);
}
public static TimeSpan hours(this double value)
{
return TimeSpan.FromHours(value);
}
public static TimeSpan days(this double value)
{
return TimeSpan.FromDays(value);
}
}
}

View File

@@ -28,12 +28,13 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SocketIOClient" Version="3.1.2" />
<PackageReference Include="System.Drawing.Common" Version="8.0.16" />
<PackageReference Include="System.Text.Json" Version="8.0.5" />
<PackageReference Include="System.Drawing.Common" Version="8.0.22" />
<PackageReference Include="System.Text.Json" Version="8.0.6" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="ElectronNET.AspNet" />
<InternalsVisibleTo Include="ElectronNET.Core.AspNet" />
<InternalsVisibleTo Include="ElectronNET.IntegrationTests" />
</ItemGroup>
</Project>

View File

@@ -76,7 +76,7 @@
{
try
{
await Task.Delay(10).ConfigureAwait(false);
await Task.Delay(10.ms()).ConfigureAwait(false);
Console.Error.WriteLine("[StartInternal]: startCmd: {0}", startCmd);
Console.Error.WriteLine("[StartInternal]: args: {0}", args);
@@ -85,7 +85,7 @@
this.process.ProcessExited += this.Process_Exited;
this.process.Run(startCmd, args, directoriy);
await Task.Delay(500).ConfigureAwait(false);
await Task.Delay(500.ms()).ConfigureAwait(false);
Console.Error.WriteLine("[StartInternal]: after run:");

View File

@@ -61,6 +61,11 @@
{
ElectronNetRuntime.OnAppReadyCallback = onAppReadyCallback;
// no matter how this is set - let's unset to prevent Electron not starting as expected
// e.g., VS Code sets this env variable, but this will cause `require("electron")` to not
// work as expected, see issue #952
Environment.SetEnvironmentVariable("ELECTRON_RUN_AS_NODE", null);
var webPort = PortHelper.GetFreePort(ElectronNetRuntime.AspNetWebPort ?? ElectronNetRuntime.DefaultWebPort);
ElectronNetRuntime.AspNetWebPort = webPort;

View File

@@ -8,7 +8,7 @@
<Import Project="..\ElectronNET\build\ElectronNET.Core.props" Condition="$(ElectronNetDevMode)" />
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<OutputType>exe</OutputType>
</PropertyGroup>
<PropertyGroup>
@@ -70,7 +70,7 @@
<ProjectReference Include="..\ElectronNET.API\ElectronNET.API.csproj" Condition="$(ElectronNetDevMode)" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ElectronNET.Core" Version="0.3.0" Condition="'$(ElectronNetDevMode)' != 'true'" />
<PackageReference Include="ElectronNET.Core" Version="0.3.1" Condition="'$(ElectronNetDevMode)' != 'true'" />
</ItemGroup>
<Import Project="..\ElectronNET\build\ElectronNET.Core.targets" Condition="$(ElectronNetDevMode)" />

View File

@@ -7,7 +7,7 @@
<PublishDir>publish\Release\net8.0\linux-x64</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<_TargetId>Folder</_TargetId>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<SelfContained>false</SelfContained>
<PublishSingleFile>false</PublishSingleFile>

View File

@@ -12,7 +12,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<PublishUrl>publish\Release\net8.0\win-x64\</PublishUrl>
<WebPublishMethod>FileSystem</WebPublishMethod>
<_TargetId>Folder</_TargetId>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
</PropertyGroup>

View File

@@ -7,7 +7,7 @@
<PublishDir>publish\Release\net8.0\win-x64</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<_TargetId>Folder</_TargetId>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>false</SelfContained>
<PublishSingleFile>false</PublishSingleFile>

View File

@@ -50,11 +50,7 @@ module.exports = (socket) => {
const trayIcon = electron_1.nativeImage.createFromPath(image);
tray.value = new electron_1.Tray(trayIcon);
if (menuItems) {
const menu = electron_1.Menu.buildFromTemplate(menuItems);
addMenuItemClickConnector(menu.items, (id) => {
electronSocket.emit('trayMenuItemClicked', id);
});
tray.value.setContextMenu(menu);
applyContextMenu(menuItems);
}
});
socket.on('tray-destroy', () => {
@@ -62,6 +58,11 @@ module.exports = (socket) => {
tray.value.destroy();
}
});
socket.on('set-contextMenu', (menuItems) => {
if (menuItems && tray.value) {
applyContextMenu(menuItems);
}
});
socket.on('tray-setImage', (image) => {
if (tray.value) {
tray.value.setImage(image);
@@ -118,6 +119,13 @@ module.exports = (socket) => {
});
}
});
function applyContextMenu(menuItems) {
const menu = electron_1.Menu.buildFromTemplate(menuItems);
addMenuItemClickConnector(menu.items, (id) => {
electronSocket.emit('trayMenuItemClicked', id);
});
tray.value.setContextMenu(menu);
}
function addMenuItemClickConnector(menuItems, callback) {
menuItems.forEach((item) => {
if (item.submenu && item.submenu.items.length > 0) {

File diff suppressed because one or more lines are too long

View File

@@ -59,12 +59,7 @@ export = (socket: Socket) => {
tray.value = new Tray(trayIcon);
if (menuItems) {
const menu = Menu.buildFromTemplate(menuItems);
addMenuItemClickConnector(menu.items, (id) => {
electronSocket.emit('trayMenuItemClicked', id);
});
tray.value.setContextMenu(menu);
applyContextMenu(menuItems);
}
});
@@ -74,6 +69,12 @@ export = (socket: Socket) => {
}
});
socket.on('set-contextMenu', (menuItems) => {
if (menuItems && tray.value) {
applyContextMenu(menuItems);
}
});
socket.on('tray-setImage', (image) => {
if (tray.value) {
tray.value.setImage(image);
@@ -136,6 +137,14 @@ export = (socket: Socket) => {
}
});
function applyContextMenu(menuItems) {
const menu = Menu.buildFromTemplate(menuItems);
addMenuItemClickConnector(menu.items, (id) => {
electronSocket.emit('trayMenuItemClicked', id);
});
tray.value.setContextMenu(menu);
}
function addMenuItemClickConnector(menuItems, callback) {
menuItems.forEach((item) => {
if (item.submenu && item.submenu.items.length > 0) {

View File

@@ -5,14 +5,13 @@ const path = require('path');
const cProcess = require('child_process').spawn;
const portscanner = require('portscanner');
const { imageSize } = require('image-size');
const { HookService } = require('electron-host-hook');
let io, server, browserWindows, ipc, apiProcess, loadURL;
let appApi, menu, dialogApi, notification, tray, webContents;
let globalShortcut, shellApi, screen, clipboard, autoUpdater;
let commandLine, browserView;
let powerMonitor;
let processInfo;
let splashScreen, hostHook;
let splashScreen;
let nativeTheme;
let dock;
let launchFile;
@@ -263,6 +262,7 @@ function startSocketApiBridge(port) {
console.log('Electron Socket: starting...');
server = require('http').createServer();
const { Server } = require('socket.io');
let hostHook;
io = new Server({
pingTimeout: 60000, // in ms, default is 5000
pingInterval: 10000, // in ms, default is 25000
@@ -359,6 +359,8 @@ function startSocketApiBridge(port) {
});
try {
const { HookService } = require('electron-host-hook');
if (hostHook === undefined) {
hostHook = new HookService(socket, app);
hostHook.onHostReady();
@@ -366,6 +368,7 @@ function startSocketApiBridge(port) {
} catch (error) {
console.error(error.message);
}
console.log('Electron Socket: startup complete.');
});
}
@@ -373,7 +376,7 @@ function startSocketApiBridge(port) {
function startAspCoreBackend(electronPort) {
startBackend();
function startBackend() {
function startBackend() {
loadURL = `about:blank`;
const envParam = getEnvironmentParameter();
const parameters = [

View File

@@ -0,0 +1,101 @@
namespace ElectronNET.IntegrationTests.Common
{
using System.Runtime.InteropServices;
using Xunit.Sdk;
/// <summary>
/// Custom fact attribute with a default timeout of 20 seconds, allowing tests to be skipped on specific environments.
/// </summary>
/// <seealso cref="Xunit.FactAttribute" />
[AttributeUsage(AttributeTargets.Method)]
[XunitTestCaseDiscoverer("Xunit.Sdk.SkippableFactDiscoverer", "Xunit.SkippableFact")]
internal sealed class IntegrationFactAttribute : FactAttribute
{
private static readonly bool IsOnWsl;
private static readonly bool IsOnCI;
static IntegrationFactAttribute()
{
IsOnWsl = DetectWsl();
IsOnCI = DetectCI();
}
/// <summary>
/// Initializes a new instance of the <see cref="IntegrationFactAttribute" /> class.
/// </summary>
public IntegrationFactAttribute()
{
this.Timeout = 20_000;
}
public bool SkipOnWsl { get; set; }
public bool SkipOnCI { get; set; }
/// <summary>
/// Marks the test so that it will not be run, and gets or sets the skip reason
/// </summary>
public override string Skip {
get
{
if (IsOnWsl && this.SkipOnWsl)
{
return "Skipping test on WSL environment.";
}
if (IsOnCI && this.SkipOnCI)
{
return "Skipping test on CI environment.";
}
return base.Skip;
}
set
{
base.Skip = value;
}
}
private static bool DetectWsl()
{
try
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return false;
}
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("WSL_DISTRO_NAME")) ||
!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("WSL_INTEROP")))
{
return true;
}
return false;
}
catch
{
return false;
}
}
private static bool DetectCI()
{
try
{
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TF_BUILD")) ||
!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("GITHUB_ACTIONS")))
{
return true;
}
return false;
}
catch
{
return false;
}
}
}
}

View File

@@ -0,0 +1,23 @@
namespace ElectronNET.IntegrationTests.Common
{
using ElectronNET.API;
using ElectronNET.API.Entities;
// Base class for integration tests providing shared access to MainWindow and OS platform constants
public abstract class IntegrationTestBase
{
protected IntegrationTestBase(ElectronFixture fixture)
{
Fixture = fixture;
MainWindow = fixture.MainWindow;
}
protected ElectronFixture Fixture { get; }
protected BrowserWindow MainWindow { get; }
// Constants for SupportedOSPlatform attributes
public const string Windows = "Windows";
public const string MacOS = "macOS";
public const string Linux = "Linux";
}
}

View File

@@ -1,49 +0,0 @@
namespace ElectronNET.IntegrationTests.Common
{
using System.Runtime.InteropServices;
[AttributeUsage(AttributeTargets.Method)]
internal sealed class SkipOnWslFactAttribute : FactAttribute
{
private static readonly bool IsOnWsl;
static SkipOnWslFactAttribute()
{
IsOnWsl = DetectWsl();
}
/// <summary>
/// Initializes a new instance of the <see cref="SkipOnWslFactAttribute" /> class.
/// </summary>
public SkipOnWslFactAttribute()
{
if (IsOnWsl)
{
this.Skip = "Skipping test on WSL environment.";
}
}
private static bool DetectWsl()
{
try
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return false;
}
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("WSL_DISTRO_NAME")) ||
!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("WSL_INTEROP")))
{
return true;
}
return false;
}
catch
{
return false;
}
}
}
}

View File

@@ -4,6 +4,7 @@ namespace ElectronNET.IntegrationTests
using System.Reflection;
using ElectronNET.API;
using ElectronNET.API.Entities;
using ElectronNET.Common;
// Shared fixture that starts Electron runtime once
[SuppressMessage("ReSharper", "MethodHasAsyncOverload")]
@@ -26,7 +27,7 @@ namespace ElectronNET.IntegrationTests
await runtimeController.Start();
Console.Error.WriteLine("[ElectronFixture] Waiting for Ready...");
await Task.WhenAny(runtimeController.WaitReadyTask, Task.Delay(TimeSpan.FromSeconds(10)));
await Task.WhenAny(runtimeController.WaitReadyTask, Task.Delay(10.seconds()));
if (!runtimeController.WaitReadyTask.IsCompleted)
{

View File

@@ -6,19 +6,16 @@ namespace ElectronNET.IntegrationTests.Tests
using System.IO;
using System.Runtime.Versioning;
using System.Threading.Tasks;
using ElectronNET.IntegrationTests.Common;
[Collection("ElectronCollection")]
public class AppTests
public class AppTests : IntegrationTestBase
{
// ReSharper disable once NotAccessedField.Local
private readonly ElectronFixture fx;
public AppTests(ElectronFixture fx)
public AppTests(ElectronFixture fx) : base(fx)
{
this.fx = fx;
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task Can_get_app_path()
{
var path = await Electron.App.GetAppPathAsync();
@@ -26,7 +23,7 @@ namespace ElectronNET.IntegrationTests.Tests
Directory.Exists(path).Should().BeTrue();
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task Can_get_version_and_locale()
{
var version = await Electron.App.GetVersionAsync();
@@ -35,7 +32,7 @@ namespace ElectronNET.IntegrationTests.Tests
locale.Should().NotBeNullOrWhiteSpace();
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task Can_get_special_paths()
{
var userData = await Electron.App.GetPathAsync(PathName.UserData);
@@ -47,7 +44,7 @@ namespace ElectronNET.IntegrationTests.Tests
Directory.Exists(temp).Should().BeTrue();
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task Can_get_app_metrics()
{
var metrics = await Electron.App.GetAppMetricsAsync();
@@ -55,23 +52,23 @@ namespace ElectronNET.IntegrationTests.Tests
metrics.Length.Should().BeGreaterThan(0);
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task Can_get_gpu_feature_status()
{
var status = await Electron.App.GetGpuFeatureStatusAsync();
status.Should().NotBeNull();
}
[SkippableFact(Timeout = 20000)]
[SupportedOSPlatform("macOS")]
[SupportedOSPlatform("Windows")]
[IntegrationFact]
[SupportedOSPlatform(MacOS)]
[SupportedOSPlatform(Windows)]
public async Task Can_get_login_item_settings()
{
var settings = await Electron.App.GetLoginItemSettingsAsync();
settings.Should().NotBeNull();
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task CommandLine_append_and_query_switch()
{
var switchName = "integration-switch";
@@ -80,9 +77,9 @@ namespace ElectronNET.IntegrationTests.Tests
(await Electron.App.CommandLine.GetSwitchValueAsync(switchName)).Should().Be("value123");
}
[SkippableFact(Timeout = 20000)]
[SupportedOSPlatform("macOS")]
[SupportedOSPlatform("Windows")]
[IntegrationFact]
[SupportedOSPlatform(MacOS)]
[SupportedOSPlatform(Windows)]
public async Task Accessibility_support_toggle()
{
Electron.App.SetAccessibilitySupportEnabled(true);
@@ -91,7 +88,7 @@ namespace ElectronNET.IntegrationTests.Tests
Electron.App.SetAccessibilitySupportEnabled(false);
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task UserAgentFallback_roundtrip()
{
var original = await Electron.App.UserAgentFallbackAsync;
@@ -101,9 +98,9 @@ namespace ElectronNET.IntegrationTests.Tests
Electron.App.UserAgentFallback = original; // restore
}
[SkippableFact(Timeout = 20000)]
[SupportedOSPlatform("Linux")]
[SupportedOSPlatform("macOS")]
[IntegrationFact]
[SupportedOSPlatform(Linux)]
[SupportedOSPlatform(MacOS)]
public async Task BadgeCount_set_and_reset_where_supported()
{
await Electron.App.SetBadgeCountAsync(2);
@@ -113,14 +110,14 @@ namespace ElectronNET.IntegrationTests.Tests
await Electron.App.SetBadgeCountAsync(0);
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task App_metrics_have_cpu_info()
{
var metrics = await Electron.App.GetAppMetricsAsync();
metrics[0].Cpu.Should().NotBeNull();
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task App_gpu_feature_status_has_some_fields()
{
var status = await Electron.App.GetGpuFeatureStatusAsync();

View File

@@ -2,18 +2,17 @@
{
using API;
using System.Threading.Tasks;
using ElectronNET.Common;
using ElectronNET.IntegrationTests.Common;
[Collection("ElectronCollection")]
public class AutoUpdaterTests
public class AutoUpdaterTests : IntegrationTestBase
{
private readonly ElectronFixture fx;
public AutoUpdaterTests(ElectronFixture fx)
public AutoUpdaterTests(ElectronFixture fx) : base(fx)
{
this.fx = fx;
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task AutoDownload_check()
{
Electron.AutoUpdater.AutoDownload = false;
@@ -24,7 +23,7 @@
test2.Should().BeTrue();
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task AutoInstallOnAppQuit_check()
{
Electron.AutoUpdater.AutoInstallOnAppQuit = false;
@@ -35,7 +34,7 @@
test2.Should().BeTrue();
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task AllowPrerelease_check()
{
Electron.AutoUpdater.AllowPrerelease = false;
@@ -46,7 +45,7 @@
test2.Should().BeTrue();
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task FullChangelog_check()
{
Electron.AutoUpdater.FullChangelog = false;
@@ -57,7 +56,7 @@
test2.Should().BeTrue();
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task AllowDowngrade_check()
{
Electron.AutoUpdater.AllowDowngrade = false;
@@ -68,14 +67,14 @@
test2.Should().BeTrue();
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task UpdateConfigPath_check()
{
var test1 = Electron.AutoUpdater.UpdateConfigPath;
test1.Should().Be(string.Empty);
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task CurrentVersionAsync_check()
{
var semver = await Electron.AutoUpdater.CurrentVersionAsync;
@@ -83,18 +82,18 @@
semver.Major.Should().BeGreaterThan(0);
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task ChannelAsync_check()
{
var test = await Electron.AutoUpdater.ChannelAsync;
test.Should().Be(string.Empty);
Electron.AutoUpdater.SetChannel = "beta";
await Task.Delay(500);
await Task.Delay(500.ms());
test = await Electron.AutoUpdater.ChannelAsync;
test.Should().Be("beta");
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task RequestHeadersAsync_check()
{
var headers = new Dictionary<string, string>
@@ -104,28 +103,28 @@
var test = await Electron.AutoUpdater.RequestHeadersAsync;
test.Should().BeNull();
Electron.AutoUpdater.RequestHeaders = headers;
await Task.Delay(500);
await Task.Delay(500.ms());
test = await Electron.AutoUpdater.RequestHeadersAsync;
test.Should().NotBeNull();
test.Count.Should().Be(1);
test["key1"].Should().Be("value1");
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task CheckForUpdatesAsync_check()
{
var test = await Electron.AutoUpdater.CheckForUpdatesAsync();
test.Should().BeNull();
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task CheckForUpdatesAndNotifyAsync_check()
{
var test = await Electron.AutoUpdater.CheckForUpdatesAsync();
test.Should().BeNull();
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task GetFeedURLAsync_check()
{
var test = await Electron.AutoUpdater.GetFeedURLAsync();

View File

@@ -2,22 +2,20 @@ namespace ElectronNET.IntegrationTests.Tests
{
using ElectronNET.API;
using ElectronNET.API.Entities;
using ElectronNET.IntegrationTests.Common;
[Collection("ElectronCollection")]
public class BrowserViewTests
public class BrowserViewTests : IntegrationTestBase
{
private readonly ElectronFixture fx;
public BrowserViewTests(ElectronFixture fx)
public BrowserViewTests(ElectronFixture fx) : base(fx)
{
this.fx = fx;
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task Create_browser_view_and_adjust_bounds()
{
var view = await Electron.WindowManager.CreateBrowserViewAsync(new BrowserViewConstructorOptions());
this.fx.MainWindow.SetBrowserView(view);
this.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;

View File

@@ -1,144 +1,163 @@
namespace ElectronNET.IntegrationTests.Tests
{
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using ElectronNET.API;
using ElectronNET.API.Entities;
using ElectronNET.Common;
using ElectronNET.IntegrationTests.Common;
[Collection("ElectronCollection")]
public class BrowserWindowTests
public class BrowserWindowTests : IntegrationTestBase
{
private readonly ElectronFixture fx;
public BrowserWindowTests(ElectronFixture fx)
public BrowserWindowTests(ElectronFixture fx) : base(fx)
{
this.fx = fx;
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task Can_set_and_get_title()
{
const string title = "Integration Test Title";
this.fx.MainWindow.SetTitle(title);
await Task.Delay(500);
var roundTrip = await this.fx.MainWindow.GetTitleAsync();
this.MainWindow.SetTitle(title);
await Task.Delay(500.ms());
var roundTrip = await this.MainWindow.GetTitleAsync();
roundTrip.Should().Be(title);
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task Can_resize_and_get_size()
{
this.fx.MainWindow.SetSize(643, 482);
await Task.Delay(500);
var size = await this.fx.MainWindow.GetSizeAsync();
this.MainWindow.SetSize(643, 482);
await Task.Delay(500.ms());
var size = await this.MainWindow.GetSizeAsync();
size.Should().HaveCount(2);
size[0].Should().Be(643);
size[1].Should().Be(482);
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task Can_set_progress_bar_and_clear()
{
this.fx.MainWindow.SetProgressBar(0.5);
this.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);
this.MainWindow.SetProgressBar(-1); // clears
await Task.Delay(50.ms());
}
[SkipOnWslFact(Timeout = 20000)]
[IntegrationFact(SkipOnWsl = true)]
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();
this.MainWindow.SetPosition(134, 246);
await Task.Delay(500.ms());
var pos = await this.MainWindow.GetPositionAsync();
pos.Should().BeEquivalentTo([134, 246]);
}
[Fact(Timeout = 20000)]
[IntegrationFact]
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);
await Task.Delay(500);
var round = await this.fx.MainWindow.GetBoundsAsync();
this.MainWindow.SetBounds(bounds);
await Task.Delay(500.ms());
var round = await this.MainWindow.GetBoundsAsync();
round.Should().BeEquivalentTo(bounds);
round.Width.Should().Be(400);
round.Height.Should().Be(300);
}
[Fact(Timeout = 20000)]
[IntegrationFact]
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);
await Task.Delay(500);
var round = await this.fx.MainWindow.GetContentBoundsAsync();
this.MainWindow.SetContentBounds(bounds);
await Task.Delay(500.ms());
var round = await this.MainWindow.GetContentBoundsAsync();
round.Width.Should().BeGreaterThan(0);
round.Height.Should().BeGreaterThan(0);
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task Show_hide_visibility_roundtrip()
{
this.fx.MainWindow.Show();
await Task.Delay(500);
(await this.fx.MainWindow.IsVisibleAsync()).Should().BeTrue();
this.fx.MainWindow.Hide();
await Task.Delay(500);
(await this.fx.MainWindow.IsVisibleAsync()).Should().BeFalse();
BrowserWindow window = null;
try
{
window = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = true }, "about:blank");
await Task.Delay(100.ms());
window.Show();
await Task.Delay(500.ms());
(await window.IsVisibleAsync()).Should().BeTrue();
window.Hide();
await Task.Delay(500.ms());
(await window.IsVisibleAsync()).Should().BeFalse();
}
finally
{
window?.Destroy();
}
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task AlwaysOnTop_toggle_and_query()
{
this.fx.MainWindow.SetAlwaysOnTop(true);
await Task.Delay(500);
(await this.fx.MainWindow.IsAlwaysOnTopAsync()).Should().BeTrue();
this.fx.MainWindow.SetAlwaysOnTop(false);
await Task.Delay(500);
(await this.fx.MainWindow.IsAlwaysOnTopAsync()).Should().BeFalse();
this.MainWindow.SetAlwaysOnTop(true);
await Task.Delay(500.ms());
(await this.MainWindow.IsAlwaysOnTopAsync()).Should().BeTrue();
this.MainWindow.SetAlwaysOnTop(false);
await Task.Delay(500.ms());
(await this.MainWindow.IsAlwaysOnTopAsync()).Should().BeFalse();
}
[SkippableFact(Timeout = 20000)]
[SupportedOSPlatform("Linux")]
[SupportedOSPlatform("Windows")]
[IntegrationFact]
[SupportedOSPlatform(Linux)]
[SupportedOSPlatform(Windows)]
public async Task MenuBar_auto_hide_and_visibility()
{
this.fx.MainWindow.SetAutoHideMenuBar(true);
await Task.Delay(500);
(await this.fx.MainWindow.IsMenuBarAutoHideAsync()).Should().BeTrue();
this.fx.MainWindow.SetMenuBarVisibility(false);
await Task.Delay(500);
(await this.fx.MainWindow.IsMenuBarVisibleAsync()).Should().BeFalse();
this.fx.MainWindow.SetMenuBarVisibility(true);
await Task.Delay(500);
(await this.fx.MainWindow.IsMenuBarVisibleAsync()).Should().BeTrue();
this.MainWindow.SetAutoHideMenuBar(true);
await Task.Delay(500.ms());
(await this.MainWindow.IsMenuBarAutoHideAsync()).Should().BeTrue();
this.MainWindow.SetMenuBarVisibility(false);
await Task.Delay(500.ms());
(await this.MainWindow.IsMenuBarVisibleAsync()).Should().BeFalse();
this.MainWindow.SetMenuBarVisibility(true);
await Task.Delay(500.ms());
(await this.MainWindow.IsMenuBarVisibleAsync()).Should().BeTrue();
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task ReadyToShow_event_fires_after_content_ready()
{
var window = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = false }, "about:blank");
var tcs = new TaskCompletionSource();
window.OnReadyToShow += () => tcs.TrySetResult();
BrowserWindow window = null;
// 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 Task.Delay(500);
await window.WebContents.LoadURLAsync("about:blank");
await domReadyTcs.Task;
try
{
window = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = false }, "about:blank");
var tcs = new TaskCompletionSource();
window.OnReadyToShow += () => tcs.TrySetResult();
var completed = await Task.WhenAny(tcs.Task, Task.Delay(3000));
completed.Should().Be(tcs.Task);
// 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 Task.Delay(500.ms());
await window.WebContents.LoadURLAsync("about:blank");
await domReadyTcs.Task;
// Typical usage is to show once ready
window.Show();
var completed = await Task.WhenAny(tcs.Task, Task.Delay(3.seconds()));
completed.Should().Be(tcs.Task);
}
finally
{
window?.Destroy();
}
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task PageTitleUpdated_event_fires_on_title_change()
{
var window = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = true }, "about:blank");
@@ -148,90 +167,90 @@ namespace ElectronNET.IntegrationTests.Tests
// 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 Task.Delay(500);
await Task.Delay(500.ms());
await window.WebContents.LoadURLAsync("about:blank");
await domReadyTcs.Task;
await window.WebContents.ExecuteJavaScriptAsync<string>("document.title='NewTitle';");
// Wait for event up to a short timeout
var completed2 = await Task.WhenAny(tcs.Task, Task.Delay(3000));
var completed2 = await Task.WhenAny(tcs.Task, Task.Delay(3.seconds()));
completed2.Should().Be(tcs.Task);
(await tcs.Task).Should().Be("NewTitle");
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task Resize_event_fires_on_size_change()
{
var window = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = false }, "about:blank");
var resized = false;
window.OnResize += () => resized = true;
await Task.Delay(500);
await Task.Delay(500.ms());
window.SetSize(500, 400);
await Task.Delay(300);
await Task.Delay(300.ms());
resized.Should().BeTrue();
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task Progress_bar_and_always_on_top_toggle()
{
var win = this.fx.MainWindow;
var win = this.MainWindow;
win.SetProgressBar(0.5);
await Task.Delay(50);
await Task.Delay(50.ms());
win.SetProgressBar(0.8, new ProgressBarOptions());
await Task.Delay(50);
await Task.Delay(50.ms());
win.SetAlwaysOnTop(true);
await Task.Delay(500);
await Task.Delay(500.ms());
(await win.IsAlwaysOnTopAsync()).Should().BeTrue();
win.SetAlwaysOnTop(false);
await Task.Delay(500);
await Task.Delay(500.ms());
(await win.IsAlwaysOnTopAsync()).Should().BeFalse();
}
[SkippableFact(Timeout = 20000)]
[SupportedOSPlatform("Linux")]
[SupportedOSPlatform("Windows")]
[IntegrationFact]
[SupportedOSPlatform(Linux)]
[SupportedOSPlatform(Windows)]
public async Task Menu_bar_visibility_and_auto_hide()
{
var win = this.fx.MainWindow;
var win = this.MainWindow;
win.SetAutoHideMenuBar(true);
await Task.Delay(500);
await Task.Delay(500.ms());
(await win.IsMenuBarAutoHideAsync()).Should().BeTrue();
win.SetMenuBarVisibility(true);
await Task.Delay(500);
await Task.Delay(500.ms());
(await win.IsMenuBarVisibleAsync()).Should().BeTrue();
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task Parent_child_relationship_roundtrip()
{
var child = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = false, Width = 300, Height = 200 }, "about:blank");
this.fx.MainWindow.SetParentWindow(null); // ensure top-level
child.SetParentWindow(this.fx.MainWindow);
await Task.Delay(500);
this.MainWindow.SetParentWindow(null); // ensure top-level
child.SetParentWindow(this.MainWindow);
await Task.Delay(500.ms());
var parent = await child.GetParentWindowAsync();
parent.Id.Should().Be(this.fx.MainWindow.Id);
var kids = await this.fx.MainWindow.GetChildWindowsAsync();
parent.Id.Should().Be(this.MainWindow.Id);
var kids = await this.MainWindow.GetChildWindowsAsync();
kids.Select(k => k.Id).Should().Contain(child.Id);
child.Destroy();
}
[SkippableFact(Timeout = 20000)]
[SupportedOSPlatform("macOS")]
[IntegrationFact]
[SupportedOSPlatform(MacOS)]
public async Task Represented_filename_and_edited_flags()
{
var win = this.fx.MainWindow;
var win = this.MainWindow;
var temp = Path.Combine(Path.GetTempPath(), "electronnet_test.txt");
File.WriteAllText(temp, "test");
win.SetRepresentedFilename(temp);
await Task.Delay(500);
await Task.Delay(500.ms());
var represented = await win.GetRepresentedFilenameAsync();
represented.Should().Be(temp);
win.SetDocumentEdited(true);
await Task.Delay(500);
await Task.Delay(500.ms());
var edited = await win.IsDocumentEditedAsync();
edited.Should().BeTrue();

View File

@@ -2,19 +2,16 @@ namespace ElectronNET.IntegrationTests.Tests
{
using System.Runtime.Versioning;
using ElectronNET.API;
using ElectronNET.IntegrationTests.Common;
[Collection("ElectronCollection")]
public class ClipboardTests
public class ClipboardTests : IntegrationTestBase
{
// ReSharper disable once NotAccessedField.Local
private readonly ElectronFixture fx;
public ClipboardTests(ElectronFixture fx)
public ClipboardTests(ElectronFixture fx) : base(fx)
{
this.fx = fx;
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task Clipboard_text_roundtrip()
{
var text = $"Hello Electron {Guid.NewGuid()}";
@@ -23,7 +20,7 @@ namespace ElectronNET.IntegrationTests.Tests
read.Should().Be(text);
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task Available_formats_contains_text_after_write()
{
var text = "FormatsTest";
@@ -32,9 +29,9 @@ namespace ElectronNET.IntegrationTests.Tests
formats.Should().Contain(f => f.Contains("text") || f.Contains("TEXT") || f.Contains("plain"));
}
[SkippableFact(Timeout = 20000)]
[SupportedOSPlatform("macOS")]
[SupportedOSPlatform("Windows")]
[IntegrationFact]
[SupportedOSPlatform(MacOS)]
[SupportedOSPlatform(Windows)]
public async Task Bookmark_write_and_read()
{
var url = "https://electron-test.com";

View File

@@ -1,26 +1,26 @@
namespace ElectronNET.IntegrationTests.Tests
{
[Collection("ElectronCollection")]
public class CookiesTests
{
private readonly ElectronFixture fx;
using ElectronNET.Common;
using ElectronNET.IntegrationTests.Common;
public CookiesTests(ElectronFixture fx)
[Collection("ElectronCollection")]
public class CookiesTests : IntegrationTestBase
{
public CookiesTests(ElectronFixture fx) : base(fx)
{
this.fx = fx;
}
[Fact(Skip = "Cookie set/get requires navigation to domain; skipping until test harness serves page")]
[IntegrationFact(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 session = this.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");
await this.MainWindow.WebContents.LoadURLAsync("https://example.com");
// Set via renderer for now
await this.fx.MainWindow.WebContents.ExecuteJavaScriptAsync<string>("document.cookie='integration_cookie=1;path=/';");
await Task.Delay(500);
await this.MainWindow.WebContents.ExecuteJavaScriptAsync<string>("document.cookie='integration_cookie=1;path=/';");
await Task.Delay(500.ms());
changed.Should().BeTrue();
}
}

View File

@@ -2,11 +2,16 @@ namespace ElectronNET.IntegrationTests.Tests
{
using System.Runtime.InteropServices;
using ElectronNET.API;
using ElectronNET.IntegrationTests.Common;
[Collection("ElectronCollection")]
public class GlobalShortcutTests
public class GlobalShortcutTests : IntegrationTestBase
{
[Fact(Timeout = 20000)]
public GlobalShortcutTests(ElectronFixture fx) : base(fx)
{
}
[IntegrationFact]
public async Task Can_register_and_unregister()
{
var accel = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "Cmd+Alt+G" : "Ctrl+Alt+G";

View File

@@ -1,11 +1,16 @@
namespace ElectronNET.IntegrationTests.Tests
{
using ElectronNET.API;
using ElectronNET.IntegrationTests.Common;
[Collection("ElectronCollection")]
public class HostHookTests
public class HostHookTests : IntegrationTestBase
{
[Fact(Skip = "Requires HostHook setup; skipping")]
public HostHookTests(ElectronFixture fx) : base(fx)
{
}
[IntegrationFact(Skip = "Requires HostHook setup; skipping")]
public async Task HostHook_call_returns_value()
{
var result = await Electron.HostHook.CallAsync<string>("create-excel-file", ".");

View File

@@ -1,18 +1,17 @@
namespace ElectronNET.IntegrationTests.Tests
{
using ElectronNET.API;
using ElectronNET.Common;
using ElectronNET.IntegrationTests.Common;
[Collection("ElectronCollection")]
public class IpcMainTests
public class IpcMainTests : IntegrationTestBase
{
private readonly ElectronFixture fx;
public IpcMainTests(ElectronFixture fx)
public IpcMainTests(ElectronFixture fx) : base(fx)
{
this.fx = fx;
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task Ipc_On_receives_message_from_renderer()
{
object received = null;
@@ -24,7 +23,7 @@ namespace ElectronNET.IntegrationTests.Tests
tcs.TrySetResult(obj as string);
});
await this.fx.MainWindow.WebContents.ExecuteJavaScriptAsync<string>("require('electron').ipcRenderer.send('ipc-on-test','payload123')");
await this.MainWindow.WebContents.ExecuteJavaScriptAsync<string>("require('electron').ipcRenderer.send('ipc-on-test','payload123')");
var result = await tcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
@@ -33,28 +32,28 @@ namespace ElectronNET.IntegrationTests.Tests
result.Should().Be("payload123");
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task Ipc_Once_only_fires_once()
{
var count = 0;
Electron.IpcMain.Once("ipc-once-test", _ => count++);
await this.fx.MainWindow.WebContents.ExecuteJavaScriptAsync<string>("const {ipcRenderer}=require('electron'); ipcRenderer.send('ipc-once-test','a'); ipcRenderer.send('ipc-once-test','b');");
await Task.Delay(500);
await this.MainWindow.WebContents.ExecuteJavaScriptAsync<string>("const {ipcRenderer}=require('electron'); ipcRenderer.send('ipc-once-test','a'); ipcRenderer.send('ipc-once-test','b');");
await Task.Delay(500.ms());
count.Should().Be(1);
}
[Fact(Timeout = 20000)]
[IntegrationFact]
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<string>("require('electron').ipcRenderer.send('ipc-remove-test','x')");
await Task.Delay(400);
await this.MainWindow.WebContents.ExecuteJavaScriptAsync<string>("require('electron').ipcRenderer.send('ipc-remove-test','x')");
await Task.Delay(400.ms());
fired.Should().BeFalse();
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task Ipc_OnSync_returns_value()
{
object received = null;
@@ -64,7 +63,7 @@ namespace ElectronNET.IntegrationTests.Tests
received = obj;
return "pong";
});
var ret = await this.fx.MainWindow.WebContents.ExecuteJavaScriptAsync<string>("require('electron').ipcRenderer.sendSync('ipc-sync-test','ping')");
var ret = await this.MainWindow.WebContents.ExecuteJavaScriptAsync<string>("require('electron').ipcRenderer.sendSync('ipc-sync-test','ping')");
received.Should().BeOfType<string>();
received.Should().Be("ping");
@@ -72,23 +71,23 @@ namespace ElectronNET.IntegrationTests.Tests
ret.Should().Be("pong");
}
[Fact(Timeout = 20000)]
[IntegrationFact]
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<string>(@"(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");
await this.MainWindow.WebContents.ExecuteJavaScriptAsync<string>(@"(function(){ const {ipcRenderer}=require('electron'); ipcRenderer.once('main-to-render',(e,arg)=>{ globalThis.__mainToRender = arg;}); return 'ready'; })();");
Electron.IpcMain.Send(this.MainWindow, "main-to-render", "hello-msg");
string value = "";
for (int i = 0; i < 20; i++)
{
var jsVal = await this.fx.MainWindow.WebContents.ExecuteJavaScriptAsync<string>("globalThis.__mainToRender === undefined ? '' : (typeof globalThis.__mainToRender === 'string' ? globalThis.__mainToRender : JSON.stringify(globalThis.__mainToRender))");
var jsVal = await this.MainWindow.WebContents.ExecuteJavaScriptAsync<string>("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);
await Task.Delay(100.ms());
}
// Normalize possible JSON array ["hello-msg"] case

View File

@@ -2,18 +2,17 @@ namespace ElectronNET.IntegrationTests.Tests
{
using ElectronNET.API;
using ElectronNET.API.Entities;
using ElectronNET.Common;
using ElectronNET.IntegrationTests.Common;
[Collection("ElectronCollection")]
public class MenuTests
public class MenuTests : IntegrationTestBase
{
private readonly ElectronFixture fx;
public MenuTests(ElectronFixture fx)
public MenuTests(ElectronFixture fx) : base(fx)
{
this.fx = fx;
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task ApplicationMenu_click_invokes_handler()
{
var clicked = false;
@@ -30,29 +29,29 @@ namespace ElectronNET.IntegrationTests.Tests
};
Electron.Menu.SetApplicationMenu(items);
var targetId = items[0].Submenu[0].Id;
await this.fx.MainWindow.WebContents.ExecuteJavaScriptAsync<string>($"require('electron').ipcRenderer.send('integration-click-application-menu','{targetId}')");
await this.MainWindow.WebContents.ExecuteJavaScriptAsync<string>($"require('electron').ipcRenderer.send('integration-click-application-menu','{targetId}')");
for (int i = 0; i < 20 && !clicked; i++)
{
await Task.Delay(100);
await Task.Delay(100.ms());
}
clicked.Should().BeTrue();
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task ContextMenu_popup_registers_items()
{
var win = this.fx.MainWindow;
var win = this.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<string>($"require('electron').ipcRenderer.send('integration-click-context-menu',{win.Id},'{ctxId}')");
await this.MainWindow.WebContents.ExecuteJavaScriptAsync<string>($"require('electron').ipcRenderer.send('integration-click-context-menu',{win.Id},'{ctxId}')");
for (int i = 0; i < 20 && !ctxClicked; i++)
{
await Task.Delay(100);
await Task.Delay(100.ms());
}
ctxClicked.Should().BeTrue();

View File

@@ -1,13 +1,12 @@
namespace ElectronNET.IntegrationTests.Tests
{
[Collection("ElectronCollection")]
public class MultiEventRegistrationTests
{
private readonly ElectronFixture fx;
using ElectronNET.IntegrationTests.Common;
public MultiEventRegistrationTests(ElectronFixture fx)
[Collection("ElectronCollection")]
public class MultiEventRegistrationTests : IntegrationTestBase
{
public MultiEventRegistrationTests(ElectronFixture fx) : base(fx)
{
this.fx = fx;
}
private static async Task<bool> WaitAllOrTimeout(TimeSpan timeout, params Task[] tasks)
@@ -17,10 +16,10 @@ namespace ElectronNET.IntegrationTests.Tests
return ReferenceEquals(completed, all) && all.IsCompletedSuccessfully;
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task BrowserWindow_OnResize_multiple_handlers_called()
{
var win = this.fx.MainWindow;
var win = this.MainWindow;
var h1 = new TaskCompletionSource();
var h2 = new TaskCompletionSource();
var h3 = new TaskCompletionSource();
@@ -41,10 +40,10 @@ namespace ElectronNET.IntegrationTests.Tests
}
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task WebContents_OnDomReady_multiple_handlers_called()
{
var wc = this.fx.MainWindow.WebContents;
var wc = this.MainWindow.WebContents;
var r1 = new TaskCompletionSource();
var r2 = new TaskCompletionSource();

View File

@@ -1,15 +1,20 @@
using System.Drawing;
using ElectronNET.API.Entities;
using ElectronNET.IntegrationTests.Common;
using System.Runtime.Versioning;
using RectangleEntity = ElectronNET.API.Entities.Rectangle;
namespace ElectronNET.IntegrationTests.Tests
{
using System.Drawing;
using ElectronNET.API.Entities;
[SupportedOSPlatform("Windows")]
public class NativeImageTests
[Collection("ElectronCollection")]
[SupportedOSPlatform(Windows)]
public class NativeImageTests : IntegrationTestBase
{
[SkippableFact(Timeout = 20000)]
public NativeImageTests(ElectronFixture fx) : base(fx)
{
}
[IntegrationFact]
public async Task Create_from_bitmap_and_to_png()
{
using var bmp = new Bitmap(10, 10);
@@ -27,7 +32,7 @@ namespace ElectronNET.IntegrationTests.Tests
png!.Length.Should().BeGreaterThan(0);
}
[SkippableFact(Timeout = 20000)]
[IntegrationFact]
public async Task Create_from_buffer_and_to_data_url()
{
// Prepare PNG bytes
@@ -46,7 +51,7 @@ namespace ElectronNET.IntegrationTests.Tests
dataUrl!.StartsWith("data:image/", StringComparison.Ordinal).Should().BeTrue();
}
[SkippableFact(Timeout = 20000)]
[IntegrationFact]
public async Task Resize_and_crop_produce_expected_sizes()
{
using var bmp = new Bitmap(12, 10);
@@ -66,7 +71,7 @@ namespace ElectronNET.IntegrationTests.Tests
csize.Height.Should().Be(3);
}
[SkippableFact(Timeout = 20000)]
[IntegrationFact]
public async Task Add_representation_for_scale_factor()
{
using var bmp = new Bitmap(5, 5);

View File

@@ -3,29 +3,35 @@ namespace ElectronNET.IntegrationTests.Tests
using System.Runtime.Versioning;
using ElectronNET.API;
using ElectronNET.API.Entities;
using ElectronNET.Common;
using ElectronNET.IntegrationTests.Common;
[Collection("ElectronCollection")]
public class NativeThemeTests
public class NativeThemeTests : IntegrationTestBase
{
[Fact(Timeout = 20000)]
public NativeThemeTests(ElectronFixture fx) : base(fx)
{
}
[IntegrationFact]
public async Task ThemeSource_roundtrip()
{
// Capture initial
_ = await Electron.NativeTheme.ShouldUseDarkColorsAsync();
// Force light
await Task.Delay(50);
await Task.Delay(50.ms());
Electron.NativeTheme.SetThemeSource(ThemeSourceMode.Light);
await Task.Delay(500);
await Task.Delay(500.ms());
var useDarkAfterLight = await Electron.NativeTheme.ShouldUseDarkColorsAsync();
var themeSourceLight = await Electron.NativeTheme.GetThemeSourceAsync();
// Force dark
Electron.NativeTheme.SetThemeSource(ThemeSourceMode.Dark);
await Task.Delay(500);
await Task.Delay(500.ms());
var useDarkAfterDark = await Electron.NativeTheme.ShouldUseDarkColorsAsync();
var themeSourceDark = await Electron.NativeTheme.GetThemeSourceAsync();
// Restore system
Electron.NativeTheme.SetThemeSource(ThemeSourceMode.System);
await Task.Delay(500);
await Task.Delay(500.ms());
var themeSourceSystem = await Electron.NativeTheme.GetThemeSourceAsync();
// Assertions are tolerant (platform dependent)
useDarkAfterLight.Should().BeFalse("forcing Light should result in light colors");
@@ -35,34 +41,34 @@ namespace ElectronNET.IntegrationTests.Tests
themeSourceSystem.Should().Be(ThemeSourceMode.System);
}
[Fact(Timeout = 20000)]
[IntegrationFact]
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);
await Task.Delay(400.ms());
Electron.NativeTheme.SetThemeSource(ThemeSourceMode.Light);
for (int i = 0; i < 10 && !fired; i++)
{
await Task.Delay(100);
await Task.Delay(100.ms());
}
fired.Should().BeTrue();
}
[SkippableFact(Timeout = 20000)]
[SupportedOSPlatform("macOS")]
[SupportedOSPlatform("Windows")]
[IntegrationFact]
[SupportedOSPlatform(MacOS)]
[SupportedOSPlatform(Windows)]
public async Task Should_use_high_contrast_colors_check()
{
var metrics = await Electron.NativeTheme.ShouldUseHighContrastColorsAsync();
metrics.Should().Be(false);
}
[SkippableFact(Timeout = 20000)]
[SupportedOSPlatform("macOS")]
[SupportedOSPlatform("Windows")]
[IntegrationFact]
[SupportedOSPlatform(MacOS)]
[SupportedOSPlatform(Windows)]
public async Task Should_use_inverted_colors_check()
{
var metrics = await Electron.NativeTheme.ShouldUseInvertedColorSchemeAsync();

View File

@@ -3,11 +3,17 @@ namespace ElectronNET.IntegrationTests.Tests
using System.Runtime.InteropServices;
using ElectronNET.API;
using ElectronNET.API.Entities;
using ElectronNET.Common;
using ElectronNET.IntegrationTests.Common;
[Collection("ElectronCollection")]
public class NotificationTests
public class NotificationTests : IntegrationTestBase
{
[SkippableFact(Timeout = 20000)]
public NotificationTests(ElectronFixture fx) : base(fx)
{
}
[IntegrationFact]
public async Task Notification_create_check()
{
Skip.If(RuntimeInformation.IsOSPlatform(OSPlatform.Linux), "Always returns false. Might need full-blown desktop environment");
@@ -17,16 +23,16 @@ namespace ElectronNET.IntegrationTests.Tests
var options = new NotificationOptions("Notification Title", "Notification test 123");
options.OnShow = () => tcs.SetResult();
await Task.Delay(500);
await Task.Delay(500.ms());
Electron.Notification.Show(options);
await Task.WhenAny(tcs.Task, Task.Delay(5_000));
await Task.WhenAny(tcs.Task, Task.Delay(5.seconds()));
tcs.Task.IsCompletedSuccessfully.Should().BeTrue();
}
[SkippableFact(Timeout = 20000)]
[IntegrationFact]
public async Task Notification_is_supported_check()
{
Skip.If(RuntimeInformation.IsOSPlatform(OSPlatform.Linux), "Always returns false. Might need full-blown desktop environment");

View File

@@ -1,11 +1,16 @@
namespace ElectronNET.IntegrationTests.Tests
{
using ElectronNET.API;
using ElectronNET.IntegrationTests.Common;
[Collection("ElectronCollection")]
public class ProcessTests
public class ProcessTests : IntegrationTestBase
{
[Fact(Timeout = 20000)]
public ProcessTests(ElectronFixture fx) : base(fx)
{
}
[IntegrationFact]
public async Task Process_info_is_accessible()
{
// Use renderer to fetch process info and round-trip
@@ -14,7 +19,7 @@ namespace ElectronNET.IntegrationTests.Tests
result.Should().Be("ok");
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task Process_properties_are_populated()
{
var execPath = await Electron.Process.ExecPathAsync;

View File

@@ -6,17 +6,13 @@ namespace ElectronNET.IntegrationTests.Tests
using ElectronNET.IntegrationTests.Common;
[Collection("ElectronCollection")]
public class ScreenTests
public class ScreenTests : IntegrationTestBase
{
// ReSharper disable once NotAccessedField.Local
private readonly ElectronFixture fx;
public ScreenTests(ElectronFixture fx)
public ScreenTests(ElectronFixture fx) : base(fx)
{
this.fx = fx;
}
[SkipOnWslFact(Timeout = 20000)]
[IntegrationFact(SkipOnWsl = true)]
public async Task Primary_display_has_positive_dimensions()
{
var display = await Electron.Screen.GetPrimaryDisplayAsync();
@@ -24,7 +20,7 @@ namespace ElectronNET.IntegrationTests.Tests
display.Size.Height.Should().BeGreaterThan(0);
}
[SkipOnWslFact(Timeout = 20000)]
[IntegrationFact(SkipOnWsl = true)]
public async Task GetAllDisplays_returns_at_least_one()
{
var displays = await Electron.Screen.GetAllDisplaysAsync();
@@ -32,17 +28,15 @@ namespace ElectronNET.IntegrationTests.Tests
displays.Length.Should().BeGreaterThan(0);
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task GetCursorScreenPoint_check()
{
var point = await Electron.Screen.GetCursorScreenPointAsync();
point.Should().NotBeNull();
point.X.Should().BeGreaterThanOrEqualTo(0);
point.Y.Should().BeGreaterThanOrEqualTo(0);
}
[SkippableFact(Timeout = 20000)]
[SupportedOSPlatform("macOS")]
[IntegrationFact]
[SupportedOSPlatform(MacOS)]
public async Task GetMenuBarWorkArea_check()
{
var area = await Electron.Screen.GetMenuBarWorkAreaAsync();
@@ -53,7 +47,7 @@ namespace ElectronNET.IntegrationTests.Tests
area.Width.Should().BeGreaterThan(0);
}
[SkipOnWslFact(Timeout = 20000)]
[IntegrationFact(SkipOnWsl = true)]
public async Task GetDisplayNearestPoint_check()
{
var point = new Point
@@ -67,7 +61,7 @@ namespace ElectronNET.IntegrationTests.Tests
display.Size.Height.Should().BeGreaterThan(0);
}
[SkipOnWslFact(Timeout = 20000)]
[IntegrationFact(SkipOnWsl = true)]
public async Task GetDisplayMatching_check()
{
var rectangle = new Rectangle

View File

@@ -1,21 +1,19 @@
namespace ElectronNET.IntegrationTests.Tests
{
using ElectronNET.API.Entities;
using ElectronNET.IntegrationTests.Common;
[Collection("ElectronCollection")]
public class SessionTests
public class SessionTests : IntegrationTestBase
{
private readonly ElectronFixture fx;
public SessionTests(ElectronFixture fx)
public SessionTests(ElectronFixture fx) : base(fx)
{
this.fx = fx;
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task Session_preloads_roundtrip()
{
var session = this.fx.MainWindow.WebContents.Session;
var session = this.MainWindow.WebContents.Session;
_ = await session.GetPreloadsAsync();
// Use a dummy path; API should store value
session.SetPreloads(new[] { "/tmp/preload_dummy.js" });
@@ -23,10 +21,10 @@ namespace ElectronNET.IntegrationTests.Tests
preloadsAfter.Should().Contain("/tmp/preload_dummy.js");
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task Session_proxy_set_and_resolve()
{
var session = this.fx.MainWindow.WebContents.Session;
var session = this.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");
@@ -34,10 +32,10 @@ namespace ElectronNET.IntegrationTests.Tests
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task Session_clear_cache_and_storage_completes()
{
var session = this.fx.MainWindow.WebContents.Session;
var session = this.MainWindow.WebContents.Session;
await session.ClearCacheAsync();
await session.ClearStorageDataAsync();
await session.ClearHostResolverCacheAsync();
@@ -46,10 +44,10 @@ namespace ElectronNET.IntegrationTests.Tests
ua.Should().NotBeNullOrWhiteSpace();
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task Session_preloads_set_multiple_and_clear()
{
var session = this.fx.MainWindow.WebContents.Session;
var session = this.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");
@@ -59,40 +57,40 @@ namespace ElectronNET.IntegrationTests.Tests
empty.Should().NotContain("/tmp/a.js");
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task Clear_auth_cache_overloads()
{
var session = this.fx.MainWindow.WebContents.Session;
var session = this.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(Timeout = 20000)]
[IntegrationFact]
public async Task Clear_storage_with_options()
{
var session = this.fx.MainWindow.WebContents.Session;
var session = this.MainWindow.WebContents.Session;
await session.ClearStorageDataAsync(new ClearStorageDataOptions { Storages = new[] { "cookies" }, Quotas = new[] { "temporary" } });
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task Enable_disable_network_emulation()
{
var session = this.fx.MainWindow.WebContents.Session;
var session = this.MainWindow.WebContents.Session;
session.EnableNetworkEmulation(new EnableNetworkEmulationOptions { Offline = false, Latency = 10, DownloadThroughput = 50000, UploadThroughput = 20000 });
session.DisableNetworkEmulation();
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task Flush_storage_data_does_not_throw()
{
var session = this.fx.MainWindow.WebContents.Session;
var session = this.MainWindow.WebContents.Session;
session.FlushStorageData();
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task Set_user_agent_affects_new_navigation()
{
var session = this.fx.MainWindow.WebContents.Session;
var session = this.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();

View File

@@ -1,11 +1,16 @@
namespace ElectronNET.IntegrationTests.Tests
{
using ElectronNET.API;
using ElectronNET.IntegrationTests.Common;
[Collection("ElectronCollection")]
public class ShellTests
public class ShellTests : IntegrationTestBase
{
[Fact(Skip = "This can keep the test process hanging until the e-mail window is closed")]
public ShellTests(ElectronFixture fx) : base(fx)
{
}
[IntegrationFact(Skip = "This can keep the test process hanging until the e-mail window is closed")]
public async Task OpenExternal_invalid_scheme_returns_error_or_empty()
{
var error = await Electron.Shell.OpenExternalAsync("mailto:test@example.com");

View File

@@ -2,28 +2,26 @@ namespace ElectronNET.IntegrationTests.Tests
{
using System.Runtime.Versioning;
using ElectronNET.API.Entities;
using ElectronNET.IntegrationTests.Common;
[Collection("ElectronCollection")]
public class ThumbarButtonTests
public class ThumbarButtonTests : IntegrationTestBase
{
private readonly ElectronFixture fx;
public ThumbarButtonTests(ElectronFixture fx)
public ThumbarButtonTests(ElectronFixture fx) : base(fx)
{
this.fx = fx;
}
[SkippableFact(Timeout = 20000)]
[SupportedOSPlatform("Windows")]
[IntegrationFact]
[SupportedOSPlatform(Windows)]
public async Task SetThumbarButtons_returns_success()
{
var btn = new ThumbarButton("icon.png") { Tooltip = "Test" };
var success = await this.fx.MainWindow.SetThumbarButtonsAsync(new[] { btn });
var success = await this.MainWindow.SetThumbarButtonsAsync(new[] { btn });
success.Should().BeTrue();
}
[SkippableFact(Timeout = 20000)]
[SupportedOSPlatform("Windows")]
[IntegrationFact]
[SupportedOSPlatform(Windows)]
public async Task Thumbar_button_click_invokes_callback()
{
var icon = Path.Combine(Directory.GetCurrentDirectory(), "ElectronNET.WebApp", "wwwroot", "icon.png");
@@ -34,7 +32,7 @@ namespace ElectronNET.IntegrationTests.Tests
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 });
var ok = await this.MainWindow.SetThumbarButtonsAsync(new[] { btn });
ok.Should().BeTrue();
}
}

View File

@@ -1,19 +1,16 @@
namespace ElectronNET.IntegrationTests.Tests
{
using ElectronNET.API;
using ElectronNET.IntegrationTests.Common;
[Collection("ElectronCollection")]
public class TrayTests
public class TrayTests : IntegrationTestBase
{
// ReSharper disable once NotAccessedField.Local
private readonly ElectronFixture fx;
public TrayTests(ElectronFixture fx)
public TrayTests(ElectronFixture fx) : base(fx)
{
this.fx = fx;
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task Can_create_tray_and_destroy()
{
//await Electron.Tray.Show("assets/icon.png");

View File

@@ -2,56 +2,56 @@ using System.Runtime.InteropServices;
namespace ElectronNET.IntegrationTests.Tests
{
using ElectronNET.API;
using ElectronNET.API.Entities;
using ElectronNET.Common;
using ElectronNET.IntegrationTests.Common;
[Collection("ElectronCollection")]
public class WebContentsTests
public class WebContentsTests : IntegrationTestBase
{
private readonly ElectronFixture fx;
public WebContentsTests(ElectronFixture fx)
public WebContentsTests(ElectronFixture fx) : base(fx)
{
this.fx = fx;
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task Can_get_url_after_navigation()
{
var wc = this.fx.MainWindow.WebContents;
var wc = this.MainWindow.WebContents;
await wc.LoadURLAsync("https://example.com");
var url = await wc.GetUrl();
url.Should().Contain("example.com");
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task ExecuteJavaScript_returns_title()
{
var wc = this.fx.MainWindow.WebContents;
var wc = this.MainWindow.WebContents;
await wc.LoadURLAsync("https://example.com");
var title = await wc.ExecuteJavaScriptAsync<string>("document.title");
title.Should().NotBeNull();
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task DomReady_event_fires()
{
var wc = this.fx.MainWindow.WebContents;
var wc = this.MainWindow.WebContents;
var fired = false;
wc.OnDomReady += () => fired = true;
await wc.LoadURLAsync("https://example.com");
await Task.Delay(500);
await Task.Delay(500.ms());
fired.Should().BeTrue();
}
[Fact(Timeout = 20000)]
[IntegrationFact]
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);
await this.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);
var ok = await this.MainWindow.WebContents.PrintToPDFAsync(tmp);
ok.Should().BeTrue();
File.Exists(tmp).Should().BeTrue();
new FileInfo(tmp).Length.Should().BeGreaterThan(0);
@@ -65,92 +65,130 @@ namespace ElectronNET.IntegrationTests.Tests
}
}
[Fact(Timeout = 20000)]
[IntegrationFact]
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 });
await this.MainWindow.WebContents.LoadURLAsync(html);
var ok = await this.MainWindow.WebContents.PrintAsync(new PrintOptions { Silent = true, PrintBackground = true });
ok.Should().BeTrue();
}
[SkippableFact(Timeout = 20000)]
[IntegrationFact]
public async Task GetPrintersAsync_check()
{
Skip.If(Environment.GetEnvironmentVariable("GITHUB_RUN_ID") != null, "Skipping printer test in CI environment.");
var info = await fx.MainWindow.WebContents.GetPrintersAsync();
var info = await this.MainWindow.WebContents.GetPrintersAsync();
info.Should().NotBeNull();
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task GetSetZoomFactor_check()
{
await fx.MainWindow.WebContents.GetZoomFactorAsync();
var ok = await fx.MainWindow.WebContents.GetZoomFactorAsync();
await this.MainWindow.WebContents.GetZoomFactorAsync();
var ok = await this.MainWindow.WebContents.GetZoomFactorAsync();
ok.Should().BeGreaterThan(0.0);
fx.MainWindow.WebContents.SetZoomFactor(2.0);
await Task.Delay(500);
ok = await fx.MainWindow.WebContents.GetZoomFactorAsync();
this.MainWindow.WebContents.SetZoomFactor(2.0);
await Task.Delay(500.ms());
ok = await this.MainWindow.WebContents.GetZoomFactorAsync();
ok.Should().Be(2.0);
}
[SkippableFact(Timeout = 20000)]
[IntegrationFact]
public async Task GetSetZoomLevel_check()
{
Skip.If(Environment.GetEnvironmentVariable("GITHUB_RUN_ID") != null && RuntimeInformation.IsOSPlatform(OSPlatform.Windows), "Skipping test on Windows CI.");
await fx.MainWindow.WebContents.GetZoomLevelAsync();
var ok = await fx.MainWindow.WebContents.GetZoomLevelAsync();
ok.Should().Be(0);
fx.MainWindow.WebContents.SetZoomLevel(2);
await Task.Delay(500);
ok = await fx.MainWindow.WebContents.GetZoomLevelAsync();
ok.Should().Be(2);
BrowserWindow window = null;
try
{
window = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = true }, "about:blank");
await Task.Delay(100.ms());
window.WebContents.SetZoomLevel(0);
await Task.Delay(500.ms());
var ok = await window.WebContents.GetZoomLevelAsync();
ok.Should().Be(0);
window.WebContents.SetZoomLevel(2);
await Task.Delay(500.ms());
ok = await window.WebContents.GetZoomLevelAsync();
ok.Should().Be(2);
}
finally
{
window?.Destroy();
}
}
[SkippableFact(Timeout = 20000)]
[IntegrationFact]
public async Task DevTools_check()
{
Skip.If(Environment.GetEnvironmentVariable("GITHUB_RUN_ID") != null && RuntimeInformation.IsOSPlatform(OSPlatform.OSX), "Skipping test on macOS CI.");
fx.MainWindow.WebContents.IsDevToolsOpened().Should().BeFalse();
fx.MainWindow.WebContents.OpenDevTools();
await Task.Delay(500);
fx.MainWindow.WebContents.IsDevToolsOpened().Should().BeTrue();
fx.MainWindow.WebContents.CloseDevTools();
await Task.Delay(500);
fx.MainWindow.WebContents.IsDevToolsOpened().Should().BeFalse();
fx.MainWindow.WebContents.ToggleDevTools();
await Task.Delay(500);
fx.MainWindow.WebContents.IsDevToolsOpened().Should().BeTrue();
BrowserWindow window = null;
try
{
window = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = true }, "about:blank");
await Task.Delay(3.seconds());
window.WebContents.IsDevToolsOpened().Should().BeFalse();
window.WebContents.OpenDevTools();
await Task.Delay(5.seconds());
window.WebContents.IsDevToolsOpened().Should().BeTrue();
window.WebContents.CloseDevTools();
await Task.Delay(2.seconds());
window.WebContents.IsDevToolsOpened().Should().BeFalse();
}
finally
{
window?.Destroy();
}
}
[Fact(Timeout = 20000)]
[IntegrationFact]
public async Task GetSetAudioMuted_check()
{
fx.MainWindow.WebContents.SetAudioMuted(true);
await Task.Delay(500);
var ok = await fx.MainWindow.WebContents.IsAudioMutedAsync();
this.MainWindow.WebContents.SetAudioMuted(true);
await Task.Delay(500.ms());
var ok = await this.MainWindow.WebContents.IsAudioMutedAsync();
ok.Should().BeTrue();
fx.MainWindow.WebContents.SetAudioMuted(false);
await Task.Delay(500);
ok = await fx.MainWindow.WebContents.IsAudioMutedAsync();
this.MainWindow.WebContents.SetAudioMuted(false);
await Task.Delay(500.ms());
ok = await this.MainWindow.WebContents.IsAudioMutedAsync();
ok.Should().BeFalse();
// Assuming no audio is playing, IsCurrentlyAudibleAsync should return false
// there is no way to play audio in this test
ok = await fx.MainWindow.WebContents.IsCurrentlyAudibleAsync();
ok = await this.MainWindow.WebContents.IsCurrentlyAudibleAsync();
ok.Should().BeFalse();
}
[SkippableFact(Timeout = 20000)]
[IntegrationFact]
public async Task GetSetUserAgent_check()
{
Skip.If(Environment.GetEnvironmentVariable("GITHUB_RUN_ID") != null && RuntimeInformation.IsOSPlatform(OSPlatform.Windows), "Skipping test on Windows CI.");
var ok = await fx.MainWindow.WebContents.GetUserAgentAsync();
ok.Should().NotBeNullOrEmpty();
fx.MainWindow.WebContents.SetUserAgent("MyUserAgent/1.0");
await Task.Delay(1000);
ok = await fx.MainWindow.WebContents.GetUserAgentAsync();
ok.Should().Be("MyUserAgent/1.0");
BrowserWindow window = null;
try
{
window = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = true }, "about:blank");
await Task.Delay(3.seconds());
window.WebContents.SetUserAgent("MyUserAgent/1.0");
await Task.Delay(1.seconds());
var ok = await window.WebContents.GetUserAgentAsync();
ok.Should().Be("MyUserAgent/1.0");
}
finally
{
window?.Destroy();
}
}
}

View File

@@ -0,0 +1,22 @@
using ElectronNET.API;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
namespace ElectronNET.Samples.ElectronHostHook.Controllers
{
public class HomeController : Controller
{
public async Task<IActionResult> Index()
{
string message = "Electron not active";
if (HybridSupport.IsElectronActive)
{
// Call the HostHook defined in ElectronHostHook/index.ts
var result = await Electron.HostHook.CallAsync<string>("ping", "Hello from C#");
message = $"Sent 'Hello from C#', Received: '{result}'";
}
return View("Index", message);
}
}
}

View File

@@ -0,0 +1,3 @@
node_modules
*.js
*.js.map

View File

@@ -0,0 +1,21 @@
import { Socket } from "socket.io";
export class Connector {
constructor(private socket: Socket, public app: any) {
}
on(key: string, javaScriptCode: Function): void {
this.socket.on(key, (...args: any[]) => {
const id: string = args.pop();
try {
javaScriptCode(...args, (data) => {
if (data) {
this.socket.emit(`${key}Complete${id}`, data);
}
});
} catch (error) {
this.socket.emit(`${key}Error${id}`, `Host Hook Exception`, error);
}
});
}
}

View File

@@ -0,0 +1,16 @@
import { Connector } from "./connector";
import { Socket } from "socket.io";
export class HookService extends Connector {
constructor(socket: Socket, public app: any) {
super(socket, app);
}
onHostReady(): void {
// execute your own JavaScript Host logic here
this.on("ping", (msg, done) => {
console.log("Received ping from C#:", msg);
done("pong: " + msg);
});
}
}

View File

@@ -0,0 +1,12 @@
{
"name": "electron-host-hook",
"version": "1.0.0",
"description": "Connector for Electron.NET projects.",
"main": "index.js",
"dependencies": {
"socket.io": "^4.8.1"
},
"devDependencies": {
"typescript": "^5.9.3"
}
}

View File

@@ -0,0 +1,9 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "ES2019",
"sourceMap": true,
"skipLibCheck": true
},
"exclude": ["node_modules"]
}

View File

@@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<ElectronNetDevMode>true</ElectronNetDevMode>
</PropertyGroup>
<Import Project="..\ElectronNET\build\ElectronNET.Core.props" Condition="$(ElectronNetDevMode)" />
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<AspNetCoreHostingModel>OutOfProcess</AspNetCoreHostingModel>
<AspNetCoreModuleName>AspNetCoreModule</AspNetCoreModuleName>
<IsPackable>false</IsPackable>
<TypeScriptModuleKind>commonjs</TypeScriptModuleKind>
<TypeScriptUseNodeJS>true</TypeScriptUseNodeJS>
<TypeScriptTSConfig>ElectronHostHook/tsconfig.json</TypeScriptTSConfig>
<TypeScriptCompileOnSaveEnabled>true</TypeScriptCompileOnSaveEnabled>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
</PropertyGroup>
<ItemGroup>
<TypeScriptCompile Remove="**\node_modules\**" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ElectronNET.API\ElectronNET.API.csproj" Condition="$(ElectronNetDevMode)" />
<ProjectReference Include="..\ElectronNET.AspNet\ElectronNET.AspNet.csproj" Condition="$(ElectronNetDevMode)" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ElectronNET.Core" Version="0.2.0" Condition="'$(ElectronNetDevMode)' != 'true'" />
<PackageReference Include="ElectronNET.Core.AspNet" Version="0.2.0" Condition="'$(ElectronNetDevMode)' != 'true'" />
<PackageReference Include="Microsoft.TypeScript.MSBuild" Version="5.9.3" />
</ItemGroup>
<Import Project="..\ElectronNET\build\ElectronNET.Core.targets" Condition="$(ElectronNetDevMode)" />
</Project>

View File

@@ -0,0 +1,33 @@
using ElectronNET.API;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
namespace ElectronNET.Samples.ElectronHostHook
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseElectron(args, async () =>
{
var window = await Electron.WindowManager.CreateWindowAsync();
});
builder.Services.AddElectron();
builder.Services.AddControllersWithViews();
var app = builder.Build();
app.UseStaticFiles();
app.UseRouting();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
}
}
}

View File

@@ -0,0 +1,12 @@
{
"profiles": {
"ElectronNET.Samples.ElectronHostHook": {
"commandName": "Project",
"launchBrowser": false,
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,25 @@
@model string
@{
Layout = null;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width" />
<title>ElectronHostHook Sample</title>
<style>
body { font-family: sans-serif; padding: 20px; }
.result { padding: 10px; background-color: #f0f0f0; border: 1px solid #ccc; margin-top: 10px; }
</style>
</head>
<body>
<h1>ElectronHostHook Sample</h1>
<p>This sample demonstrates bidirectional communication between C# and the Electron Host process.</p>
<div class="result">
<strong>Result:</strong> @Model
</div>
</body>
</html>

View File

@@ -8,7 +8,7 @@
<Import Project="..\ElectronNET\build\ElectronNET.Core.props" Condition="$(ElectronNetDevMode)" />
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<AspNetCoreHostingModel>OutOfProcess</AspNetCoreHostingModel>
<AspNetCoreModuleName>AspNetCoreModule</AspNetCoreModuleName>
</PropertyGroup>
@@ -76,8 +76,8 @@
<ProjectReference Include="..\ElectronNET.AspNet\ElectronNET.AspNet.csproj" Condition="$(ElectronNetDevMode)" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ElectronNET.Core" Version="0.3.0" Condition="'$(ElectronNetDevMode)' != 'true'" />
<PackageReference Include="ElectronNET.Core.AspNet" Version="0.3.0" Condition="'$(ElectronNetDevMode)' != 'true'" />
<PackageReference Include="ElectronNET.Core" Version="0.3.1" Condition="'$(ElectronNetDevMode)' != 'true'" />
<PackageReference Include="ElectronNET.Core.AspNet" Version="0.3.1" Condition="'$(ElectronNetDevMode)' != 'true'" />
<PackageReference Include="Microsoft.TypeScript.MSBuild" Version="5.9.3" />
</ItemGroup>

View File

@@ -11,7 +11,7 @@
<WebPublishMethod>FileSystem</WebPublishMethod>
<_TargetId>Folder</_TargetId>
<SiteUrlToLaunchAfterPublish />
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<ProjectGuid>6ea447d9-343f-46b8-b456-66557bddbb9f</ProjectGuid>
<SelfContained>true</SelfContained>

View File

@@ -11,7 +11,7 @@
<WebPublishMethod>FileSystem</WebPublishMethod>
<_TargetId>Folder</_TargetId>
<SiteUrlToLaunchAfterPublish />
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<ProjectGuid>6ea447d9-343f-46b8-b456-66557bddbb9f</ProjectGuid>
<SelfContained>true</SelfContained>

View File

@@ -41,10 +41,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test Apps", "Test Apps", "{
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{985D39A7-5216-4945-8167-2FD0CB387BD8}"
ProjectSection(SolutionItems) = preProject
..\.github\workflows\ci.yml = ..\.github\workflows\ci.yml
..\.github\workflows\Build and Publish.yml = ..\.github\workflows\Build and Publish.yml
..\.github\workflows\integration-tests.yml = ..\.github\workflows\integration-tests.yml
..\.github\workflows\PR Validation.yml = ..\.github\workflows\PR Validation.yml
..\.github\workflows\pr-comment.yml = ..\.github\workflows\pr-comment.yml
..\.github\workflows\publish-wiki.yml = ..\.github\workflows\publish-wiki.yml
..\.github\workflows\retry-test-jobs.yml = ..\.github\workflows\retry-test-jobs.yml
..\.github\workflows\trailing-whitespace-check.yml = ..\.github\workflows\trailing-whitespace-check.yml
EndProjectSection
EndProject
@@ -66,6 +68,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ElectronNET.IntegrationTest
EndProject
Project("{54A90642-561A-4BB1-A94E-469ADEE60C69}") = "ElectronNET.Host", "ElectronNET.Host\ElectronNET.Host.esproj", "{1C5FD66E-A1C6-C436-DF7C-3ECE4FEDDFE6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ElectronNET.Samples.ElectronHostHook", "ElectronNET.Samples.ElectronHostHook\ElectronNET.Samples.ElectronHostHook.csproj", "{B8D65F3A-7E54-4632-9F1C-46679237B312}"
ProjectSection(ProjectDependencies) = postProject
{1C5FD66E-A1C6-C436-DF7C-3ECE4FEDDFE6} = {1C5FD66E-A1C6-C436-DF7C-3ECE4FEDDFE6}
{8860606D-6847-F22A-5AED-DF4E0984DD24} = {8860606D-6847-F22A-5AED-DF4E0984DD24}
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -109,6 +117,10 @@ Global
{1C5FD66E-A1C6-C436-DF7C-3ECE4FEDDFE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1C5FD66E-A1C6-C436-DF7C-3ECE4FEDDFE6}.Release|Any CPU.Build.0 = Release|Any CPU
{1C5FD66E-A1C6-C436-DF7C-3ECE4FEDDFE6}.Release|Any CPU.Deploy.0 = Release|Any CPU
{B8D65F3A-7E54-4632-9F1C-46679237B312}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B8D65F3A-7E54-4632-9F1C-46679237B312}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B8D65F3A-7E54-4632-9F1C-46679237B312}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B8D65F3A-7E54-4632-9F1C-46679237B312}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -124,6 +136,7 @@ Global
{06CAADC7-DE5B-47B4-AB2A-E9501459A2D1} = {D36CDFFD-3438-42E4-A7FF-88BA19AC4964}
{AE877E48-6B44-63C2-8EA0-DB58D096B553} = {75129C45-FC6F-41B0-A485-07F4A7E031ED}
{1C5FD66E-A1C6-C436-DF7C-3ECE4FEDDFE6} = {1BB6F634-2831-4496-83A6-BC6761DCEC8D}
{B8D65F3A-7E54-4632-9F1C-46679237B312} = {EDCBFC49-2AEE-4BAF-9368-4409298C52FC}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {81A62E71-9E04-4EFE-AD5C-23165375F8EF}

View File

@@ -1,6 +1,8 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToExtensionBlock/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/LINE_FEED_AT_FILE_END/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ACCESSOR_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CI/@EntryIndexedValue">CI</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>

View File

@@ -9,7 +9,7 @@
<ElectronSplashScreen></ElectronSplashScreen>
<ElectronIcon></ElectronIcon>
<PackageIcon></PackageIcon>
<PackageId>$(MSBuildProjectName.Replace(".", "-").ToLower())</PackageId>
<ElectronPackageId>$(MSBuildProjectName.Replace(".", "-").ToLower())</ElectronPackageId>
<ElectronBuilderJson>electron-builder.json</ElectronBuilderJson>
<Title>$(MSBuildProjectName)</Title>
</PropertyGroup>

View File

@@ -4,7 +4,7 @@
<PropertyGroup Condition="'$(ElectronExecutable)' == ''">
<WinPrefix>win</WinPrefix>
<ElectronExecutable Condition="'$(RuntimeIdentifier.StartsWith($(WinPrefix)))' == 'true'">$(Title)</ElectronExecutable>
<ElectronExecutable Condition="'$(RuntimeIdentifier.StartsWith($(WinPrefix)))' != 'true'">$(PackageId)</ElectronExecutable>
<ElectronExecutable Condition="'$(RuntimeIdentifier.StartsWith($(WinPrefix)))' != 'true'">$(ElectronPackageId)</ElectronExecutable>
</PropertyGroup>
<PropertyGroup>

View File

@@ -124,7 +124,7 @@
</PropertyGroup>
<ItemGroup>
<TemplateProperty Include="PackageId" Value="$(PackageId)" />
<TemplateProperty Include="ElectronPackageId" Value="$(ElectronPackageId)" />
<TemplateProperty Include="Title" Value="$(Title)" />
<TemplateProperty Include="ElectronTitle" Value="$(ElectronTitle)" />
<TemplateProperty Include="Version" Value="$(Version)" />
@@ -452,7 +452,7 @@
<PropertyGroup>
<ElectronPaParams></ElectronPaParams>
<ElectronPaParams Condition="'$(PackageId)' != ''">$(ElectronPaParams) -c.appId &quot;$(PackageId)&quot;</ElectronPaParams>
<ElectronPaParams Condition="'$(ElectronPackageId)' != ''">$(ElectronPaParams) -c.appId &quot;$(ElectronPackageId)&quot;</ElectronPaParams>
<ElectronPaParams Condition="'$(Version)' != ''">$(ElectronPaParams) -c.buildVersion &quot;$(Version)&quot;</ElectronPaParams>
<ElectronPaParams Condition="'$(Copyright)' != ''">$(ElectronPaParams) -c.copyright &quot;$(Copyright)&quot;</ElectronPaParams>
<ElectronPaParams>$(ElectronPaParams) -c.extraResources &quot;bin/**/*&quot;</ElectronPaParams>

View File

@@ -479,7 +479,7 @@
Description="The version of the app, following the major.minor.patch pattern. Version numbers may include a pre-release suffix."
Category="AppInfo" />
<StringProperty Name="PackageId"
<StringProperty Name="ElectronPackageId"
DisplayName="App Identifier"
Category="AppInfo">
</StringProperty>

View File

@@ -1,13 +1,13 @@
{
"name": "$(PackageId)",
"name": "$(ElectronPackageId)",
"productName": "$(ElectronTitle)",
"build": {
"appId": "$(PackageId)",
"appId": "$(ElectronPackageId)",
"linux": {
"desktop": {
"entry": { "Name": "$(Title)" }
},
"executableName": "$(PackageId)"
"executableName": "$(ElectronPackageId)"
},
"deb": {
"desktop": {

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>0.3.0</Version>
<Version>0.3.1</Version>
<PackageNamePrefix>ElectronNET.Core</PackageNamePrefix>
<Authors>Gregor Biswanger, Florian Rappl, softworkz</Authors>
<Product>Electron.NET</Product>