diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a89ff0b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,51 @@ +name: CI + +on: + push: + branches: [main, dev] + pull_request: + branches: [main, dev] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-and-test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: "9.0.x" + + - name: Cache NuGet packages + uses: actions/cache@v4 + with: + path: ~/.nuget/packages + key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }} + restore-keys: | + ${{ runner.os }}-nuget- + + - name: Restore + run: dotnet restore BlazorBlog.sln + + - name: Build (Release) + run: dotnet build BlazorBlog.sln -c Release --no-restore + + - name: Test + run: dotnet test BlazorBlog.sln -c Release --no-build --logger "trx;LogFileName=test_results.trx" --results-directory "TestResults" + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results + path: TestResults/**/*.trx diff --git a/BlazorBlog/BlazorBlog.csproj b/BlazorBlog/BlazorBlog.csproj index 7d11d4b..ee96350 100644 --- a/BlazorBlog/BlazorBlog.csproj +++ b/BlazorBlog/BlazorBlog.csproj @@ -12,7 +12,6 @@ all - diff --git a/BlazorBlog/Components/Pages/Admin/ManageCategories.razor.cs b/BlazorBlog/Components/Pages/Admin/ManageCategories.razor.cs index 7608d82..631077c 100644 --- a/BlazorBlog/Components/Pages/Admin/ManageCategories.razor.cs +++ b/BlazorBlog/Components/Pages/Admin/ManageCategories.razor.cs @@ -120,7 +120,7 @@ private async Task SaveCategoryAsync() } catch (InvalidOperationException ex) { - var nameField = new FieldIdentifier(_operatingCategory, nameof(Category.Name)); + var nameField = new FieldIdentifier(_operatingCategory!, nameof(Category.Name)); _messageStore.Add(nameField, ex.Message); _editContext.NotifyValidationStateChanged(); diff --git a/BlazorBlog/Components/Pages/Admin/SaveBlogPost.razor.cs b/BlazorBlog/Components/Pages/Admin/SaveBlogPost.razor.cs index fa295c4..15a7c19 100644 --- a/BlazorBlog/Components/Pages/Admin/SaveBlogPost.razor.cs +++ b/BlazorBlog/Components/Pages/Admin/SaveBlogPost.razor.cs @@ -8,38 +8,40 @@ public partial class SaveBlogPost { private const int MaxFileLenght = 10 * 1024 * 1024; - private bool _isLoading; - private string? _loadingText; + private bool _isLoading = false; + private string? _loadingText = null; private BlogPostVm _blogPostVm = new BlogPostVm(); private EditContext _editContext = default!; private ValidationMessageStore? _messageStore; private BlazoredTextEditor? _quillHtml; private Category[] _categories = []; private string? _content = default!; - private string? _errorMessage = null; + private string? _errorMessage = null; private IBrowserFile? _fileToUpload; private string? _imageUrl; private string PageTitle => Id is > 0 ? "Edit Blog Post" : "New Blog Post"; - private bool _isSaving; - public bool IsSaving => _isSaving; - public string TagsCsv { get; set; } = string.Empty; + private bool _isSaving; + public bool IsSaving => _isSaving; + public string TagsCsv { get; set; } = string.Empty; private readonly CancellationTokenSource _cts = new(); - [Inject] AuthenticationStateProvider AuthenticationStateProvider { get; set; } = default!; - [Inject] IWebHostEnvironment WebHostEnvironment { get; set; } = default!; - [Inject] IBlogPostAdminService BlogPostService { get; set; } = default!; - [Inject] ICategoryService CategoryService { get; set; } = default!; - [Inject] NavigationManager NavigationManager { get; set; } = default!; - [Inject] IToastService ToastService { get; set; } = default!; - [Inject] IHtmlSanitizer HtmlSanitizer { get; set; } = default!; - [Inject] IValidator Validator { get; set; } = default!; - [Inject] ITagService TagService { get; set; } = default!; + [Inject] AuthenticationStateProvider AuthenticationStateProvider { get; set; } = default!; + [Inject] IWebHostEnvironment WebHostEnvironment { get; set; } = default!; + [Inject] IBlogPostAdminService BlogPostService { get; set; } = default!; + [Inject] ICategoryService CategoryService { get; set; } = default!; + [Inject] NavigationManager NavigationManager { get; set; } = default!; + [Inject] IToastService ToastService { get; set; } = default!; + [Inject] IHtmlSanitizer HtmlSanitizer { get; set; } = default!; + [Inject] IValidator Validator { get; set; } = default!; + [Inject] ITagService TagService { get; set; } = default!; [Parameter] public int? Id { get; set; } protected override async Task OnInitializedAsync() { + _isLoading = true; + _loadingText = "Loading blog post..."; _editContext = new EditContext(_blogPostVm); _messageStore = new ValidationMessageStore(_editContext); @@ -67,6 +69,8 @@ protected override async Task OnInitializedAsync() } catch { /* optional */ } } + _isLoading = false; + _loadingText = null; } private async Task PreviewImageAsync(IBrowserFile file) @@ -130,6 +134,8 @@ private async Task SubmitAsync() } _isSaving = true; + _isLoading = true; + _loadingText = "Saving blog post..."; StateHasChanged(); await SaveBlogPostAsync(); } @@ -190,6 +196,11 @@ private async Task SaveBlogPostAsync() ToastService.ShowToast(ToastLevel.Error, "Something went wrong while saving the blog post.", heading: "Error"); _isSaving = false; } + finally + { + _isLoading = false; + _loadingText = null; + } } private async Task SaveFileAsync(IBrowserFile file)