Skip to content

test(edit-mode): expand EditModeController coverage#537

Merged
fernandotonon merged 5 commits into
masterfrom
feat/edit-mode-controller-coverage
May 16, 2026
Merged

test(edit-mode): expand EditModeController coverage#537
fernandotonon merged 5 commits into
masterfrom
feat/edit-mode-controller-coverage

Conversation

@fernandotonon
Copy link
Copy Markdown
Owner

@fernandotonon fernandotonon commented May 16, 2026

Summary

Adds 62 new TEST_F cases in EditModeController_test.cpp covering the many methods that previously had zero coverage. Baseline on EditModeController.cpp is 64.5% line coverage (1099 uncovered lines across 139 functions, 3979 LoC).

New coverage groups

  • Color setters (direct QColor): setVertexPaintColor, setVertexPaintBackgroundColor — invalid input, zero-alpha promotion (FG), zero-alpha allowed (BG), same-value no-emit
  • Mode lifecycle: canEnterEditMode, toggleEditMode, mesh-info counts when outside edit mode
  • Normals: setNormalsMode round-trip + signal, recalculateNormals smooth + flat paths, validateMesh, degenerateTriangleCount, removeDegenerateTriangles
  • Vertex color preview: setVertexColorPreviewEnabled toggle + signal
  • Vertex transforms: getSelectedVerticesCentroid, rotateSelectedVertices, scaleSelectedVertices, scaleFromSnapshot, snapshotVertexPositions / restoreVertexPositions round-trip
  • Mesh classification: isMeshQuadBased, canConvertToQuads
  • Topology: extrudeSelection error + cube happy path, selectedFacesAsHEFaceIndices, flushPendingVertexPaintForEntity, vertex-paint-stroke initial state guards
  • Bevel gizmo with no session: tickBevelGizmo, isBevelGizmoHandle, updateBevelWidth, commitBevel (no-op safety)
  • Knife: addKnifePointOnEdge outside any session
  • Merge ops on cube: mergeAtCenter, mergeAtFirst, mergeAtLast, mergeByDistance (no-selection + happy 2-vert paths)
  • Loop cut / convert-to-quads / Catmull-Clark on the cube

A new fixture EditModeControllerMergeOpsTest provides a cube + selection setup mirroring the existing EditModeControllerBevelE2ETest so merge / extrude / loop-cut / CC tests have something real to work on.

Test plan

  • Compiles cleanly on macOS arm64
  • Linux unit-tests-linux runs the new tests under Xvfb
  • SonarCloud reports improved coverage on EditModeController.cpp post-merge

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Tests
    • Expanded coverage for edit-mode behaviors: paint color/brush handling (including invalid inputs, alpha rules, and suppressed duplicate signals), mode toggles and eligibility, normals and validation semantics, vertex operations (selection, transform, snapshots), mesh conversion/merge/extrude/loop-cut workflows, bevel/knife/session guards, dissolve/delete behaviors, geometry helpers, and numerous no-op/edge-case guards to ensure idempotency and correct signal emission.

Review Change Stack

…thods

Covers:
- Direct QColor setters (setVertexPaintColor / setVertexPaintBackgroundColor)
- canEnterEditMode / toggleEditMode
- vertexCount / triangleCount / subMeshCount default zero outside edit mode
- normalsMode round-trip + signal
- recalculateNormals smooth + flat paths
- validateMesh + degenerateTriangleCount + hasValidationWarnings
- removeDegenerateTriangles no-op paths
- vertexColorPreviewEnabled toggle + signal
- getSelectedVerticesCentroid empty + selection
- rotateSelectedVertices / scaleSelectedVertices / scaleFromSnapshot
- snapshotVertexPositions / restoreVertexPositions round-trip
- isMeshQuadBased / canConvertToQuads
- extrudeSelection error paths + cube happy path
- selectedFacesAsHEFaceIndices empty + with selection
- flushPendingVertexPaintForEntity no-op paths
- vertex paint stroke initial-state guards
- bevel gizmo helpers (tick / isHandle / updateBevelWidth / commitBevel)
  with no active session
- addKnifePointOnEdge with no session
- merge ops (mergeAtCenter / mergeAtFirst / mergeAtLast / mergeByDistance)
  no-selection + 2-vert happy paths
- loopCutSelection error paths
- convertToQuads cube happy path + outside-edit-mode
- subdivideCatmullClarkAll cube happy path + outside-edit-mode

Baseline coverage on EditModeController.cpp was 64.5% (1099 uncovered
lines, 3979 LoC); these tests target the largest untested methods.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 16, 2026

Warning

