mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
feat.airapp与融合桌面
This commit is contained in:
81
LanMountainDesktop.Tests/AirAppLauncherServiceTests.cs
Normal file
81
LanMountainDesktop.Tests/AirAppLauncherServiceTests.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using LanMountainDesktop.ComponentSystem;
|
||||
using LanMountainDesktop.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace LanMountainDesktop.Tests;
|
||||
|
||||
public sealed class AirAppLauncherServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public void BuildOpenRequest_IncludesWorldClockSourceContext()
|
||||
{
|
||||
var request = AirAppLauncherService.BuildOpenRequest(
|
||||
AirAppLauncherService.WorldClockAppId,
|
||||
BuiltInComponentIds.DesktopWorldClock,
|
||||
"placement-7",
|
||||
42);
|
||||
|
||||
Assert.Equal("world-clock", request.AppId);
|
||||
Assert.Equal(BuiltInComponentIds.DesktopWorldClock, request.SourceComponentId);
|
||||
Assert.Equal("placement-7", request.SourcePlacementId);
|
||||
Assert.Equal(42, request.RequesterProcessId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildOpenRequest_NormalizesEmptyOptionalContext()
|
||||
{
|
||||
var request = AirAppLauncherService.BuildOpenRequest(
|
||||
AirAppLauncherService.WorldClockAppId,
|
||||
null,
|
||||
" ",
|
||||
42);
|
||||
|
||||
Assert.Equal("world-clock", request.AppId);
|
||||
Assert.Null(request.SourceComponentId);
|
||||
Assert.Null(request.SourcePlacementId);
|
||||
Assert.Equal(42, request.RequesterProcessId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildOpenRequest_IncludesWhiteboardSourceContext()
|
||||
{
|
||||
var request = AirAppLauncherService.BuildOpenRequest(
|
||||
AirAppLauncherService.WhiteboardAppId,
|
||||
BuiltInComponentIds.DesktopWhiteboard,
|
||||
"whiteboard-placement",
|
||||
99);
|
||||
|
||||
Assert.Equal("whiteboard", request.AppId);
|
||||
Assert.Equal(BuiltInComponentIds.DesktopWhiteboard, request.SourceComponentId);
|
||||
Assert.Equal("whiteboard-placement", request.SourcePlacementId);
|
||||
Assert.Equal(99, request.RequesterProcessId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildSingleInstanceKey_UsesWhiteboardComponentAndPlacement()
|
||||
{
|
||||
var key = AirAppLauncherService.BuildSingleInstanceKey(
|
||||
AirAppLauncherService.WhiteboardAppId,
|
||||
BuiltInComponentIds.DesktopBlackboardLandscape,
|
||||
"placement-3");
|
||||
|
||||
Assert.Equal(
|
||||
$"whiteboard:{BuiltInComponentIds.DesktopBlackboardLandscape}:placement-3",
|
||||
key);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateBrokerStartInfo_UsesAirAppBrokerCommandAndRequesterPid()
|
||||
{
|
||||
var startInfo = AirAppLauncherService.CreateBrokerStartInfo(
|
||||
@"C:\Apps\LanMountainDesktop.Launcher.exe",
|
||||
12345);
|
||||
|
||||
Assert.Equal(@"C:\Apps\LanMountainDesktop.Launcher.exe", startInfo.FileName);
|
||||
Assert.Equal(@"C:\Apps", startInfo.WorkingDirectory);
|
||||
Assert.False(startInfo.UseShellExecute);
|
||||
Assert.Equal(
|
||||
["air-app-broker", "--requester-pid", "12345"],
|
||||
startInfo.ArgumentList);
|
||||
}
|
||||
}
|
||||
110
LanMountainDesktop.Tests/ComponentCategoryIconResolverTests.cs
Normal file
110
LanMountainDesktop.Tests/ComponentCategoryIconResolverTests.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using FluentIcons.Common;
|
||||
using LanMountainDesktop.ComponentSystem;
|
||||
using Xunit;
|
||||
|
||||
namespace LanMountainDesktop.Tests;
|
||||
|
||||
public sealed class ComponentCategoryIconResolverTests
|
||||
{
|
||||
[Fact]
|
||||
public void ResolveCategoryIcon_AllCategory_ReturnsApps()
|
||||
{
|
||||
var result = ComponentCategoryIconResolver.ResolveCategoryIcon("all", []);
|
||||
Assert.Equal(Icon.Apps, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveCategoryIcon_ResolvesFromFirstComponentIconKey()
|
||||
{
|
||||
var components = new[]
|
||||
{
|
||||
new DesktopComponentDefinition("test1", "Test", "Clock", "Clock", 2, 2, false, true)
|
||||
};
|
||||
var result = ComponentCategoryIconResolver.ResolveCategoryIcon("Clock", components);
|
||||
Assert.Equal(Icon.Clock, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveCategoryIcon_WeatherSunny_ResolvesCorrectly()
|
||||
{
|
||||
var components = new[]
|
||||
{
|
||||
new DesktopComponentDefinition("test1", "Test", "WeatherSunny", "Weather", 2, 2, false, true)
|
||||
};
|
||||
var result = ComponentCategoryIconResolver.ResolveCategoryIcon("Weather", components);
|
||||
Assert.Equal(Icon.WeatherSunny, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveCategoryIcon_News_ResolvesCorrectly()
|
||||
{
|
||||
var components = new[]
|
||||
{
|
||||
new DesktopComponentDefinition("test1", "Test", "News", "Info", 2, 2, false, true)
|
||||
};
|
||||
var result = ComponentCategoryIconResolver.ResolveCategoryIcon("Info", components);
|
||||
Assert.Equal(Icon.News, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveCategoryIcon_Edit_ResolvesCorrectly()
|
||||
{
|
||||
var components = new[]
|
||||
{
|
||||
new DesktopComponentDefinition("test1", "Test", "Edit", "Board", 2, 2, false, true)
|
||||
};
|
||||
var result = ComponentCategoryIconResolver.ResolveCategoryIcon("Board", components);
|
||||
Assert.Equal(Icon.Edit, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveCategoryIcon_InvalidIconKey_FallsBackToApps()
|
||||
{
|
||||
var components = new[]
|
||||
{
|
||||
new DesktopComponentDefinition("test1", "Test", "NonExistentIcon", "Other", 2, 2, false, true)
|
||||
};
|
||||
var result = ComponentCategoryIconResolver.ResolveCategoryIcon("Other", components);
|
||||
Assert.Equal(Icon.Apps, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveCategoryIcon_EmptyComponents_FallsBackToApps()
|
||||
{
|
||||
var result = ComponentCategoryIconResolver.ResolveCategoryIcon("Unknown", []);
|
||||
Assert.Equal(Icon.Apps, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveCategoryIcon_Play_ResolvesCorrectly()
|
||||
{
|
||||
var components = new[]
|
||||
{
|
||||
new DesktopComponentDefinition("test1", "Test", "Play", "Media", 2, 2, false, true)
|
||||
};
|
||||
var result = ComponentCategoryIconResolver.ResolveCategoryIcon("Media", components);
|
||||
Assert.Equal(Icon.Play, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveCategoryIcon_Calculator_ResolvesCorrectly()
|
||||
{
|
||||
var components = new[]
|
||||
{
|
||||
new DesktopComponentDefinition("test1", "Test", "Calculator", "Calculator", 2, 2, false, true)
|
||||
};
|
||||
var result = ComponentCategoryIconResolver.ResolveCategoryIcon("Calculator", components);
|
||||
Assert.Equal(Icon.Calculator, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveCategoryIcon_Folder_ResolvesCorrectly()
|
||||
{
|
||||
var components = new[]
|
||||
{
|
||||
new DesktopComponentDefinition("test1", "Test", "Folder", "File", 2, 2, false, true)
|
||||
};
|
||||
var result = ComponentCategoryIconResolver.ResolveCategoryIcon("File", components);
|
||||
Assert.Equal(Icon.Folder, result);
|
||||
}
|
||||
}
|
||||
@@ -117,6 +117,40 @@ public sealed class DesktopComponentRenderModeTests
|
||||
Assert.NotNull(WeatherIconAssetResolver.ResolveAssetUri(styleId, 999, "Unknown", isDaylight: true));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(WeatherVisualStyleId.GoogleWeatherV4, "google")]
|
||||
[InlineData(WeatherVisualStyleId.Geometric, "geometric")]
|
||||
[InlineData(WeatherVisualStyleId.Breezy, "breezy")]
|
||||
[InlineData(WeatherVisualStyleId.LemonFlutter, "lemon")]
|
||||
public void WeatherSceneProfileResolver_UsesDistinctRendererPerVisualStyle(string styleId, string expectedRenderer)
|
||||
{
|
||||
var profile = WeatherSceneProfileResolver.Resolve(styleId, MaterialWeatherCondition.Rain, isNight: false, isLive: true);
|
||||
|
||||
Assert.Equal(expectedRenderer, profile.RendererId);
|
||||
Assert.Equal("rain", profile.WeatherLayerId);
|
||||
Assert.True(profile.IsLive);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(MaterialWeatherCondition.Clear, "clear")]
|
||||
[InlineData(MaterialWeatherCondition.PartlyCloudy, "partly-cloudy")]
|
||||
[InlineData(MaterialWeatherCondition.Cloudy, "cloudy")]
|
||||
[InlineData(MaterialWeatherCondition.Rain, "rain")]
|
||||
[InlineData(MaterialWeatherCondition.Storm, "storm")]
|
||||
[InlineData(MaterialWeatherCondition.Snow, "snow")]
|
||||
[InlineData(MaterialWeatherCondition.Fog, "fog")]
|
||||
[InlineData(MaterialWeatherCondition.Haze, "haze")]
|
||||
[InlineData(MaterialWeatherCondition.Unknown, "ambient")]
|
||||
public void WeatherSceneProfileResolver_UsesDistinctWeatherLayerPerCondition(MaterialWeatherCondition condition, string expectedLayer)
|
||||
{
|
||||
var profile = WeatherSceneProfileResolver.Resolve(WeatherVisualStyleId.Breezy, condition, isNight: true, isLive: false);
|
||||
|
||||
Assert.Equal("breezy", profile.RendererId);
|
||||
Assert.Equal(expectedLayer, profile.WeatherLayerId);
|
||||
Assert.True(profile.IsNight);
|
||||
Assert.False(profile.IsLive);
|
||||
}
|
||||
|
||||
private static DesktopComponentRuntimeDescriptor CreateDescriptor()
|
||||
{
|
||||
Assert.True(CreateRuntimeRegistry().TryGetDescriptor(ComponentId, out var descriptor));
|
||||
|
||||
164
LanMountainDesktop.Tests/LauncherAirAppLifecycleServiceTests.cs
Normal file
164
LanMountainDesktop.Tests/LauncherAirAppLifecycleServiceTests.cs
Normal file
@@ -0,0 +1,164 @@
|
||||
using System.Diagnostics;
|
||||
using LanMountainDesktop.ComponentSystem;
|
||||
using LanMountainDesktop.Launcher;
|
||||
using LanMountainDesktop.Launcher.Services.AirApp;
|
||||
using LanMountainDesktop.Shared.IPC.Abstractions.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace LanMountainDesktop.Tests;
|
||||
|
||||
public sealed class LauncherAirAppLifecycleServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task OpenAsync_ReusesExistingInstanceForSameKey()
|
||||
{
|
||||
var starter = new TestAirAppProcessStarter(Process.GetCurrentProcess());
|
||||
var service = new LauncherAirAppLifecycleService(starter);
|
||||
var request = new AirAppOpenRequest(
|
||||
"whiteboard",
|
||||
BuiltInComponentIds.DesktopWhiteboard,
|
||||
"placement-1",
|
||||
Environment.ProcessId);
|
||||
|
||||
var first = await service.OpenAsync(request);
|
||||
var second = await service.OpenAsync(request);
|
||||
|
||||
Assert.True(first.Accepted);
|
||||
Assert.True(second.Accepted);
|
||||
Assert.Equal("started", first.Code);
|
||||
Assert.Equal("activated_existing", second.Code);
|
||||
Assert.Equal(1, starter.StartCount);
|
||||
Assert.Equal(first.Instance!.InstanceKey, second.Instance!.InstanceKey);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OpenAsync_PrunesExitedRegisteredInstanceBeforeRestart()
|
||||
{
|
||||
var starter = new TestAirAppProcessStarter(Process.GetCurrentProcess());
|
||||
var service = new LauncherAirAppLifecycleService(starter);
|
||||
var instanceKey = AirAppInstanceKey.Build(
|
||||
"whiteboard",
|
||||
BuiltInComponentIds.DesktopWhiteboard,
|
||||
"placement-2");
|
||||
|
||||
_ = await service.RegisterAsync(new AirAppRegistrationRequest(
|
||||
instanceKey,
|
||||
"whiteboard",
|
||||
"dead-session",
|
||||
int.MaxValue,
|
||||
"Dead Air APP",
|
||||
BuiltInComponentIds.DesktopWhiteboard,
|
||||
"placement-2"));
|
||||
|
||||
var result = await service.OpenAsync(new AirAppOpenRequest(
|
||||
"whiteboard",
|
||||
BuiltInComponentIds.DesktopWhiteboard,
|
||||
"placement-2",
|
||||
Environment.ProcessId));
|
||||
|
||||
Assert.True(result.Accepted);
|
||||
Assert.Equal("started", result.Code);
|
||||
Assert.Equal(1, starter.StartCount);
|
||||
Assert.Equal(Environment.ProcessId, result.Instance!.ProcessId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HasLiveAirApps_ReturnsFalseAfterUnregisteringLastInstance()
|
||||
{
|
||||
var service = new LauncherAirAppLifecycleService(new TestAirAppProcessStarter(Process.GetCurrentProcess()));
|
||||
var instanceKey = AirAppInstanceKey.Build("world-clock", BuiltInComponentIds.DesktopWorldClock, "clock-1");
|
||||
|
||||
_ = await service.RegisterAsync(new AirAppRegistrationRequest(
|
||||
instanceKey,
|
||||
"world-clock",
|
||||
"session",
|
||||
Environment.ProcessId,
|
||||
"World Clock",
|
||||
BuiltInComponentIds.DesktopWorldClock,
|
||||
"clock-1"));
|
||||
|
||||
Assert.True(service.HasLiveAirApps());
|
||||
|
||||
_ = await service.UnregisterAsync(instanceKey, Environment.ProcessId);
|
||||
|
||||
Assert.False(service.HasLiveAirApps());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AirAppBrokerLifetime_KeepsAliveWhileRequesterIsAlive()
|
||||
{
|
||||
var service = new LauncherAirAppLifecycleService(new TestAirAppProcessStarter(null));
|
||||
|
||||
Assert.True(LanMountainDesktop.Launcher.App.ShouldKeepAirAppBrokerAlive(Environment.ProcessId, service));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AirAppBrokerLifetime_StopsWhenRequesterExitedAndNoAirAppsRemain()
|
||||
{
|
||||
var service = new LauncherAirAppLifecycleService(new TestAirAppProcessStarter(null));
|
||||
|
||||
Assert.False(LanMountainDesktop.Launcher.App.ShouldKeepAirAppBrokerAlive(int.MaxValue, service));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AirAppBrokerLifetime_KeepsAliveWhileAirAppIsAlive()
|
||||
{
|
||||
var service = new LauncherAirAppLifecycleService(new TestAirAppProcessStarter(null));
|
||||
var instanceKey = AirAppInstanceKey.Build("world-clock", BuiltInComponentIds.DesktopWorldClock, "clock-2");
|
||||
|
||||
_ = await service.RegisterAsync(new AirAppRegistrationRequest(
|
||||
instanceKey,
|
||||
"world-clock",
|
||||
"session",
|
||||
Environment.ProcessId,
|
||||
"World Clock",
|
||||
BuiltInComponentIds.DesktopWorldClock,
|
||||
"clock-2"));
|
||||
|
||||
Assert.True(LanMountainDesktop.Launcher.App.ShouldKeepAirAppBrokerAlive(int.MaxValue, service));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CommandContext_RecognizesAirAppBrokerAsGuiCommandInDebugEnvironment()
|
||||
{
|
||||
var oldEnvironment = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT");
|
||||
try
|
||||
{
|
||||
Environment.SetEnvironmentVariable("DOTNET_ENVIRONMENT", "Development");
|
||||
|
||||
var context = CommandContext.FromArgs(["air-app-broker", "--requester-pid", "42"]);
|
||||
|
||||
Assert.True(context.IsGuiCommand);
|
||||
Assert.True(context.IsAirAppBrokerCommand);
|
||||
Assert.True(context.IsDebugMode);
|
||||
Assert.Equal(42, context.GetIntOption("requester-pid", 0));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Environment.SetEnvironmentVariable("DOTNET_ENVIRONMENT", oldEnvironment);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TestAirAppProcessStarter : IAirAppProcessStarter
|
||||
{
|
||||
private readonly Process? _process;
|
||||
|
||||
public TestAirAppProcessStarter(Process? process)
|
||||
{
|
||||
_process = process;
|
||||
}
|
||||
|
||||
public int StartCount { get; private set; }
|
||||
|
||||
public Process? Start(
|
||||
string appId,
|
||||
string sessionId,
|
||||
string instanceKey,
|
||||
string? sourceComponentId,
|
||||
string? sourcePlacementId)
|
||||
{
|
||||
StartCount++;
|
||||
return _process;
|
||||
}
|
||||
}
|
||||
}
|
||||
42
LanMountainDesktop.Tests/MusicControlViewModelTests.cs
Normal file
42
LanMountainDesktop.Tests/MusicControlViewModelTests.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using LanMountainDesktop.Services;
|
||||
using LanMountainDesktop.ViewModels;
|
||||
using Xunit;
|
||||
|
||||
namespace LanMountainDesktop.Tests;
|
||||
|
||||
public sealed class MusicControlViewModelTests : IDisposable
|
||||
{
|
||||
private readonly MusicControlViewModel _viewModel;
|
||||
|
||||
public MusicControlViewModelTests()
|
||||
{
|
||||
_viewModel = new MusicControlViewModel();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispose_CanBeCalledMultipleTimes()
|
||||
{
|
||||
_viewModel.Dispose();
|
||||
_viewModel.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Dispose_StopsRefreshAfterCancellation()
|
||||
{
|
||||
var refreshTask = _viewModel.RefreshAsync();
|
||||
_viewModel.Dispose();
|
||||
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ViewModel_InitializesWithNoSession()
|
||||
{
|
||||
Assert.True(_viewModel.IsNoMedia);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_viewModel.Dispose();
|
||||
}
|
||||
}
|
||||
88
LanMountainDesktop.Tests/WindowLayerIsolationTests.cs
Normal file
88
LanMountainDesktop.Tests/WindowLayerIsolationTests.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using Xunit;
|
||||
|
||||
namespace LanMountainDesktop.Tests;
|
||||
|
||||
public sealed class WindowLayerIsolationTests
|
||||
{
|
||||
[Fact]
|
||||
public void AirAppWindow_DoesNotUseDesktopBottomMostOrTopmostPromotion()
|
||||
{
|
||||
var source = ReadRepositoryFile("LanMountainDesktop.AirAppHost", "AirAppWindow.axaml.cs");
|
||||
|
||||
Assert.DoesNotContain("WindowBottomMostServiceFactory", source);
|
||||
Assert.DoesNotContain("IWindowBottomMostService", source);
|
||||
Assert.DoesNotContain("SendToBottom", source);
|
||||
Assert.DoesNotContain("Topmost = true", source);
|
||||
Assert.DoesNotContain("Topmost=true", source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AirAppWindowDescriptor_DefinesSupportedChromeModes()
|
||||
{
|
||||
var source = ReadRepositoryFile("LanMountainDesktop.AirAppHost", "AirAppWindowDescriptor.cs");
|
||||
|
||||
Assert.Contains("AirAppWindowChromeMode", source);
|
||||
Assert.Contains("Standard", source);
|
||||
Assert.Contains("Borderless", source);
|
||||
Assert.Contains("FullScreen", source);
|
||||
Assert.Contains("Tool", source);
|
||||
Assert.Contains("BackgroundOnly", source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AirAppWindowDescriptor_MapsBuiltInAppsToExpectedChromeModes()
|
||||
{
|
||||
var source = ReadRepositoryFile("LanMountainDesktop.AirAppHost", "AirAppWindowDescriptor.cs");
|
||||
|
||||
Assert.Contains("AirAppLaunchOptions.WorldClockAppId", source);
|
||||
Assert.Contains("AirAppWindowChromeMode.Standard", source);
|
||||
Assert.Contains("AirAppLaunchOptions.WhiteboardAppId", source);
|
||||
Assert.Contains("AirAppWindowChromeMode.FullScreen", source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FusedDesktopWindows_KeepDesktopBottomMostBoundary()
|
||||
{
|
||||
var desktopWidgetWindow = ReadRepositoryFile("LanMountainDesktop", "Views", "DesktopWidgetWindow.axaml.cs");
|
||||
var transparentOverlayWindow = ReadRepositoryFile("LanMountainDesktop", "Views", "TransparentOverlayWindow.axaml.cs");
|
||||
|
||||
Assert.Contains("WindowBottomMostServiceFactory.GetOrCreate()", desktopWidgetWindow);
|
||||
Assert.Contains("RefreshDesktopLayer", desktopWidgetWindow);
|
||||
Assert.Contains("SendToBottom", desktopWidgetWindow);
|
||||
|
||||
Assert.Contains("WindowBottomMostServiceFactory.GetOrCreate()", transparentOverlayWindow);
|
||||
Assert.Contains("RefreshDesktopLayer", transparentOverlayWindow);
|
||||
Assert.Contains("SendToBottom", transparentOverlayWindow);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FusedDesktopManager_RefreshesDesktopLayerAfterShowingWidgets()
|
||||
{
|
||||
var source = ReadRepositoryFile("LanMountainDesktop", "Services", "FusedDesktopManagerService.cs");
|
||||
|
||||
Assert.Contains("existingWindow.RefreshDesktopLayer()", source);
|
||||
Assert.Contains("window.RefreshDesktopLayer()", source);
|
||||
}
|
||||
|
||||
private static string ReadRepositoryFile(params string[] segments)
|
||||
{
|
||||
var directory = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
while (directory is not null)
|
||||
{
|
||||
var candidate = Path.Combine(new[] { directory.FullName }.Concat(segments).ToArray());
|
||||
if (File.Exists(candidate))
|
||||
{
|
||||
return File.ReadAllText(candidate);
|
||||
}
|
||||
|
||||
if (File.Exists(Path.Combine(directory.FullName, "LanMountainDesktop.slnx")))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
directory = directory.Parent;
|
||||
}
|
||||
|
||||
throw new FileNotFoundException($"Could not locate repository file '{Path.Combine(segments)}'.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user