Skip to content

test(bunit): Sprint 19 markdown editor lifecycle and interop validation - #333

Merged
mpaulosky merged 2 commits into
devfrom
squad/327-test-interop-lifecycle-validation
May 13, 2026
Merged

test(bunit): Sprint 19 markdown editor lifecycle and interop validation#333
mpaulosky merged 2 commits into
devfrom
squad/327-test-interop-lifecycle-validation

Conversation

@mpaulosky

Copy link
Copy Markdown
Owner

Working as Gimli (Testing/Quality Engineer)

Closes #327

Adds MarkdownEditorLifecycleTests.cs with 5 targeted bUnit tests closing four coverage gaps from Sprint 19:

  • CreateEditorInitializesWithEmptyContent: Create lifecycle, editor starts empty
  • EditEditorInitializesWithExistingPostContent: Edit lifecycle, editor pre-populated from DB
  • CreatePageDisposesWithoutException: navigation safety for Create
  • EditPageDisposesWithoutException: navigation safety for Edit
  • TextEditorRendersStandaloneWithLooseJsInteropWithoutThrow: Loose mode absorbs EasyMDE JS calls

Test count: 94 -> 99 bUnit tests, all passing. Full suite: 315 tests, 0 failures.

All tests use JSRuntimeMode.Loose (matching EditAclTests.cs pattern). No feature code or existing tests modified. Disposal tests use DisposeComponentsAsync() per bUnit 2.7.2 API.

Copilot AI review requested due to automatic review settings May 13, 2026 07:13
@github-actions github-actions Bot added the squad Squad triage inbox — Lead will assign to a member label May 13, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🏗️ PR Added to Squad Triage Queue

This PR has been labeled with squad and added to the triage queue.

Next steps:

  • The squad Lead will review and assign to an appropriate team member
  • A squad:member label will be added after triage

If you know which squad member should handle this, you can add the appropriate squad:member label yourself.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new markdown editor-based TextEditor component (with image upload + alignment tooling), wires it into the BlogPost Create/Edit pages, and adds/updates bUnit tests to validate lifecycle/interop behavior.

Changes:

  • Replace Create/Edit post <InputTextArea> content fields with a new TextEditor (PSC.Blazor.Components.MarkdownEditor-based) and add supporting JS/CSS includes.
  • Add local-disk file storage abstraction/implementation and register it in DI for image uploads.
  • Add new bUnit lifecycle tests and update existing bUnit suites to run with loose JS interop and accommodate the new editor.

Reviewed changes

Copilot reviewed 18 out of 19 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
tests/Web.Tests.Bunit/GlobalUsings.cs Adds global using for IFileStorage to support updated/new tests.
tests/Web.Tests.Bunit/Features/MarkdownEditorLifecycleTests.cs New bUnit tests covering create/edit editor lifecycle, disposal, and loose JS interop behavior.
tests/Web.Tests.Bunit/Features/EditAclTests.cs Sets bUnit JSInterop to Loose and registers IFileStorage for editor rendering.
tests/Web.Tests.Bunit/Components/RazorSmokeTests.cs Updates smoke tests to assert TextEditor usage and adjusts create/edit flows for editor binding.
src/Web/wwwroot/js/text-editor-helpers.js Adds getSelectedText JS helper used by TextEditor custom toolbar behavior.
src/Web/wwwroot/js/markdown-editor-init.js Adds an interop wrapper script (currently not referenced by the app).
src/Web/Web.csproj Adds markdown-related package references.
src/Web/Program.cs Registers file storage via AddFileStorage().
src/Web/Infrastructure/FileStorage/LocalDiskFileStorage.cs Implements local disk storage under wwwroot/uploads with size/extension checks.
src/Web/Infrastructure/FileStorage/IFileStorage.cs Introduces storage abstraction used by the editor for uploads.
src/Web/Infrastructure/FileStorage/FileStorageModels.cs Adds FileData/FileMetaData models for storage operations.
src/Web/Infrastructure/FileStorage/FileStorageExtensions.cs Adds DI registration extension method.
src/Web/GlobalUsings.cs Adds global using for file storage types.
src/Web/Features/BlogPosts/Edit/Edit.razor Replaces textarea with TextEditor and updates label to indicate Markdown.
src/Web/Features/BlogPosts/Create/Create.razor Replaces textarea with TextEditor and updates label to indicate Markdown.
src/Web/Components/Shared/TextEditor.razor New markdown editor wrapper component with image upload and custom button handling.
src/Web/Components/App.razor Adds Font Awesome CDN + PSC MarkdownEditor CSS/JS + helper script includes.
src/AppHost/MongoDbResourceBuilderExtensions.cs Minor formatting/bracing fix in seed document block.
Directory.Packages.props Centralizes new markdown-related package versions.

