diff --git a/.gitignore b/.gitignore index 53a6d75..9b47fee 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ /src/NodeDev.Blazor.MAUI/bin/Debug/net9.0-windows10.0.19041.0/win10-x64 /src/NodeDev.Blazor.MAUI/obj /src/NodeDev.Blazor.MAUI/bin/Release/net9.0-windows10.0.19041.0/win-x64 +/src/NodeDev.Blazor.Server/AppOptions.json \ No newline at end of file diff --git a/src/Blazor.Diagrams b/src/Blazor.Diagrams new file mode 160000 index 0000000..ac4eef3 --- /dev/null +++ b/src/Blazor.Diagrams @@ -0,0 +1 @@ +Subproject commit ac4eef3d8e4ef0cc5a7caabfda33864d154890be diff --git a/src/Dis2Msil b/src/Dis2Msil new file mode 160000 index 0000000..780f509 --- /dev/null +++ b/src/Dis2Msil @@ -0,0 +1 @@ +Subproject commit 780f509a50a91dfb5bdd4c18e34c386bfde087cb diff --git a/src/NodeDev.Blazor.Server/Program.cs b/src/NodeDev.Blazor.Server/Program.cs index 6dc07cf..16895fc 100644 --- a/src/NodeDev.Blazor.Server/Program.cs +++ b/src/NodeDev.Blazor.Server/Program.cs @@ -14,9 +14,9 @@ // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { - app.UseExceptionHandler("/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); + app.UseExceptionHandler("/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); } app.UseAntiforgery(); diff --git a/src/NodeDev.Blazor/Components/OpenProjectDialog.razor b/src/NodeDev.Blazor/Components/OpenProjectDialog.razor new file mode 100644 index 0000000..05860dd --- /dev/null +++ b/src/NodeDev.Blazor/Components/OpenProjectDialog.razor @@ -0,0 +1,75 @@ +@using System.Text.Json +@using NodeDev.Blazor.Services +@using NodeDev.Core +@inject AppOptionsContainer AppOptionsContainer +@inject IDialogService DialogService +@inject ProjectService ProjectService +@inject ISnackbar Snackbar + +<MudDialog> + <DialogContent> + <MudStack> + <MudPaper Class="full-width"> + <MudList T="string" @bind-SelectedValue="ProjectName" ReadOnly="false"> + @foreach (var item in RecentProjects) + { + <MudListItem Text="@item"></MudListItem> + } + </MudList> + </MudPaper> + </MudStack> + </DialogContent> + <DialogActions> + <MudButton Color="Color.Primary" OnClick="LoadProject" Disabled="ProjectName is null">Open</MudButton> + <MudButton Color="Color.Primary" OnClick="Close">Cancel</MudButton> + </DialogActions> +</MudDialog> + +@code { + [CascadingParameter] + private MudDialogInstance MudDialog { get; set; } = null!; + + private string? ProjectName { get; set; } + private List<string> RecentProjects { get; set; } = new List<string>(); + + private void Close() => MudDialog.Close(DialogResult.Ok(true)); + + protected override void OnInitialized() + { + base.OnInitialized(); + RecentProjects = ListRecentProjects(); + } + + private List<string> ListRecentProjects() + { + if (AppOptionsContainer.AppOptions.ProjectsDirectory is null) + { + return []; + } + if (!Directory.Exists(AppOptionsContainer.AppOptions.ProjectsDirectory)) + { + return []; + } + return Directory.EnumerateFiles(AppOptionsContainer.AppOptions.ProjectsDirectory, "*.ndproj").Select(Path.GetFileNameWithoutExtension).ToList()!; + } + + private async Task LoadProject() + { + if (string.IsNullOrWhiteSpace(ProjectName)) + { + return; + } + try + { + var projectPath = Path.Combine(AppOptionsContainer.AppOptions.ProjectsDirectory!, $"{ProjectName}.ndproj"); + await ProjectService.LoadProjectFromFileAsync(projectPath); + } + catch (Exception ex) + { + Snackbar.Configuration.VisibleStateDuration = 10000; + Snackbar.Configuration.PositionClass = Defaults.Classes.Position.TopCenter; + Snackbar.Add(ex.Message, Severity.Error); + } + MudDialog.Close(DialogResult.Ok(true)); + } +} diff --git a/src/NodeDev.Blazor/Components/OptionsDialog.razor b/src/NodeDev.Blazor/Components/OptionsDialog.razor new file mode 100644 index 0000000..66314c8 --- /dev/null +++ b/src/NodeDev.Blazor/Components/OptionsDialog.razor @@ -0,0 +1,38 @@ +@using NodeDev.Blazor.Services +@inject IDialogService DialogService +@inject Services.AppOptionsContainer AppOptionsContainer + +<MudDialog> + <TitleContent> + Options + </TitleContent> + <DialogContent> + <MudTextField @bind-Value="AppOptions.ProjectsDirectory" Label="Projects Directory" Variant="Variant.Text" AutoGrow data-test-id="optionsProjectDirectory"></MudTextField> + </DialogContent> + <DialogActions> + <MudButton Color="Color.Primary" OnClick="Close" data-test-id="optionsCancel">Cancel</MudButton> + <MudButton Color="Color.Primary" OnClick="Accept" data-test-id="optionsAccept">Ok</MudButton> + </DialogActions> +</MudDialog> + +@code { + [CascadingParameter] + private MudDialogInstance MudDialog { get; set; } = null!; + + private AppOptions AppOptions { get; set; } = null!; + + protected override void OnInitialized() + { + base.OnInitialized(); + AppOptions = AppOptionsContainer.AppOptions with { }; + } + + private void Accept() + { + AppOptionsContainer.AppOptions = AppOptions; + MudDialog.Close(); + } + + private void Close() => MudDialog.Close(); + +} \ No newline at end of file diff --git a/src/NodeDev.Blazor/Components/ProjectToolbar.razor b/src/NodeDev.Blazor/Components/ProjectToolbar.razor new file mode 100644 index 0000000..76b5c4d --- /dev/null +++ b/src/NodeDev.Blazor/Components/ProjectToolbar.razor @@ -0,0 +1,88 @@ +@using Microsoft.AspNetCore.Components.Forms +@using NodeDev.Blazor.Services +@using NodeDev.Core +@inject ProjectService ProjectService +@inject ISnackbar Snackbar +@inject AppOptionsContainer AppOptionsContainer +@inject IDialogService DialogService + + +<MudButton OnClick="Open" Class="ml-3" Disabled=@Project.IsLiveDebuggingEnabled data-test-id="openProject">Open</MudButton> +<MudButton OnClick="NewProject" Class="ml-3" Disabled=@Project.IsLiveDebuggingEnabled data-test-id="newProject">New Project</MudButton> +<MudButton OnClick="Save" Class="ml-3" data-test-id="save">Save</MudButton> +<MudButton OnClick="SaveAs" Class="ml-3" data-test-id="saveAs">Save As</MudButton> +<MudButton OnClick="Add" Class="ml-3">Add node</MudButton> +<MudButton OnClick="Run" Class="ml-3">Run</MudButton> +<MudButton OnClick="SwitchLiveDebugging">@(Project.IsLiveDebuggingEnabled ? "Stop Live Debugging" : "Start Live Debugging")</MudButton> +<MudSpacer /> +<MudButton OnClick="OpenOptionsDialogAsync" Class="ml-3" data-test-id="options">Options</MudButton> +<MudIconButton Icon="@Icons.Material.Filled.MoreVert" Color="Color.Inherit" Edge="Edge.End" /> + + +@code { + + private Project Project => ProjectService.Project; + + private DialogOptions DialogOptions => new DialogOptions { MaxWidth = MaxWidth.Medium, FullWidth = true }; + + private Task Open() + { + return DialogService.ShowAsync<OpenProjectDialog>("Open Project", DialogOptions); + } + + private Task Save() + { + if (string.IsNullOrWhiteSpace(Project.Settings.ProjectName)) + { + return SaveAs(); + } + + try + { + ProjectService.SaveProjectToFile(); + Snackbar.Add("Project saved", Severity.Success); + } + catch (Exception ex) + { + Snackbar.Add(ex.Message, Severity.Error); + } + + return Task.CompletedTask; + } + + private Task SaveAs() + { + return DialogService.ShowAsync<SaveAsProjectDialog>("Save As Project", DialogOptions); + } + + private void NewProject() + { + ProjectService.ChangeProject(Core.Project.CreateNewDefaultProject()); + } + + private void Add() + { + //GraphCanvas?.ShowAddNode(); + } + + public void Run() + { + new Thread(() => + { + Project.Run(Project.IsLiveDebuggingEnabled ? Core.BuildOptions.Debug : Core.BuildOptions.Release); + }).Start(); + } + + private void SwitchLiveDebugging() + { + if (Project.IsLiveDebuggingEnabled) + Project.StopLiveDebugging(); + else + Project.StartLiveDebugging(); + } + + private Task OpenOptionsDialogAsync() + { + return DialogService.ShowAsync<OptionsDialog>("Options", DialogOptions); + } +} diff --git a/src/NodeDev.Blazor/Components/SaveAsProjectDialog.razor b/src/NodeDev.Blazor/Components/SaveAsProjectDialog.razor new file mode 100644 index 0000000..70de910 --- /dev/null +++ b/src/NodeDev.Blazor/Components/SaveAsProjectDialog.razor @@ -0,0 +1,48 @@ +@using System.Text.Json +@using NodeDev.Blazor.Services +@using NodeDev.Core +@inject AppOptionsContainer AppOptionsContainer +@inject IDialogService DialogService +@inject ProjectService ProjectService +@inject ISnackbar Snackbar + +<MudDialog> + <DialogContent> + <MudTextField @bind-Value="ProjectName" Label="File Name" Variant="Variant.Text" data-test-id="saveAsProjectName"></MudTextField> + </DialogContent> + <DialogActions> + <MudButton Color="Color.Primary" OnClick="SaveProject" Disabled="ProjectName is null" data-test-id="saveAsSave">Save</MudButton> + <MudButton Color="Color.Primary" OnClick="Close" data-test-id="saveAsCancel">Cancel</MudButton> + </DialogActions> +</MudDialog> + +@code { + [CascadingParameter] + private MudDialogInstance MudDialog { get; set; } = null!; + + public string? ProjectName { get; set; } + + private void Close() => MudDialog.Close(DialogResult.Ok(true)); + + protected override void OnInitialized() + { + base.OnInitialized(); + ProjectName = ProjectService.Project.Settings.ProjectName; + } + + private void SaveProject() + { + try + { + ProjectService.Project.Settings.ProjectName = ProjectName!; + ProjectService.SaveProjectToFile(); + Snackbar.Add("Project saved", Severity.Success); + MudDialog.Close(DialogResult.Ok(ProjectName)); + } + catch (Exception ex) + { + Snackbar.Add(ex.Message, Severity.Error); + } + } + +} diff --git a/src/NodeDev.Blazor/Index.razor b/src/NodeDev.Blazor/Index.razor index 0f1b210..038194e 100644 --- a/src/NodeDev.Blazor/Index.razor +++ b/src/NodeDev.Blazor/Index.razor @@ -1,6 +1,9 @@ -@inject Services.DebuggedPathService DebuggedPathService +@using NodeDev.Blazor.Services +@inject Services.DebuggedPathService DebuggedPathService @inject NavigationManager NavigationManager @inject ISnackbar Snackbar +@inject ProjectService ProjectService +@implements IDisposable <MudThemeProvider /> <MudPopoverProvider /> @@ -9,14 +12,7 @@ <MudLayout Style="height: 100vh; width: 100%; overflow: hidden"> <MudAppBar Elevation="1" data-test-id="appBar"> - <MudButton OnClick="NewProject" Class="ml-3" data-test-id="newProject">New Project</MudButton> - <MudButton OnClick="Save" Class="ml-3" data-test-id="Save">Save</MudButton> - <MudButton OnClick="Build" Class="ml-3" data-test-id="Build">Build</MudButton> - <MudButton OnClick="Add" Class="ml-3">Add node</MudButton> - <MudButton OnClick="Run" Class="ml-3">Run</MudButton> - <MudButton OnClick="SwitchLiveDebugging">@(Project.IsLiveDebuggingEnabled ? "Stop Live Debugging" : "Start Live Debugging")</MudButton> - <MudSpacer /> - <MudIconButton Icon="@Icons.Material.Filled.MoreVert" Color="Color.Inherit" Edge="Edge.End" /> + <ProjectToolbar /> </MudAppBar> <MudMainContent Style="width: 100%; height: 100%; overflow-y: hidden"> @@ -24,7 +20,7 @@ <StartContent> <MudTabs Elevation="2" Rounded="true" ApplyEffectsToContainer="true" PanelClass="pa-2 wh100" KeepPanelsAlive="true" Class="wh100" data-test-id="ProjectExplorerSection"> <MudTabPanel Text="Project"> - <ProjectExplorer Project="Project" @bind-SelectedClass="SelectedClass"></ProjectExplorer> + <ProjectExplorer Project="ProjectService.Project" @bind-SelectedClass="SelectedClass"></ProjectExplorer> </MudTabPanel> <MudTabPanel Text="Class"> @if (SelectedClass != null) @@ -33,7 +29,6 @@ } </MudTabPanel> </MudTabs> - </StartContent> <EndContent> @@ -61,7 +56,7 @@ </MudTabs> </CascadingValue> - <DebuggerConsolePanel Project="Project" /> + <DebuggerConsolePanel Project="ProjectService.Project" /> </MudStack> @@ -85,8 +80,6 @@ @code { - private Core.Project Project { get; set; } = null!; - private Core.Class.NodeClass? SelectedClass { get; set; } private List<Core.Class.NodeClassMethod> OpenedMethods { get; } = new(); @@ -105,14 +98,20 @@ protected override void OnInitialized() { - if (File.Exists("project.json")) - Project = Core.Project.Deserialize(File.ReadAllText("project.json")); - else - Project = Core.Project.CreateNewDefaultProject(); + ProjectService.ProjectChanged += OnProjectChanged; - DebuggedPathService.ChangeProject(Project); + DebuggedPathService.ChangeProject(ProjectService.Project); } + private void OnProjectChanged() + { + _ = InvokeAsync(() => + { + DebuggedPathService.ChangeProject(ProjectService.Project); + NavigationManager.Refresh(true); + }); + } + private void SwitchSourceViewerGraph() { if (SourceViewerGraphPercentage <= SourceViewer_OpenedGraphPercentage) @@ -144,7 +143,7 @@ if (method == null) { // Find the main method - var program = Project.Classes.FirstOrDefault(x => x.Name == "Program"); + var program = ProjectService.Project.Classes.FirstOrDefault(x => x.Name == "Program"); // Find the main method in the program class method = program?.Methods.FirstOrDefault(x => x.Name == "Main"); @@ -164,46 +163,8 @@ StateHasChanged(); } - private void Save() - { - var content = Project.Serialize(); - File.WriteAllText("project.json", content); - - Snackbar.Add("Project saved", Severity.Success); - } - - private void NewProject() - { - if (File.Exists("project.json")) - File.Move("project.json", "project_backup.json", true); - - - NavigationManager.Refresh(true); - } - - private void Add() - { - //GraphCanvas?.ShowAddNode(); - } - - public void Build() - { - Project.Build(Project.IsLiveDebuggingEnabled ? Core.BuildOptions.Debug : Core.BuildOptions.Release); - } - - public void Run() - { - new Thread(() => - { - Project.Run(Project.IsLiveDebuggingEnabled ? Core.BuildOptions.Debug : Core.BuildOptions.Release); - }).Start(); - } - - private void SwitchLiveDebugging() + public void Dispose() { - if (Project.IsLiveDebuggingEnabled) - Project.StopLiveDebugging(); - else - Project.StartLiveDebugging(); + ProjectService.ProjectChanged -= OnProjectChanged; } } \ No newline at end of file diff --git a/src/NodeDev.Blazor/Services/AppOptions.cs b/src/NodeDev.Blazor/Services/AppOptions.cs new file mode 100644 index 0000000..046ba50 --- /dev/null +++ b/src/NodeDev.Blazor/Services/AppOptions.cs @@ -0,0 +1,6 @@ +namespace NodeDev.Blazor.Services; + +public record class AppOptions +{ + public string ProjectsDirectory { get; set; } = string.Empty; +} diff --git a/src/NodeDev.Blazor/Services/AppOptionsContainer.cs b/src/NodeDev.Blazor/Services/AppOptionsContainer.cs new file mode 100644 index 0000000..426b514 --- /dev/null +++ b/src/NodeDev.Blazor/Services/AppOptionsContainer.cs @@ -0,0 +1,44 @@ +using System.Text.Json; + +namespace NodeDev.Blazor.Services; + +public class AppOptionsContainer +{ + private readonly string OptionsFileName; + + private AppOptions appOptions = new AppOptions(); + public AppOptions AppOptions + { + get => appOptions; + set + { + appOptions = value; + SaveOptions(); + } + } + + public AppOptionsContainer(string optionFileName) + { + OptionsFileName = optionFileName; + if (!string.IsNullOrWhiteSpace(OptionsFileName)) + { + LoadOptions(); + } + } + + public void SaveOptions() + { + File.WriteAllText(OptionsFileName, JsonSerializer.Serialize(appOptions, new JsonSerializerOptions() + { + WriteIndented = true + })); + } + + public void LoadOptions() + { + if (File.Exists(OptionsFileName)) + { + appOptions = JsonSerializer.Deserialize<AppOptions>(File.ReadAllText(OptionsFileName)) ?? new AppOptions(); + } + } +} diff --git a/src/NodeDev.Blazor/Services/ProjectService.cs b/src/NodeDev.Blazor/Services/ProjectService.cs new file mode 100644 index 0000000..4bfa531 --- /dev/null +++ b/src/NodeDev.Blazor/Services/ProjectService.cs @@ -0,0 +1,58 @@ +using NodeDev.Core; + +namespace NodeDev.Blazor.Services +{ + /// <summary> + /// Service used to keep a singleton of the project throughout the application. + /// </summary> + public class ProjectService + { + public Project Project { get; private set; } + + private readonly AppOptionsContainer AppOptionsContainer; + + public delegate void ProjectChangedHandler(); + /// <summary> + /// Event used to notify subscribers when the current project has changed. + /// </summary> + public event ProjectChangedHandler? ProjectChanged; + + /// <summary> + /// Instanciates a default project as the current project. + /// </summary> + public ProjectService(AppOptionsContainer appOptionsContainer) + { + Project = Project.CreateNewDefaultProject(); + AppOptionsContainer = appOptionsContainer; + } + + /// <summary> + /// Changes the current project and notifies all subscribers of <see cref="ProjectChanged" />. + /// </summary> + /// <param name="project"></param> + public void ChangeProject(Project project) + { + Project = project; + ProjectChanged?.Invoke(); + } + + public async Task LoadProjectFromFileAsync(string file) + { + ArgumentNullException.ThrowIfNullOrWhiteSpace(file); + + var json = await File.ReadAllTextAsync(file); + var project = Project.Deserialize(json); + project.Settings.ProjectName = Path.GetFileNameWithoutExtension(file); + ChangeProject(project); + } + + public void SaveProjectToFile() + { + ArgumentNullException.ThrowIfNullOrWhiteSpace(Project.Settings.ProjectName); + var projectPath = Path.Combine(AppOptionsContainer.AppOptions.ProjectsDirectory!, $"{Project.Settings.ProjectName}.ndproj"); + string content = Project.Serialize(); + File.WriteAllText(projectPath, content); + + } + } +} diff --git a/src/NodeDev.Blazor/Services/ServicesExtension.cs b/src/NodeDev.Blazor/Services/ServicesExtension.cs index 407d087..e08d725 100644 --- a/src/NodeDev.Blazor/Services/ServicesExtension.cs +++ b/src/NodeDev.Blazor/Services/ServicesExtension.cs @@ -10,7 +10,9 @@ public static IServiceCollection AddNodeDev(this IServiceCollection services) { services .AddMudServices() - .AddScoped<DebuggedPathService>(); + .AddScoped<DebuggedPathService>() + .AddSingleton<ProjectService>() + .AddSingleton(new AppOptionsContainer("AppOptions.json")); return services; } diff --git a/src/NodeDev.Core/ProjectSettings.cs b/src/NodeDev.Core/ProjectSettings.cs index a5e25a0..ce2c687 100644 --- a/src/NodeDev.Core/ProjectSettings.cs +++ b/src/NodeDev.Core/ProjectSettings.cs @@ -2,5 +2,6 @@ public record class ProjectSettings() { + public string ProjectName { get; set; } = string.Empty; public static ProjectSettings Default { get; } = new(); } diff --git a/src/NodeDev.EndToEndTests/Features/SaveProject.feature b/src/NodeDev.EndToEndTests/Features/SaveProject.feature index be229fc..110d219 100644 --- a/src/NodeDev.EndToEndTests/Features/SaveProject.feature +++ b/src/NodeDev.EndToEndTests/Features/SaveProject.feature @@ -4,5 +4,5 @@ Scenario: Save empty project Given I load the default project Then The 'Main' method in the 'Program' class should exist - Given I save the current project + Given I save the current project as 'EmptyProject' Then Snackbar should contain 'Project saved' \ No newline at end of file diff --git a/src/NodeDev.EndToEndTests/Features/SaveProject.feature.cs b/src/NodeDev.EndToEndTests/Features/SaveProject.feature.cs index 916f927..b2aa478 100644 --- a/src/NodeDev.EndToEndTests/Features/SaveProject.feature.cs +++ b/src/NodeDev.EndToEndTests/Features/SaveProject.feature.cs @@ -105,7 +105,7 @@ public async System.Threading.Tasks.Task SaveEmptyProject() await testRunner.ThenAsync("The \'Main\' method in the \'Program\' class should exist", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); #line hidden #line 7 - await testRunner.GivenAsync("I save the current project", ((string)(null)), ((global::Reqnroll.Table)(null)), "Given "); + await testRunner.GivenAsync("I save the current project as \'EmptyProject\'", ((string)(null)), ((global::Reqnroll.Table)(null)), "Given "); #line hidden #line 8 await testRunner.ThenAsync("Snackbar should contain \'Project saved\'", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); diff --git a/src/NodeDev.EndToEndTests/Pages/HomePage.cs b/src/NodeDev.EndToEndTests/Pages/HomePage.cs index a5f0801..bdb248c 100644 --- a/src/NodeDev.EndToEndTests/Pages/HomePage.cs +++ b/src/NodeDev.EndToEndTests/Pages/HomePage.cs @@ -18,6 +18,10 @@ public HomePage(Hooks.Hooks hooks) private ILocator SearchProjectExplorerTabsHeader => _user.Locator("[data-test-id='ProjectExplorerSection'] .mud-tabs-tabbar"); private ILocator SearchClassExplorer => _user.Locator("[data-test-id='classExplorer']"); private ILocator SearchSnackBarContainer => _user.Locator("#mud-snackbar-container"); + private ILocator SearchOptionsButton => SearchAppBar.Locator("[data-test-id='options']"); + private ILocator SearchSaveButton => SearchAppBar.Locator("[data-test-id='save']"); + private ILocator SearchSaveAsButton => SearchAppBar.Locator("[data-test-id='saveAs']"); + public async Task CreateNewProject() { @@ -69,11 +73,48 @@ public async Task HasMethodByName(string name) public async Task SaveProject() { - var saveBtn = SearchAppBar.Locator("[data-test-id='Save']"); + await SearchSaveButton.WaitForVisible(); + await SearchSaveButton.ClickAsync(); + } - await saveBtn.WaitForVisible(); + public async Task OpenOptionsDialog() + { + await SearchOptionsButton.WaitForVisible(); + await SearchOptionsButton.ClickAsync(); + } - await saveBtn.ClickAsync(); + public async Task SetProjectsDirectory(string directory) + { + var projectsDirectoryInput = _user.Locator("[data-test-id='optionsProjectDirectory']"); + await projectsDirectoryInput.WaitForVisible(); + await projectsDirectoryInput.FillAsync(directory); + } + + public async Task AcceptOptions() + { + var acceptButton = _user.Locator("[data-test-id='optionsAccept']"); + await acceptButton.WaitForVisible(); + await acceptButton.ClickAsync(); + } + + public async Task OpenSaveAsDialog() + { + await SearchSaveAsButton.WaitForVisible(); + await SearchSaveAsButton.ClickAsync(); + } + + public async Task SetProjectNameAs(string projectName) + { + var projectNameInput = _user.Locator("[data-test-id='saveAsProjectName']"); + await projectNameInput.WaitForVisible(); + await projectNameInput.FillAsync(projectName); + } + + public async Task AcceptSaveAs() + { + var saveButton = _user.Locator("[data-test-id='saveAsSave']"); + await saveButton.WaitForVisible(); + await saveButton.ClickAsync(); } public async Task SnackBarHasByText(string text) diff --git a/src/NodeDev.EndToEndTests/StepDefinitions/MainPageStepDefinitions.cs b/src/NodeDev.EndToEndTests/StepDefinitions/MainPageStepDefinitions.cs index 5258535..9099f7b 100644 --- a/src/NodeDev.EndToEndTests/StepDefinitions/MainPageStepDefinitions.cs +++ b/src/NodeDev.EndToEndTests/StepDefinitions/MainPageStepDefinitions.cs @@ -46,4 +46,21 @@ public async Task ThenSnackbarShouldContain(string text) { await HomePage.SnackBarHasByText(text); } + + [Given("I set the projects directory to {string}")] + public async Task GivenISetTheProjectsDirectoryTo(string directory) + { + await HomePage.OpenOptionsDialog(); + await HomePage.SetProjectsDirectory(directory); + await HomePage.AcceptOptions(); + } + + [Given("I save the current project as {string}")] + public async Task GivenISaveTheCurrentProjectAs(string projectName) + { + await HomePage.OpenSaveAsDialog(); + await HomePage.SetProjectNameAs(projectName); + await HomePage.AcceptSaveAs(); + } + } diff --git a/src/NodeDev.Tests/ProjectServiceTest.cs b/src/NodeDev.Tests/ProjectServiceTest.cs new file mode 100644 index 0000000..a9b46f0 --- /dev/null +++ b/src/NodeDev.Tests/ProjectServiceTest.cs @@ -0,0 +1,32 @@ +using NodeDev.Blazor.Services; +using NodeDev.Core; + +namespace NodeDev.Tests; + +public class ProjectServiceTest +{ + [Fact] + public void TestsChangeProject() + { + var project = new Project(Guid.NewGuid()); + var projectService = new ProjectService(new AppOptionsContainer("")); + + projectService.ChangeProject(project); + + Assert.Equal(project, projectService.Project); + } + + [Fact] + public void TestsProjectChangedEvent() + { + var project = new Project(Guid.NewGuid()); + var projectService = new ProjectService(new AppOptionsContainer("")); + bool isEventTriggered = false; + + projectService.ProjectChanged += () => isEventTriggered = true; + projectService.ChangeProject(project); + + Assert.True(isEventTriggered); + } +} +