Rate limit exceeded

@fernandotonon has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 21 minutes and 39 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b4abb8c9-485b-49a8-bd9f-f7055536cb21

📥 Commits

Reviewing files that changed from the base of the PR and between 9314276 and 74eabd9.

📒 Files selected for processing (1)
  • src/EditModeController_test.cpp
📝 Walkthrough

Walkthrough

Adds ~1.4k lines of GTest coverage exercising EditModeController: vertex painting, edit-mode lifecycle, normals/validation, selection transforms, tool-session guards, merge/bevel/mesh topology ops, and geometry helper edge cases.

Changes

EditModeController Test Suite

Layer / File(s) Summary
Vertex paint & preview controls
src/EditModeController_test.cpp
Direct color/background setters, brush-string parsing, enabled/preview toggles with signal deduping, pending-stroke guards, and applyVertexColorBrush tests.
Edit mode lifecycle and mesh properties
src/EditModeController_test.cpp
canEnter/toggle idempotency, zeroed mesh counts outside edit mode, and paint/wireframe same-value signal suppression tests.
Normals mode & mesh validation
src/EditModeController_test.cpp
Normals-mode round-trips and signal semantics, recalculateNormals gating, validateMesh and removeDegenerateTriangles inside vs. outside edit mode.
Selection transforms, snapshots, and quad capability
src/EditModeController_test.cpp
Centroid/rotate/scale behavior for empty vs selected, snapshot/restore and scaleFromSnapshot semantics, quad-conversion gating, extrude/face-selection edge cases, and selected-face indexing.
Bevel / knife / stroke session guards
src/EditModeController_test.cpp
No-op and null-check guards for bevel gizmo, bevel session lifecycle (begin/update/commit/cancel), knife-point guard, and stroke lifecycle guards.
Merge, extrude, loop-cut, convert, subdivide
src/EditModeController_test.cpp
EditModeControllerMergeOpsTest with merge variants, extrude happy path, loop-cut error/hint paths, convertToQuads happy path and outside-edit-mode no-op, subdivideCatmullClarkAll tests.
Post-merge selection, dissolve, delete and strict convert thresholds
src/EditModeController_test.cpp
Post-merge selection/retirement, mergeByDistance subset cases, dissolve/delete across modes, and strict-threshold convertToQuads case.
Geometry helpers & ray tests
src/EditModeController_test.cpp
applyVertexColorBrush edge cases, weightToColor clamping tests, pointToSegmentDistance cases, and degenerate-triangle rayTriangleIntersect no-hit.
Entity rewrite safety test
src/EditModeController_test.cpp
rewriteEntityAfterTopologyChange called on a real entity to validate safe behavior (no crash) and undo/redo contract coverage.

Possibly Related PRs

"🐰 I hopped through code with a cheerful peep,
Tests for paint, merge, and normals to keep,
No-op guards snug, signals only when new,
Meshes checked tidy — the test-rabbit says woo!
🥕✨"

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.98% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary change: expanding test coverage for EditModeController with 62 new test cases.
Description check ✅ Passed The description provides comprehensive coverage organization, test plan status, and technical details; it closely follows the template structure with Summary and Technical Details sections.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/edit-mode-controller-coverage

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

…edge cases

Adds 38 additional TEST_F / TEST cases:
- beginBevel happy paths (edge + vertex mode), error paths
  (no selection, face mode, outside edit mode, second begin), commit /
  cancel of an active session, updateBevelWidth no-op without session
- bevelSegments / profile-points no-op paths
- mergeAtCenter all-corners + mergeByDistance large threshold
- dissolveSelection / deleteSelection in vertex + edge modes
- convertToQuads strict 0° threshold
- enterEditMode no-selection refusal + double-enter idempotence
- exitEditMode commitChanges=true safe + outside-edit-mode no-op
- paint shape / radius / strength / falloff same-value no-emit
- wireframe same-value no-emit
- applyVertexColorBrush square shape + zero-radius + empty mesh
- weightToColor clamps above 1 / below 0
- pointToSegmentDistance vertical + diagonal segments
- rayTriangleIntersect degenerate triangle
- rewriteEntityAfterTopologyChange on real entity