Comment on lines +80 to +82
// Persist the file and build a retrieval URL
var fileName = await FileStorage.AddFileAsync(new FileData(stream, new FileMetaData(file.Name, string.Empty, file.LastModified)));
file.UploadUrl = new Uri(new Uri(NavigationManager.BaseUri), $"/api/files/{fileName}").ToString();
set
{
_content = value;
ContentChanged.InvokeAsync(value);
Comment on lines +72 to +83
public async Task<FileEntry> HandleImageUpload(MarkdownEditor editor, FileEntry file)
{
Console.WriteLine($"Image: {file.Name} Size: {file.Size}");

// Convert the base64 string to a byte array and load it into a memory stream
var bytes = Convert.FromBase64String(file.ContentBase64);
var stream = new MemoryStream(bytes);

// Persist the file and build a retrieval URL
var fileName = await FileStorage.AddFileAsync(new FileData(stream, new FileMetaData(file.Name, string.Empty, file.LastModified)));
file.UploadUrl = new Uri(new Uri(NavigationManager.BaseUri), $"/api/files/{fileName}").ToString();
return file;
Comment on lines +86 to +101
public async Task OnCustomButtonClicked(MarkdownButtonEventArgs args)
{
// Get the selected text using JavaScript
var selectedText = await JsRuntime.InvokeAsync<string>("getSelectedText", ".EasyMDEContainer textarea");

if (!string.IsNullOrEmpty(selectedText))
{
// Wrap the selected text with the chosen alignment
var wrappedText = $"<div style='text-align:{args.Value}'>{selectedText}</div>";

// Replace the selected text in the editor
MyContent = MyContent.Replace(selectedText, wrappedText);

// Update the editor
await _textEditor.SetValueAsync(MyContent);
}
Comment on lines +43 to +46
if (file.Content.Length > MaxFileSizeBytes)
{
throw new InvalidOperationException("File exceeds the maximum allowed size of 10 MB.");
}
Comment on lines +48 to +53
var extension = Path.GetExtension(file.Metadata.FileName).ToUpperInvariant();
if (string.IsNullOrEmpty(extension) || !AllowedExtensions.Contains(extension))
{
throw new InvalidOperationException(
$"File type '{extension}' is not allowed. Only images are permitted.");
}
Comment thread src/Web/Web.csproj
Comment on lines 14 to 20
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" />
<PackageReference Include="Markdig" />
<PackageReference Include="MediatR" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
<PackageReference Include="MongoDB.EntityFrameworkCore" />
<PackageReference Include="PSC.Blazor.Components.MarkdownEditor" />
<PackageReference Include="Snappier" />
Comment on lines +1 to +11
// Custom initialization wrapper for PSC.Blazor.Components.MarkdownEditor
// This ensures proper loading and initialization of the markdown editor

window.markdownEditorInterop = {
editors: {},

initialize: function (dotNetObjectRef, elementRef, elementId, options) {
try {
// Wait for EasyMDE to be available
if (typeof EasyMDE === 'undefined') {
console.error('EasyMDE is not loaded');
<ResourcePreloader />
<link rel="stylesheet" href="css/tailwind.css" />
<link rel="stylesheet" href="@Assets["Web.styles.css"]" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" integrity="sha512-Evv84Mr4kqVGRNSgIGL/F/aIDqQb7xQ2vcrdIwxfjThSH8CSR7PBEakCr51Ck+w+/U6swU2Im1vVX0SVk9ABhg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
Comment thread src/Web/Web.csproj
Comment on lines 12 to 20
<PackageReference Include="Auth0.ManagementApi" />
<PackageReference Include="FluentValidation.AspNetCore" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" />
<PackageReference Include="Markdig" />
<PackageReference Include="MediatR" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
<PackageReference Include="MongoDB.EntityFrameworkCore" />
<PackageReference Include="PSC.Blazor.Components.MarkdownEditor" />
<PackageReference Include="Snappier" />
@mpaulosky
mpaulosky force-pushed the squad/327-test-interop-lifecycle-validation branch from 290ead9 to f686b44 Compare May 13, 2026 16:54
@mpaulosky

Copy link
Copy Markdown
Owner Author

🤖 Aragorn — Rebase complete

Branch squad/327-test-interop-lifecycle-validation rebased onto squad/326 tip (3d1f50f) via git rebase --onto squad/326-ux-parity-hardening 0e6458b. Clean rebase — new test file MarkdownEditorLifecycleTests.cs applied with no conflicts. All pre-push gates passed ✅

@github-actions

github-actions Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

Test Results Summary

385 tests  +7   384 ✅ +7   22s ⏱️ +2s
  6 suites ±0     1 💤 ±0 
  6 files   ±0     0 ❌ ±0 

Results for commit 5f3023b. ± Comparison against base commit 6d9ffd0.

♻️ This comment has been updated with latest results.

@codecov

codecov Bot commented May 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.66%. Comparing base (2a75eb4) to head (036aac2).
⚠️ Report is 3 commits behind head on dev.

Additional details and impacted files
@@            Coverage Diff             @@
##              dev     #333      +/-   ##
==========================================
- Coverage   81.67%   81.66%   -0.02%     
==========================================
  Files          51       51              
  Lines        1370     1369       -1     
  Branches      166      166              
==========================================
- Hits         1119     1118       -1     
  Misses        184      184              
  Partials       67       67              
Files with missing lines Coverage Δ
src/Web/Features/BlogPosts/Create/Create.razor 79.41% <ø> (-0.59%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@mpaulosky
mpaulosky force-pushed the squad/327-test-interop-lifecycle-validation branch from f686b44 to 5f3023b Compare May 13, 2026 17:24
@mpaulosky

Copy link
Copy Markdown
Owner Author

🔁 Aragorn restack — rebased onto dev via stacked-chain (after #332). No conflicts. Force-pushed with --force-with-lease. Branch now at 5f3023b.

@mpaulosky
mpaulosky enabled auto-merge May 13, 2026 17:25
@mpaulosky
mpaulosky force-pushed the squad/327-test-interop-lifecycle-validation branch from 5f3023b to f96c477 Compare May 13, 2026 17:51
Boromir and others added 2 commits May 13, 2026 10:57
- Align Create.razor label from 'Markdown' to 'Content (Markdown)'
  matching Edit.razor's label pattern for visual parity
- Switch Create.razor TextEditor binding to @bind-Content for
  idiomatic Blazor two-way binding, consistent with Edit.razor
- Add bUnit parity tests: CreatePostShowsMarkdownContentLabel and
  EditPostShowsMarkdownContentLabel asserting label presence on both
  pages

No behavioral change — form submit, validation, and navigation paths
are unchanged. Editor is not rendered before data loads in Edit
(existing _isLoading guard remains in place).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…dation (#327)

Add MarkdownEditorLifecycleTests.cs with 5 targeted tests covering four
gaps not addressed by the existing smoke and ACL suites:

- CreateEditorInitializesWithEmptyContent: asserts TextEditor.Content == ""
  on initial render of the Create page
- EditEditorInitializesWithExistingPostContent: explicit lifecycle assertion
  that the Edit page pre-populates the editor with the post's stored content
- CreatePageDisposesWithoutException: disposes Create page without JS errors
- EditPageDisposesWithoutException: disposes Edit page without JS errors
- TextEditorRendersStandaloneWithLooseJsInteropWithoutThrow: documents that
  JSRuntimeMode.Loose absorbs EasyMDE init calls silently

All tests follow the JSRuntimeMode.Loose pattern established in
EditAclTests.cs. Result: 94 → 99 bUnit tests, all passing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mpaulosky
mpaulosky force-pushed the squad/327-test-interop-lifecycle-validation branch from f96c477 to 036aac2 Compare May 13, 2026 18:02
@mpaulosky
mpaulosky merged commit 21a173a into dev May 13, 2026
15 checks passed
@mpaulosky
mpaulosky deleted the squad/327-test-interop-lifecycle-validation branch May 13, 2026 18:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

squad Squad triage inbox — Lead will assign to a member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Sprint 19] Test/interop/lifecycle vertical validation slice

2 participants