refactor: share pendingRequests between connector and controller#9238
Open
DiegoCardoso wants to merge 23 commits into
Open
refactor: share pendingRequests between connector and controller#9238DiegoCardoso wants to merge 23 commits into
DiegoCardoso wants to merge 23 commits into
Conversation
vursen
reviewed
May 7, 2026
vursen
reviewed
May 7, 2026
vursen
reviewed
May 7, 2026
vursen
reviewed
May 7, 2026
b467eff to
d2e2d62
Compare
DiegoCardoso
commented
May 7, 2026
76cbd53 to
5cbf58f
Compare
27dfc71 to
44d8577
Compare
DiegoCardoso
commented
May 13, 2026
Comment on lines
+108
to
+117
| try { | ||
| WebElement log = findElement(By.id("log-" + index)); | ||
| Assert.assertEquals("Invalid range for index " + index, | ||
| index + ":" + rangeLog.getRange().toString(), | ||
| log.getText()); | ||
| } catch (NoSuchElementException e) { | ||
| Assert.fail("Log element not found for index " + index | ||
| + ", expected range: " | ||
| + rangeLog.getRange().toString()); | ||
| } |
Contributor
Author
There was a problem hiding this comment.
This is purely to give a better feedback in case the log-{N} element is not found.
4af3d8c to
be5e60c
Compare
The combo-box connector tracked pending page-load callbacks in a private pageCallbacks map, separate from the data-provider controller's own rootCache.pendingRequests. The two diverged in lifetime — pageCallbacks accumulated entries until cleared explicitly, while the controller's map shrank as responses arrived. Splits across these two maps caused the connector's "non-sequential range" check to misclassify a fresh single-page fetch as a stale gap and trigger clearPageCallbacks instead of serverFacade.requestData, leaving lazy callbacks unresolved on later scrolls. Replace pageCallbacks with getPendingRequests() reading the controller's own map. The connector and controller now share a single source of truth for in-flight page requests. Behavior change: the connector's overflow-eviction heuristic (active range * pageSize > 500) no longer fires, because the shared pendingRequests is small (only in-flight) instead of growing with every loaded page. Loaded pages stay cached for the lifetime of the combo-box session. Memory grows with how far the user scrolls (~50 items per page), matching Grid's behavior. Trade-off: scrolling back no longer triggers a re-fetch (spinner-free), at the cost of some retained items. Tests: - combo-box-connector.test.ts: new "pending requests" describe block exercising the shared map (4 tests). - ComboBoxClientSideDataRangeIT: rewritten to drop the eviction assertions; verifies pages load cumulatively. - ItemCountUnknownComboBoxIT: updated RangeLog indices to match the new fetch sequence (each scroll requests just the target page rather than a wide range). - ComponentRendererIT.multiplePagesOfItems_scrollDown_close_*: now asserts items are immediately rendered on reopen (cache hit) rather than blank during a fresh fetch. - LazyLoadingIT.setComponentRenderer_scrollDown_*: dropped the scroll-up step and renamed; the original test relied on eviction-driven scroll reset to verify the new renderer applies at the top. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The exact order/index of intermediate fetches the connector issues during a scroll-to-N call varies with viewport height and scroll timing across environments (intermediate prefetches appear or not depending on the browser). Asserting log-N == specific-range was flaky in CI. Verify the expected ranges appear somewhere in the server fetch log instead. The test purpose (the right pages were fetched in response to user scrolls) is preserved. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Use DataProviderController type in shared.ts instead of an inline literal mirroring its shape. - Drop the redundant delete in clearPageCallbacks: invoking the callback already removes the entry from pendingRequests via the controller's own callback. - Simplify $connector.confirm with Object.entries().forEach. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tracks active pages in the connector with a `Set` separate from the controller's `pendingRequests` (which is reset on commit) so the eviction heuristic survives committed pages. Without it, scrolling away from a page leaves the server's per-item Flow components passivated while the client still holds the items, and on scroll-back the renderer follows stale `nodeId` references that point at destroyed components. Also reverts the test updates that assumed no eviction. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…back" This reverts commit 2a53de9.
…improved clarity and functionality
…drain The dataProvider had its `_clientSideFilter` short-circuit removed during the refactor, breaking small-dataset client-side filtering — every typed filter now hit the 500ms server debounce and ended up wiping the cache. Restored the short-circuit so subsequent dataProvider calls filter `cache[0]` locally without server roundtrip. `$connector.clear` also stamped placeholders into `filteredItems` for every cleared page, breaking the connector's cumulative caching: scroll forward + back showed only the latest page rather than the full history ComboBoxClientSideDataRangeIT expects. Reverted to plain `delete cache[i]`, keeping `filteredItems` intact across server-driven drops. Filter-change drain now runs without calling stale callbacks (the controller's own `#loadCachePage` already overwrites pendingRequests with the new wrapper). WTR test updated accordingly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous fix removed placeholder-stamping in $connector.clear, which broke LazyLoadingIT.setComponentRenderer_scrollDown_itemsRendered: items out of the server's active range kept stale Flow nodeId references and re-rendered with the new ComponentRenderer pointed at destroyed components. Re-stamp placeholders on server-emitted clear so the virtualizer triggers a fresh fetch on scroll-back (the ensureFlatIndexLoaded path only fires on placeholders). The 150ms request debouncer also masked the in-flight setViewportRange RPC from TestBench's waitForVaadin: the test asserts after one requestAnimationFrame, before the timer macrotask runs. The debouncer isn't load-bearing for combo-box scrolling (smaller viewport than grid) so call requestPage directly. Drop hasData while there. Widen the prefetch buffer to at least pageSize so a single-page scroll keeps the previously-loaded page within the server's active range — otherwise the server emits clear() for it and placeholder-stamping trims loaded items count back to one viewport's worth. Update ComboBoxClientSideDataRangeIT to the bounded-caching shape introduced by the reverted b908b4f (matches the new prefetch eviction semantics). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
At the first dataProvider call after openPopup, comboBox.size can be undefined and the virtualizer's firstVisibleIndex/lastVisibleIndex can be NaN (set before the first row renders). Math.min(50, undefined) is NaN, which propagated through the range math and made setViewportRange receive length=null. The server treated it as 0 length, returned no items, and the virtualizer triggered a second dataProvider call once items rendered — visible to the user as the loading spinner flickering (appear → quickly disappear → appear again) and every interaction taking two server round-trips instead of one. Default size to +Infinity when not finite, and substitute 0 for NaN/undefined virtualizer indices. Each open or filter change now makes a single setViewportRange call. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When buffer-prefetch fetches multiple pages per server call, only the page the controller explicitly requested gets a pendingRequests entry and is committed via confirm(). Pages beyond that are stored as "orphan" entries in the connector-local cache. When the user later scrolls into one of those pages, the new dataProvider call would find the same lastRequestedRange and skip the server roundtrip, leaving the page-pending callback unresolved and the items rendered as placeholders. If cache already has the requested page, commit it synchronously and skip the server call. This makes scroll-driven reveals of prefetched pages instantaneous without extra server traffic. Tests updated: - ComboBoxClientSideDataRangeIT: replace the bounded-cumulative assertions with simpler "items render + count stays within buffer" checks. The connector caches viewport ± pageSize, not full history. - ComponentRendererIT.multiplePagesOfItems_scrollDown_close_*: reopening now re-fetches the visible page (placeholder-stamping on server-driven clear), so wait for items to render before asserting rather than reading the first DOM node immediately. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirror gridConnector by computing the viewport buffer from the scroller's rendered children (overscan window) instead of the visible-only virtualizer indices, and drop the pageSize floor on the buffer. The previous floor inflated the active range to 3+ pages for large pageSize. Update ITs to match: - ComboBoxClientSideDataRangeIT: replace cumulative-loading assertions with a bound-only check (loaded count stays within ~2 pages). - LazyLoadingIT: restore main's expectation after scrolling a disabled ComboBox (50, not 100). - ItemCountUnknownComboBoxIT: shift RangeLog indices back closer to main now that fewer fetches happen per scroll. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After the connector stopped writing to pendingRequests in dataProvider (912faae), the existing tests asserted state that only the controller's loadCachePage populates. The failing assertions caused chai's formatter to choke on sinon spy values, so mocha never reported and WTR hit its 120s testsFinishTimeout. Rewrite the pending requests describe to drive loadFirstPage and assert on observable controller state, and stub confirmUpdate / resetDataCommunicator on the $server spy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
490f9e6 to
43ca860
Compare
43ca860 to
fea69b9
Compare
Without the truthy guard around the placeholder write, clear would unconditionally set comboBox.filteredItems[index] = placeHolder for the full requested range, extending the array beyond cache.size. After a filter that shrinks size, that left phantom placeholders past the new size, which the virtualizer kept requesting — creating a pending entry the server never resolved, so the dropdown stayed in loading forever. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
In $connector.confirm, the client-side-filter branch gated performClientSideFilter on page === 0, but Object.entries returns string keys — so the comparison was always false and the callback fired with empty data. Filtering a small dataset (CustomValueIT) silently dropped all matches. Swap the gate for an items-presence check, which also guards performClientSideFilter against undefined input. Update FilteringIT.verifyFilteredItems and LazyLoadingIT to assert a bounded loaded-items count instead of the pre-buffer-fix cumulative total — the active range now stays within ~2 pages by design. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
This was referenced May 14, 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.



