Repository navigation
Improve viewport overlay update order and regression coverage - #470
nightt5879 wants to merge 2 commits into
Conversation
Extract the existing React board change effect into an internal hook and exercise it with the shared fixture, real viewport transforms, ListRender, and RoughSVG output. Cover zoom in/out, batched viewport changes, and scrolling. Document why the current post-layout active refresh remains necessary. Refs #461
Deploying drawnix with
|
| Latest commit: |
4b4f306
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://13467e53.drawnix.pages.dev |
| Branch Preview URL: | https://nightt5879-issue-461-viewpor.drawnix.pages.dev |
Review: unit test structureScope note: this comment covers the unit tests only. Feedback on 1. The behaviour this PR changes is never asserted directlyThe stated goal is "selected components draw against the final inputs once, without a corrective pass". Of those three properties, drawn after the viewBox was finalised is never asserted — it is inferred from whether the coordinates come out right. That inference is why the spec has to bring in the whole viewport geometry model (the 33-line Recording the viewBox at draw time turns the ordering into a direct assertion: const roughSVG = PlaitBoard.getRoughSVG(board);
const original = roughSVG.rectangle.bind(roughSVG);
const draws: { viewBox: string | null; args: unknown[] }[] = [];
vi.spyOn(roughSVG, 'rectangle').mockImplementation((...args) => {
draws.push({ viewBox: host.getAttribute('viewBox'), args });
return original(...args);
});
2. The real browser-scroll path is not coveredAll four scroll-related cases call
3. Suggested split
const calls: string[] = [];
const viewBoxAtRender: (string | null)[] = [];
class TestComponent extends CommonElementFlavour<TestElement, PlaitBoard> {
initialize() {
super.initialize();
this.getRef().updateActiveSection = () => calls.push('updateActiveSection');
}
onContextChanged() {
calls.push('render');
viewBoxAtRender.push(PlaitBoard.getHost(this.board).getAttribute('viewBox'));
}
}Four cases, matching the paths that can actually occur: a. await act(async () => { Transforms.setSelection(board, selection); });
expect(calls).toEqual(['render']);
expect(host.getAttribute('viewBox')).toBe(before);b. const before = host.getAttribute('viewBox');
await act(async () => { BoardTransforms.updateZoom(board, 2); });
expect(calls).toEqual(['render']);
expect(host.getAttribute('viewBox')).not.toBe(before);
expect(viewBoxAtRender).toEqual([host.getAttribute('viewBox')]);c. Browser scrolling — driven through the real listener renderHook(() => {
useBoardChange(board, listRender, noop);
useBoardEvent(board, { current: viewportContainer } as React.RefObject<HTMLDivElement>);
});
await act(async () => {
viewportContainer.scrollLeft = 40;
viewportContainer.scrollTop = 30;
viewportContainer.dispatchEvent(new Event('scroll'));
});
expect(host.getAttribute('viewBox')).toBe(before); // scrolling does not rebuild it
expect(isFromScrolling(board)).toBe(false); // the flag is consumed by this flush
expect(calls).toEqual(['render']);d. Ordinary content update — render first, then viewport, then the active refresh await act(async () => { Transforms.setNode(board, { points: nextPoints }, [0]); });
expect(calls).toEqual(['render', 'updateActiveSection']);Asserting the sequence rather than coordinates also fixes a blind spot in the current
expect(draws).toHaveLength(1); // drawn once
expect(draws[0].viewBox).toBe(host.getAttribute('viewBox')); // drawn on the new viewBox
expect(rectangle).toHaveBeenLastCalledWith(
x - p, y - p, w + 2 * p, h + 2 * p,
expect.objectContaining({ strokeWidth: ACTIVE_STROKE_WIDTH })
); // correct geometryThe remaining six cases are all exercising 4. Assertions that can goInside
Inside the cases:
5. Two prerequisites
Verification behind the above: |
Follow-up: e2e tests, and a suggestion to drop
|
Summary
#454 already fixed freehand selection alignment by adding a post-layout refresh. This follow-up finalizes the SVG viewBox and scroll offsets before rendering viewport-only changes, so selected overlays use the final viewport without a corrective second pass.
The change cycle is extracted into an internal hook for testing. Batches containing element changes retain the post-layout refresh because component updates can recalculate layout. Native scrolling and mixed operation batches are handled separately, and the lifecycle findings and test boundaries are documented.
Regression tests use the shared testing fixture, real viewport transforms, the production change hook, and real RoughSVG output. Browser tests cover freehand, rectangles, images, arrows, mind maps, multiple selection, and text layout changes.
Related Issue
Closes #461.
The lifecycle improvement applies to viewport-only changes. Removing
updateActiveSectionfrom all element-update paths remains outside this change.Validation
Evidence, screenshots, or compatibility notes:
npm run lintandnpm run build: passed.npm run test -- --run: 36 tests passed, including 8 focused viewport tests.npx nx e2e web-e2e --workers=3 --reporter=line: 24 tests passed across Chromium, Firefox, and WebKit (21 new regression runs and 3 existing smoke runs).apps/web-e2e/playwright.config.ts:32..drawnixserialization changes.AI Assistance
Did you use AI tools to generate or substantially modify code, tests, or documentation in this pull request?
If yes:
Tool/model: Codex/GPT-6 Astra
AI-assisted work: Lifecycle investigation, implementation, regression tests, documentation, and local automated/browser validation.
My review and validation: I reviewed and understand the submitted changes, discussed the lifecycle behavior and scope, and manually tested the local frontend. Codex ran the automated checks listed above.
I reviewed and understand the AI-assisted changes and can explain and maintain them
Checklist