diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 2adf293..b0997a3 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -13,6 +13,8 @@ - Каждый тег XML‑комментария располагай на отдельной строке - Порядок тегов: `` → `` → `` → `` → `` → `` - Для сложных публичных метдов генерируй блок с простым примером использования кода внутри тега `` +- Примеры использования кода внутри тега `` должны быть лаконичными и демонстрировать только ключевые моменты использования метода, без избыточных деталей +- Примеры кода нужно заворачивать в тег `` внутри тега ``, чтобы обеспечить правильное форматирование и подсветку синтаксиса, а также размещать в элементе `` что бы не использовать экранирование xml-символов Примеры: - `Краткое описание сущности` @@ -67,4 +69,19 @@ public string PropertyName { get; set => Set(ref field, value); } ## Совместимость целей - В рабочем пространстве используются целевые платформы: `.NET Standard 2.0` и `.NET 10` -- Применяй современные возможности языка и платформы только если они доступны для соответствующей целевой платформы проекта \ No newline at end of file +- Применяй современные возможности языка и платформы только если они доступны для соответствующей целевой платформы проекта + +## Модульные тесты с использованием MSTest +- Используй платформу MSTest для написания модульных тестов +- Классы тестов должны быть помечены атрибутом `[TestClass]` +- Методы тестов должны быть помечены атрибутом `[TestMethod]` +- Для параметризованных тестов используй `[DataTestMethod]` и `[DataRow]` +- Следуй паттерну Arrange-Act-Assert (AAA) при написании тестов +- Каждый тест должен проверять только одно поведение +- Избегай использования статических полей в тестах +- Убедись, что тесты могут выполняться в любом порядке и параллельно +- Для проверки исключений используй `Assert.ThrowsException` +- Используй `Debug.WriteLine` для вывода отладочной информации о процессе выполнения тестов, если в тесте есть промежуточные вычисления +- При написании Assert-методов добавляй сообщения об ошибках на русском языке +- Файлы модульных тестов должны создаваться с учётом структуры каталогов тестируемого кода +- Каждый модульный тест должен быть снабжён XML‑документацией, описывающей его назначение и поведение \ No newline at end of file diff --git a/MathCore.Hosting.WPF/ApplicationHosting.cs b/MathCore.Hosting.WPF/ApplicationHosting.cs index 72dc206..ebb1cef 100644 --- a/MathCore.Hosting.WPF/ApplicationHosting.cs +++ b/MathCore.Hosting.WPF/ApplicationHosting.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; + using MathCore.DI; // ReSharper disable EventNeverSubscribedTo.Global @@ -11,8 +12,8 @@ namespace MathCore.Hosting.WPF; /// /// /// Пример настройки приложения: -/// 1. Создаём класс App.xaml.cs, наследуя его от ApplicationHosting: -/// +/// 1. Создаём класс App.xaml.cs, наследуя его от ApplicationHosting в разметке XAML: +/// (); /// } /// } -/// +/// ]]> /// 2. Изменяем корень файла App.xaml, указывая локальный класс (унаследован от ApplicationHosting): -/// -/// /// -/// -/// +/// +/// ]]> /// 3. Использование зарегистрированного сервиса в окне: -/// +/// +/// ]]> +/// +/// Пример компактной настройки приложения: +/// ConfigureServices += (_, services) => services.AddSingleton(); +/// } +/// ]]> /// public abstract class ApplicationHosting : Application { @@ -198,7 +207,7 @@ protected override async void OnStartup(StartupEventArgs e) } catch (Exception error) { - if(!HandleStartupException(error)) + if (!HandleStartupException(error)) // ReSharper disable once AsyncVoidThrowException throw; } @@ -225,7 +234,7 @@ protected override async void OnExit(ExitEventArgs e) } catch (Exception error) { - if(!HandleExitException(error)) + if (!HandleExitException(error)) // ReSharper disable once AsyncVoidThrowException throw; } diff --git a/MathCore.Hosting.WPF/Extensions/CommandEx.cs b/MathCore.Hosting.WPF/Extensions/CommandEx.cs index 45b82e1..f9ecc7e 100644 --- a/MathCore.Hosting.WPF/Extensions/CommandEx.cs +++ b/MathCore.Hosting.WPF/Extensions/CommandEx.cs @@ -5,6 +5,12 @@ namespace MathCore.Hosting.WPF.Extensions; /// Методы расширения для конфигурации команд +/// +/// DoWork()); +/// command.WithLogging(logger); +/// ]]> +/// public static class CommandEx { /// Добавляет логирование событий выполнения команды diff --git a/MathCore.Hosting.WPF/HostedServiceLocator.cs b/MathCore.Hosting.WPF/HostedServiceLocator.cs index da218d5..de4f56f 100644 --- a/MathCore.Hosting.WPF/HostedServiceLocator.cs +++ b/MathCore.Hosting.WPF/HostedServiceLocator.cs @@ -1,25 +1,50 @@ namespace MathCore.Hosting.WPF; /// Базовый класс для реализации локатора сервисов приложения +/// +/// (); +/// ]]> +/// public class ServiceLocatorHosted : ServiceLocator { static ServiceLocatorHosted() => ApplicationHosting.HostBuilderConfiguratorAdd(ConfigureAppServices); private static void ConfigureAppServices(IHostBuilder HostBuilder) => HostBuilder.ConfigureServices(ConfigureServices); + /// Контейнер сервисов приложения protected override IServiceProvider Services => ApplicationHosting.Services; + /// Получить сервис по типу + /// Тип сервиса + /// Экземпляр сервиса или public object? this[Type ServiceType] => Services.GetService(ServiceType); + /// Получить сервис по имени типа + /// Имя типа сервиса + /// Экземпляр сервиса или public object? this[string ServiceTypeName] => Type.GetType(ServiceTypeName) is { } type ? this[type] : null; + /// Получить сервис по типу + /// Тип сервиса + /// Экземпляр сервиса или public virtual object? GetService(Type ServiceType) => Services.GetService(ServiceType); + /// Получить сервис по типу + /// Тип сервиса + /// Экземпляр сервиса или public virtual T? GetService() => (T?)GetService(typeof(T)); + /// Получить обязательный сервис по типу + /// Тип сервиса + /// Экземпляр сервиса public virtual object GetRequiredService(Type ServiceType) => Services.GetRequiredService(ServiceType); #pragma warning disable CS8714 // Тип не может быть использован как параметр типа в универсальном типе или методе. Допустимость значения NULL для аргумента типа не соответствует ограничению "notnull". + /// Получить обязательный сервис по типу + /// Тип сервиса + /// Экземпляр сервиса public virtual T GetRequiredService() => Services.GetRequiredService(); #pragma warning restore CS8714 // Тип не может быть использован как параметр типа в универсальном типе или методе. Допустимость значения NULL для аргумента типа не соответствует ограничению "notnull". } \ No newline at end of file diff --git a/MathCore.Hosting.WPF/Infrastructure/RequiredMemberAttribute.cs b/MathCore.Hosting.WPF/Infrastructure/RequiredMemberAttribute.cs index 8e6e3b5..30a511b 100644 --- a/MathCore.Hosting.WPF/Infrastructure/RequiredMemberAttribute.cs +++ b/MathCore.Hosting.WPF/Infrastructure/RequiredMemberAttribute.cs @@ -3,23 +3,55 @@ // ReSharper disable once CheckNamespace namespace System.Runtime.CompilerServices; +/// Атрибут обязательного члена +/// +/// +/// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, Inherited = false)] internal sealed class RequiredMemberAttribute : Attribute { } +/// Атрибут требования возможностей компилятора +/// +/// +/// [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] internal sealed class CompilerFeatureRequiredAttribute : Attribute { + /// Имя возможности обязательных членов public const string RequiredMembers = nameof(RequiredMembers); + /// Имя возможности ссылочных структур public const string RefStructs = nameof(RefStructs); + /// Имя требуемой возможности public string FeatureName { get; } + /// Признак необязательной возможности public bool IsOptional { get; init; } + /// Создать атрибут требования возможностей компилятора + /// Имя требуемой возможности public CompilerFeatureRequiredAttribute(string FeatureName) => this.FeatureName = FeatureName; } +/// Маркер инициализации внешнего члена +/// +/// +/// internal sealed class IsExternalInit { } #endif \ No newline at end of file diff --git a/MathCore.Hosting.WPF/MathCore.Hosting.WPF.csproj b/MathCore.Hosting.WPF/MathCore.Hosting.WPF.csproj index 2308db8..f7b0932 100644 --- a/MathCore.Hosting.WPF/MathCore.Hosting.WPF.csproj +++ b/MathCore.Hosting.WPF/MathCore.Hosting.WPF.csproj @@ -6,7 +6,6 @@ net10.0-windows; net8.0-windows; net4.8-windows; - net4.7-windows; net4.6.1-windows; true @@ -17,7 +16,7 @@ - 1.0.1 + 1.0.2 Обновление пакетов @@ -50,7 +49,7 @@ - + diff --git a/MathCore.Hosting.WPF/WindowViewModelAttribute.cs b/MathCore.Hosting.WPF/WindowViewModelAttribute.cs index 7a58d20..9c5c619 100644 --- a/MathCore.Hosting.WPF/WindowViewModelAttribute.cs +++ b/MathCore.Hosting.WPF/WindowViewModelAttribute.cs @@ -6,9 +6,17 @@ namespace MathCore.Hosting.WPF; /// Модель-представления окна +/// Тип окна для модели-представления +/// +/// +/// [AttributeUsage(AttributeTargets.Class)] public class WindowViewModelAttribute(Type WindowType) : Attribute { + /// Создать атрибут без указания типа окна public WindowViewModelAttribute() : this(null!) { } /// Тип окна для модели-представления @@ -19,6 +27,13 @@ public WindowViewModelAttribute() : this(null!) { } #if NET7_0_OR_GREATER /// Модель-представления окна +/// Тип окна +/// +/// ] +/// public sealed class MainWindowViewModel : ViewModel { } +/// ]]> +/// [AttributeUsage(AttributeTargets.Class)] public sealed class WindowViewModelAttribute() : Attribute where TWindow : Window {