The combo-box connector tracked pending page-load callbacks in a private
pageCallbacksmap, separate from the data-provider controller's ownrootCache.pendingRequests. The two diverged in lifetime —pageCallbacksaccumulated entries until cleared explicitly, while the controller's map shrank as responses arrived.A scroll-to-index call (e.g. via the new
scrollToIndexweb-component API or any programmatic jump) lands on a page index that's non-contiguous with whatever pages already populatedpageCallbacks, which the connector's "active range" check classifies as a stale gap and clears instead of issuing the fetch. Subsequent lazy callbacks never resolve andloadingstaystrue.The connector now reads
pendingRequestsdirectly from the controller (single source of truth). On top of that, the request path is rewritten to mirrorgridConnector:requestPagederives the fetch range from the virtualizer's visible viewport ±pageSize, so a single-page scroll keeps the previously-loaded page in the server's active range and a fast scroll past several pages only issues one request for the final viewport.lastRequestedRangededuplicates back-to-back requests for the same range.comboBox._filterDebouncer(withneedsDataCommunicatorResetto force a re-emit on A→B→A cycles within the debounce window). The filter guard in$connector.set/$connector.confirmcompares against the latestlastFilterso a stale-filter response is dropped immediately rather than briefly painting old data.$connector.clearstamps placeholders intofilteredItemsfor the cleared positions. When the user scrolls back into a range the server has evicted, the virtualizer re-fetches via the placeholders rather than rendering stale items (this is required forComponentRendereritems, which carrynodeIdreferences that the server invalidates when items leave the active range).dataProvideris called for a page whose data is already in the connector-local cache (from a buffer prefetch), the callback is committed synchronously — no server round-trip.getViewportRangedefaults the virtualizer indices to0when undefined/NaN, and the size clamp falls back to+InfinitywhencomboBox.sizeis undefined. Without these guards the very first request afteropenPopupsentlength=nullto the server, the server returned empty, the virtualizer re-fireddataProvideronce the size arrived, and the user saw two server round-trips per action and a brief loading-spinner flicker.Warning
Behavior change: the connector's overflow-eviction heuristic (
active range * pageSize > 500) no longer fires, because the sharedpendingRequestsonly holds in-flight pages instead of growing with every loaded page. Loaded pages stay cached for the lifetime of the active range; the server still drops items outside that range, which the connector now propagates tofilteredItemsas placeholders so the virtualizer re-fetches on scroll-back. Memory grows with how far the user scrolls (~50 items per page), matching Grid's behavior.Tests:
combo-box-connector.test.ts: newpending requestsdescribe block (4 tests) covering the shared map, the post-debounce callback, and the stale-callback replacement on filter change.shared.tsextended with the matching connector-side typings.LazyLoadingIT.openPopup_setDisabled_removeDisabledAttribute_noNewDataLoaded: expected loaded count updated from 50 to 100. Initial open now prefetches one extra page beyond the viewport.LazyLoadingIT.setComponentRenderer_scrollDown_itemsRendered: minor formatting changes; the scroll-up step still verifies the new renderer is applied at the top after server-side eviction + re-fetch.ItemCountUnknownComboBoxIT:RangeLogindices and a couple of ranges updated to match the new single-request-per-scroll fetch sequence; one assertion split to reflect that revisiting a previously-visited page no longer re-fetches it when it's still inside the active range.AbstractItemCountComboBoxIT.verifyFetchForUndefinedItemCountCallback: wraps the log-element lookup in atry/catchso a missing log produces a clear failure message instead ofNoSuchElementException.🤖 Generated with Claude Code