Total new tests in this PR: 100 across two commits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/EditModeController_test.cpp`:
- Around line 3047-3049: The assertion EXPECT_GE(spy.count(), 0) is tautological
and can't detect missing hint emissions; update the test to assert that at least
one hint was emitted by replacing the check on spy.count() with a stronger
assertion (e.g., EXPECT_GE(spy.count(), 1) or EXPECT_GT(spy.count(), 0)) so the
test fails if no hint is produced; locate the assertion referencing spy.count()
in the EditModeController_test case and change the expected value accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d370c9cd-a854-45c0-bef1-90278d41293e

📥 Commits

Reviewing files that changed from the base of the PR and between 6b00059 and d570a87.

📒 Files selected for processing (1)
  • src/EditModeController_test.cpp

Comment thread src/EditModeController_test.cpp Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d570a87d14

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/EditModeController_test.cpp Outdated
EXPECT_EQ(ctrl->loopCutSelection(), 0);
// Hint may or may not fire depending on internal walk — but in this
// case (welded cube triangle topology) it should.
EXPECT_GE(spy.count(), 0);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Assert loop-cut hint emission

This assertion is always true, so the test does not actually verify the behavior described in its name/comment (that loopCutSelection() emits editHintMessage on triangle-only topology). If a regression stops emitting the hint, CI will still pass, which weakens the new coverage for this error path. Please assert a positive count (or exact expected count) after calling loopCutSelection().

Useful? React with 👍 / 👎.

fernandotonon and others added 2 commits May 16, 2026 12:13
The MergeAtCenter / MergeByDistance "merge all 8 verts" tests caused a
SIGSEGV in the GPU-side mesh rebuild path under Linux/Xvfb — the post-
merge mesh has zero volume and a single vertex, and the entity
buffer-resize crashes.

Replaces with 3-vertex (one face's corners) merges that exercise the
same merge / cluster paths without producing a degenerate mesh:
- MergeAtCenterThreeAdjacentVertsRetiresTwo
- MergeByDistanceModerateThresholdOnSubset

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…asserts

CodeRabbit flagged that EXPECT_GE(spy.count(), 0) is always true and can't
detect missing hint emissions on the loop-cut tri-adjacency path.

- LoopCutOnTriangleAdjacencyEmitsHintAndReturnsZero: now asserts the
  hint actually fires (spy.count() >= 1) and that its content matches
  the "Loop cut needs a quad mesh — try Mesh → Convert to Quads"
  copy from EditModeController.cpp:3999.

While here, replaced other weak EXPECT_GE(n, 0) asserts on
dissolve/delete/convertToQuads return values with concrete expectations:
- DissolveSelectionVertexModeOnCorner: cube corner v0 has degree 5, so
  dissolveVertices refuses → EXPECT_EQ(n, 0) (no-op is the contract).
- DeleteSelection (vertex / edge): EXPECT_GT(n, 0) AND the GPU triangle
  count must drop — checks both the API return and the mesh side-effect.
- ConvertToQuadsStrictThresholdMergesCubePairs: a welded cube's 12 tris
  pair up coplanarly into exactly 6 quads, so EXPECT_EQ(merged, 6) plus
  isMeshQuadBased() now flips to true.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/EditModeController_test.cpp`:
- Around line 3192-3195: The test currently converts canUndo() to an int and
compares counts which can hide a missing undo push; after calling
ctrl->commitBevel() and EXPECT_FALSE(ctrl->bevelSessionActive()), replace the
integer comparison with a direct check of UndoManager::getSingleton()->canUndo()
(e.g. EXPECT_TRUE(UndoManager::getSingleton()->canUndo())) to assert that
commitBevel() actually created an undoable state.
- Around line 3510-3512: The test only asserts sub.vertices[2].color.b == 1.0
which could also pass for pure blue; update the assertion to verify the vertex
remained white by checking that color.r and color.g are also ~1.0 in addition to
color.b. Locate the test that references sub.vertices[2].color (the EXPECT_NEAR
on .color.b) and add EXPECT_NEAR checks for .color.r and .color.g with the same
tolerance (1e-3f) to ensure the vertex stayed white.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1cd7389e-70ca-4342-835f-c4215c8cbdfc

📥 Commits

Reviewing files that changed from the base of the PR and between d570a87 and 9314276.

📒 Files selected for processing (1)
  • src/EditModeController_test.cpp

Comment on lines +3192 to +3195
const int undoCountBefore = UndoManager::getSingleton()->canUndo() ? 1 : 0;
ctrl->commitBevel();
EXPECT_FALSE(ctrl->bevelSessionActive());
EXPECT_GE(UndoManager::getSingleton()->canUndo() ? 1 : 0, undoCountBefore);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Strengthen undo assertion to verify commit actually creates undo state.

The current boolean-to-int comparison can pass even when commitBevel() does not push a new undoable action. Assert canUndo() directly after commit.

