Rewrite the HARD-tier and remaining merge tests with React Testing Library#316
Rewrite the HARD-tier and remaining merge tests with React Testing Library#316jonathangreen wants to merge 2 commits into
Conversation
|
Claude finished @jonathangreen's task in 25m 18s —— View job Code Review
SummaryThis is a large but clean test-only migration. I reviewed the two big suites ( DetailsMinor:
|
Greptile SummaryThis PR rewrites the HARD-tier and remaining merge tests with React Testing Library. The main changes are:
Confidence Score: 4/5This is close, but the uploaded-cover test gap should be fixed before merging.
tests/jest/components/BookCoverEditor.test.tsx Important Files Changed
Reviews (7): Last reviewed commit: "Rewrite the HARD-heavy and remaining mer..." | Re-trigger Greptile |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #316 +/- ##
==========================================
+ Coverage 90.19% 90.90% +0.70%
==========================================
Files 205 205
Lines 7610 7610
Branches 1664 1664
==========================================
+ Hits 6864 6918 +54
+ Misses 437 394 -43
+ Partials 309 298 -11
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:
|
7826d2d to
637e028
Compare
d45ae2c to
3739c04
Compare
637e028 to
5655faf
Compare
5655faf to
85f9e49
Compare
48ea4b9 to
5643d14
Compare
## Description Adds one shared FormData test shim at `tests/jest/testUtils/formDataShim.ts`, plus its spec test. The unit jsdom environment installs undici's `FormData`, which throws on `new FormData(formElement)`. The reusable-components `Form` builds FormData that way on submit, so any test that asserts a submitted payload needs a stand-in. Previously each migration branch carried its own inline copy; this consolidates them into one. The shim mirrors the browser's `new FormData(form)` **successful-controls** rules: - named, non-disabled controls only; - checkboxes/radios contribute only when checked; - submit/reset/button/image/file inputs excluded; - every selected option of a `<select multiple>` included. It also exposes the standard read/write/iterate surface (`get`/`getAll`/`has`/`append`/`set`/`delete`/`forEach`/`keys`/`values`/`entries`/iterator). `installFormDataShim()` swaps it in around a suite (`beforeAll`) and restores the real `FormData` afterward (`afterAll`). `formDataShim.test.ts` (8 cases) pins the successful-controls behavior — disabled omitted, checkbox only when checked, radio group, submit excluded, multi-select expansion, and the read/write API — so the fidelity can't silently regress. ## Motivation and Context Part of the mocha/chai/enzyme → jest/RTL migration. Extracting the shim into its own small, self-tested base PR lets #314, #315, and #316 depend on one implementation via `installFormDataShim()` instead of duplicating (and independently drifting) a local shim in each — while keeping this base stable so those stacked PRs don't churn. ## How Has This Been Tested? `npx jest --selectProjects=unit --testPathPattern formDataShim` — 8/8 green. ## Checklist: - [x] I have updated the documentation accordingly. - [x] All new and existing tests passed.
Twelve more enzyme suites migrated: the announcement/genre/contributor/paired-menu forms, ChangePasswordForm, NeighborhoodAnalyticsForm, TextWithEditMode, ToolTip, and three that merge into their existing jest files (Lane, EditableInput, IndividualAdmins — which already had a jest test for a newer feature, so the legacy behaviors were appended as a new describe block rather than duplicated). The recurring rewrites: prototype stubs (EditableInput.prototype.getValue, TextWithEditMode) become real userEvent.type into the field, reading the value back through the component's real ref API; state()/instance() reads become DOM assertions; window.confirm via jest.spyOn. IndividualAdmins' only gap was its connect wiring (the RBAC gating is covered by roleBasedAccess.test.ts), so a single connected-default-export render closes it. Coverage 94.73% (+41 lines over baseline), no previously-covered line lost. 24 legacy suites remain.
…ibrary Twenty more enzyme suites, the hardest of the migration: ServiceEditForm (1087 LOC), CustomListEntriesEditor (1461 LOC), Header, CatalogPage, EditorField, InputList, Lanes, LaneCustomListsEditor, LanesSidebar, LibraryRegistration, SelfTests, BookCoverEditor, ClassificationsForm — plus seven that merge into an existing tests/jest file (Collections, CustomLists, DiscoveryServices, EditableConfigList, IndividualAdminEditForm, LaneEditor, ProtocolFormField). The techniques that made the hard cases tractable: - Live-instance-method spies (ServiceEditForm's handleData / removeLibrary / isLibraryRemovalPermitted) become observable behavior — asserting the submitted FormData and the removed/retained library row — with tiny test subclasses to drive the removal-permitted branches, since the base method always returns true. - react-beautiful-dnd, which can't drag in jsdom, is handled by mocking DragDropContext to capture the component's onDragStart/onDragEnd (and rendering the render-prop children so the DOM is intact), then calling those callbacks with synthetic drag results. CustomListEntriesEditor and LaneCustomListsEditor reach 100% this way; Lanes drives its `drag` callback through a ref (mirroring what LanesSidebar does in production). - CatalogPage's OPDSCatalog and the web-opds-client require() are mocked to a marker that echoes the computed URLs and reads the tab/library child context; the legacy global.jsdom.reconfigure origin trick is replaced with the real jsdom origin (the expand helpers only strip the origin). - Connected panels (SelfTests, BookCoverEditor, and the merges) render their connected default export so mapStateToProps/mapDispatchToProps stay covered — SelfTests' wiring is what the SelfTestsTabContainer full-mount used to carry. - ClassificationsForm's setTimeout(500) focus path uses fake timers and a focus spy; forms install the FormData shim the unit env needs. Coverage rises to 95.21% (+75 lines over the 94.22% baseline), no previously- covered line lost. The gate caught every drag/edge line the first pass dropped (Lanes, LaneCustomListsEditor, LibraryRegistration, LanesSidebar), each then covered with the capture-callback or a targeted branch test. Only 4 legacy enzyme suites remain: the deferred tab containers (ConfigTabContainer, BookDetailsTabContainer, SelfTestsTabContainer, AdvancedSearchBuilder), whose panels are now all migrated with connected coverage, so they can finally be rewritten mocking their panels.
5643d14 to
4e4fdfa
Compare
| it("saves the cover with the entered values", async () => { | ||
| const user = userEvent.setup(); | ||
| const { store, refreshCatalog } = renderEditor(); | ||
| await awaitMounted(); | ||
|
|
||
| // A preview must exist for the save button to be enabled. | ||
| act(() => { | ||
| store.dispatch({ type: "PREVIEW_BOOK_COVER_LOAD", data: "image data" }); | ||
| }); | ||
|
|
||
| await user.type( | ||
| document.querySelector('input[name="cover_url"]') as HTMLElement, | ||
| "http://example.com" | ||
| ); | ||
| await user.selectOptions( | ||
| document.querySelector('select[name="title_position"]') as HTMLElement, | ||
| "center" | ||
| ); | ||
| await user.selectOptions( | ||
| document.querySelector('select[name="rights_status"]') as HTMLElement, | ||
| "http://creativecommons.org/licenses/by/4.0/" | ||
| ); | ||
| await user.type( | ||
| document.querySelector( | ||
| 'textarea[name="rights_explanation"]' | ||
| ) as HTMLElement, | ||
| "explanation" | ||
| ); | ||
|
|
||
| await user.click(screen.getByRole("button", { name: "Save this cover" })); | ||
|
|
||
| // The edited cover is posted to the change-cover link with the form values, | ||
| // and on success the catalog is refreshed. | ||
| const editCall = fetchSpy.mock.calls.find(([u]) => | ||
| String(u).includes("change_cover") | ||
| ); | ||
| expect(editCall).toBeTruthy(); | ||
| expect(editCall[1].method).toBe("POST"); | ||
| expect(editCall[1].body.get("cover_url")).toBe("http://example.com"); | ||
| expect(editCall[1].body.get("title_position")).toBe("center"); | ||
| expect(editCall[1].body.get("rights_status")).toBe( | ||
| "http://creativecommons.org/licenses/by/4.0/" | ||
| ); | ||
| expect(editCall[1].body.get("rights_explanation")).toBe("explanation"); | ||
|
|
||
| await waitFor(() => expect(refreshCatalog).toHaveBeenCalledTimes(1)); | ||
| }); |
There was a problem hiding this comment.
This save test only exercises cover_url and never selects or asserts the cover_file field. The suite installs a FormData shim that omits file inputs, while a browser new FormData(imageForm) includes the uploaded cover as a File. A change that drops the uploaded cover from BookCoverEditor.save() would still pass this test because the posted body is only checked for URL, title position, and rights fields. Please add a file-upload save case or preserve File entries in the test shim so uploaded-cover submissions are asserted.
|
I'm going to close this monster, and try to split it into a few smaller more reviewable PRs. |
Description
The HARD-tier suites and the "remaining merge" cases, across two commits.
Rewrite more HARD-tier tests— 12 suites: the announcement/genre/contributor/paired-menu forms,ChangePasswordForm,NeighborhoodAnalyticsForm,TextWithEditMode,ToolTip, and three that merge into existing jest files (Lane,EditableInput,IndividualAdmins). Prototype stubs (EditableInput.prototype.getValue,TextWithEditMode) become realuserEvent.typereading back through the component's ref API;state()/instance()reads become DOM assertions.Rewrite the HARD-heavy and remaining merge tests— 20 more, the hardest of the migration:ServiceEditForm(1087 LOC),CustomListEntriesEditor(1461 LOC),Header,CatalogPage,EditorField,InputList,Lanes,LaneCustomListsEditor,LanesSidebar,LibraryRegistration,SelfTests,BookCoverEditor,ClassificationsForm, plus seven merges. Highlights:react-beautiful-dnd(can't drag in jsdom) is handled by mockingDragDropContextto captureonDragStart/onDragEnd, then invoking them with synthetic drag results —CustomListEntriesEditor/LaneCustomListsEditorreach 100% this way.mapStateToProps/mapDispatchToPropsstay covered.userEventusage follows theconst user = userEvent.setup()convention acrosstests/jest/.Motivation and Context
Part of the mocha/chai/enzyme → jest/RTL migration — the hardest suites, which enzyme mounted and which poked class internals. After this, the only legacy enzyme suites left are the deferred tab containers, whose panels are now all migrated with connected coverage.
How Has This Been Tested?
npx jest --selectProjects=unit— full suite green. Coverage rises to 95.21% (+75 lines over baseline) across the two commits; no previously-covered line lost. eslint + prettier clean.Checklist: