Repository navigation
Cover in the browser what jest was covering alone, and drop those cases - #2240
Merged
Merged
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #2240 +/- ##
==========================================
- Coverage 74.10% 74.01% -0.09%
==========================================
Files 129 129
Lines 3746 3745 -1
Branches 829 840 +11
==========================================
- Hits 2776 2772 -4
- Misses 964 967 +3
Partials 6 6
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
size-limit report 📦
|
paskal
force-pushed
the
e2e-covers-more
branch
6 times, most recently
from
September 4, 2026 09:26
95a9d6e to
9c3ecf0
Compare
paskal
force-pushed
the
e2e-covers-more
branch
2 times, most recently
from
September 7, 2026 21:45
064bec1 to
4256323
Compare
The previous change removed the jest cases the browser suite already asserted. These are the ones it could have asserted and did not: the states behind a held-open request, the actions an ordinary reader is offered, an order nothing read, and the values inside elements the suite only waited for. Eighteen jest cases go. Each is released by a named browser assertion, and each of those is proven by a mutant that makes it fail at the assertion it names, recorded in the pull request with the mutant and the expected failure point. Line coverage is not the check here: two cases can run identical lines and assert different things. That discipline caught four assertions of mine that proved nothing. Delete being absent on another reader's comment held with the ownership test removed, because editDeadline is supplied only for the reader's own comment and the weaker condition is equivalent. Reply being absent on a read-only thread held because the thread had no comments in it. The profile states passed with the route glob never matching, since a question mark is a single-character wildcard in playwright's url matching. And a cast downvote staying disabled held with its guard removed, because the in-flight guard covers it either way; that one could not be fixed, so the assertion was withdrawn and its jest case kept. Where one of those traps can be tripped again, the warning sits beside the code that would trip it: the read-only case says why it posts a comment first, the profile case says why its route is a regexp, the countdown says why its bound allows a second, and the downvote case says why the assertion that looks like it belongs there is not there. The counter gets a class outside the css modules. It carried only a hashed name and a data-testid the production bundle strips, which is what kept the count out of reach of a browser case.
setEmailStep deferred its step change behind a zero timeout. That existed to dodge the dropdown closing on the click: the rerender removed the Back button before the click reached the document's bubble-phase listener, which then found no target inside the panel and treated the click as outside. With that listener in the capture phase the deferral does nothing, so it goes, and the flow becomes the one place in the widget where a click detaches its own control inside the click's task. That is the flow the capture listener exists for, so it is now the browser case that pins it: submit an address, click Back on the token step, and the panel has to stay open on the email step. With the listener moved back to the bubble phase the case fails at that assertion, with the two clicks before it passing.
paskal
force-pushed
the
e2e-covers-more
branch
from
September 8, 2026 08:52
ef21f8f to
1169a73
Compare
umputun
approved these changes
Sep 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#2239 removed the jest cases the browser suite already asserted. These are the ones it could have asserted and did not: the states behind a held-open request, the actions an ordinary reader is offered, an order nothing read, and the values inside elements the suite only ever waited for.
Eighteen jest cases go, 340 to 322. Two commits on master: the cases, and the Back step below.
The rule, and why coverage is not it
Each removed case is released by a named browser assertion, and each of those is proven by a mutant that makes it fail at the assertion it names. Two cases can execute identical lines and assert different things, so a coverage diff cannot tell a real replacement from a coincidental one.
Where a trap can be tripped again, the warning sits beside the code that would trip it: the read-only case says why it posts a comment first, the profile case why its route is a regexp, the countdown why its bound allows a second, and the vote case why the assertion that looks like it belongs there is not there.
What was added
TestComment_ActionsDependOnWhoseCommentItIsTestVote_BothButtonsAreDisabledWhileTheVoteIsInFlightTestTelegramSub_AFailed{Check,Unsubscribe}IsClearedByASuccessfulOneTestProfile_LoadingAndFailureStatesTestComment_AdminActionsKeepTheirOrderTestComment_EditCountdownCountsDownTestTelegramSub_ThePanelSaysItIsWorkingTestSubscribe_BackFromTheTokenStepKeepsThePanelOpenThe first two of those add coverage and release nothing: every jest case touching them also asserts a call count or another thing the browser does not.
The two notification-flag assertions cost no stack time. The main instance runs
NOTIFY_USERS=emailand the Telegram instance the inverse, so each is already the other's negative case, and both were folded into cases that load those instances anyway.Four assertions that proved nothing
The interesting half. Each of these passed, and would have shipped as a proof:
editDeadlineis supplied only for the reader's own comment, so the weakened condition is equivalent on the comment under test.**/api/v1/comments?user=**is a glob, and?is a single-character wildcard, so the request was never intercepted and simply succeeded. It uses a regexp now.Three were fixable. One was not. A coverage check would have been green for all four.
Two code changes
Countergains a class outside the CSS modules. It carried only a hashed module name and adata-testidthe production bundle strips, which is what kept the count out of reach of any browser case.setEmailStepno longer defers its step change behind a zero timeout. That deferral existed to dodge the dropdown closing on the click: the rerender removed the Back button before the click reached the document's bubble-phase listener, which found no target inside the panel and closed it over the email form. With that listener in the capture phase the deferral does nothing, so it is gone, and Back on the token step is now the one flow in the widget where a click detaches its own control inside the click's task.TestSubscribe_BackFromTheTokenStepKeepsThePanelOpenpins the phase on that flow: submit an address, click Back, and the panel has to stay open on the email step. With the listener moved back to the bubble phase and the image rebuilt, it fails at that assertion with both clicks passing first, which the earlier synthetic case could show only by removing the target itself.Review
Both reviewers went over this twice. Every finding is fixed; the ones worth knowing about are the assertions that could not fail, because each looked like a check and was not.
Could not fail, and were rewritten or removed
commentsJSX'scomments?.lengthbranch and never renders while the list is out anyway. They are now proven by rendering the heading unconditionally, and by keeping the preloader alive through the failure.2ssatisfied it. A unit-scaling mutant now fails it: "the countdown started 14s below the edit window, having spent 0s getting there".Correctness and coverage
Load moreabsences the removed jest cases asserted. Both restored, in both states.TextContent()inside aneventuallyclosure carried playwright's 30s default instead ofpollText's 1s, overshooting the loop's budget and reporting the wrong failure.window - 2, which is scheduler-sensitive at-parallel 4. It now derives from time actually spent since the comment was posted.Copied!comment claimed the assertion separates a working copy from a silent no-op. It does not:copyCommentsetsisCopiedoutside its own catch.A CI failure this introduced
The read-only case posted a comment containing the words "read-only", and
text=Read-onlymatches case-insensitively, so the status locator matched both the status and the comment. Two matches is a strict-mode failure. It passed locally because the comment rendered below the fold, where the IntersectionObserver leaves the article empty, and failed in CI where it did not. The text can no longer collide and the status is matched exactly.Tidying: an empty
describe('verification')husk, an orphaned comment this same commit falsified, a comment detached from the statement it explained, theSelectorslist missing the two new hooks, and the backlog work-list still presenting as open the items this change completes.Codecov
codecov/projectpasses. Removing jest cases lowers the frontend number while the widget coverage that replaces it is measured bymake e2e-coverand never uploaded, so the threshold added in #2239 gives the project status half a percent of slack. Uploading the widget lcov from the e2e workflow under its own flag, so codecov unions it with jest on the same commit, remains a workflow change this PR deliberately does not make.