Proposed fix
-    const int undoCountBefore = UndoManager::getSingleton()->canUndo() ? 1 : 0;
+    const bool hadUndoBefore = UndoManager::getSingleton()->canUndo();
     ctrl->commitBevel();
     EXPECT_FALSE(ctrl->bevelSessionActive());
-    EXPECT_GE(UndoManager::getSingleton()->canUndo() ? 1 : 0, undoCountBefore);
+    EXPECT_TRUE(UndoManager::getSingleton()->canUndo());
+    (void)hadUndoBefore; // keep if you want to later assert growth with an explicit count API
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const int undoCountBefore = UndoManager::getSingleton()->canUndo() ? 1 : 0;
ctrl->commitBevel();
EXPECT_FALSE(ctrl->bevelSessionActive());
EXPECT_GE(UndoManager::getSingleton()->canUndo() ? 1 : 0, undoCountBefore);
const bool hadUndoBefore = UndoManager::getSingleton()->canUndo();
ctrl->commitBevel();
EXPECT_FALSE(ctrl->bevelSessionActive());
EXPECT_TRUE(UndoManager::getSingleton()->canUndo());
(void)hadUndoBefore; // keep if you want to later assert growth with an explicit count API
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/EditModeController_test.cpp` around lines 3192 - 3195, The test currently
converts canUndo() to an int and compares counts which can hide a missing undo
push; after calling ctrl->commitBevel() and
EXPECT_FALSE(ctrl->bevelSessionActive()), replace the integer comparison with a
direct check of UndoManager::getSingleton()->canUndo() (e.g.
EXPECT_TRUE(UndoManager::getSingleton()->canUndo())) to assert that
commitBevel() actually created an undoable state.

Comment on lines +3510 to +3512
// v2 is outside the box (x=2 > radius=1), stays white.
EXPECT_NEAR(sub.vertices[2].color.b, 1.0f, 1e-3f);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Assertion does not prove the vertex stayed white.

Checking only b == 1.0 cannot distinguish white from fully blue paint. Assert r/g stay at 1.0 too.

Proposed fix
-    // v2 is outside the box (x=2 > radius=1), stays white.
-    EXPECT_NEAR(sub.vertices[2].color.b, 1.0f, 1e-3f);
+    // v2 is outside the box (x=2 > radius=1), stays white.
+    EXPECT_NEAR(sub.vertices[2].color.r, 1.0f, 1e-3f);
+    EXPECT_NEAR(sub.vertices[2].color.g, 1.0f, 1e-3f);
+    EXPECT_NEAR(sub.vertices[2].color.b, 1.0f, 1e-3f);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// v2 is outside the box (x=2 > radius=1), stays white.
EXPECT_NEAR(sub.vertices[2].color.b, 1.0f, 1e-3f);
}
// v2 is outside the box (x=2 > radius=1), stays white.
EXPECT_NEAR(sub.vertices[2].color.r, 1.0f, 1e-3f);
EXPECT_NEAR(sub.vertices[2].color.g, 1.0f, 1e-3f);
EXPECT_NEAR(sub.vertices[2].color.b, 1.0f, 1e-3f);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/EditModeController_test.cpp` around lines 3510 - 3512, The test only
asserts sub.vertices[2].color.b == 1.0 which could also pass for pure blue;
update the assertion to verify the vertex remained white by checking that
color.r and color.g are also ~1.0 in addition to color.b. Locate the test that
references sub.vertices[2].color (the EXPECT_NEAR on .color.b) and add
EXPECT_NEAR checks for .color.r and .color.g with the same tolerance (1e-3f) to
ensure the vertex stayed white.

…ests

CI revealed two wrong expectations from the previous tightening pass:

- DissolveSelectionVertexModeOnCorner expected dissolveSelection() == 0
  (assuming degree-5 verts can't dissolve). Actual: cube corner v0 dissolves
  cleanly and returns 1, retiring it. Updated to assert == 1 AND that the
  GPU vertex count drops (so a return-without-mutation is still caught).

- ConvertToQuadsStrictThresholdMergesCubePairs expected exactly 6 quad
  merges at 0° threshold (1 per cube face). Actual: 7 — the welded cube
  layout has an extra coplanar pair beyond the 6 face pairs. Asserting
  the exact 7 would couple the test to createInMemoryWeldedCube's
  internal triangulation, so it now asserts merged > 0 (proving 0° is
  inclusive) AND isMeshQuadBased() flips to true.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sonarqubecloud
Copy link
Copy Markdown

@fernandotonon fernandotonon merged commit faae230 into master May 16, 2026
20 checks passed
@fernandotonon fernandotonon deleted the feat/edit-mode-controller-coverage branch May 16, 2026 19:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant