-
Notifications
You must be signed in to change notification settings - Fork 92
Revert "refactor: avoid unnecessary re-renders on virtualizer size change (#5650)" #7205
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4cac75a
d815ad1
81477cc
65222e9
e758e3f
e094f2d
2d01e7e
cd66b8e
2f5cf29
4acc605
d1b48a3
8de7147
32248b6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,7 +9,7 @@ describe('unlimited size', () => { | |
|
|
||
| beforeEach(() => { | ||
| scrollTarget = fixtureSync(` | ||
| <div style="height: 100px;"> | ||
| <div style="height: 250px;"> | ||
| <div></div> | ||
| </div> | ||
| `); | ||
|
|
@@ -22,6 +22,13 @@ describe('unlimited size', () => { | |
| el.index = index; | ||
| el.id = `item-${index}`; | ||
| el.textContent = el.id; | ||
| el.style.right = '0'; | ||
| el.style.left = '0'; | ||
| el.style.display = 'flex'; | ||
| el.style.alignItems = 'center'; | ||
| el.style.background = index % 2 === 0 ? '#e7e7e7' : '#d0d0d0'; | ||
| el.style.height = '30px'; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note: Setting a fixed height prevents test failures related to differences in font rendering across browsers. |
||
| el.style.padding = '0 10px'; | ||
| }, | ||
| scrollTarget, | ||
| scrollContainer, | ||
|
|
@@ -30,6 +37,10 @@ describe('unlimited size', () => { | |
| virtualizer.size = 1000000; | ||
| }); | ||
|
|
||
| function getLastRenderedIndex() { | ||
| return [...elementsContainer.children].reduce((max, el) => Math.max(max, el.index), 0); | ||
| } | ||
|
|
||
| it('should scroll to a large index', () => { | ||
| const index = Math.floor(virtualizer.size / 2); | ||
| virtualizer.scrollToIndex(index); | ||
|
|
@@ -202,4 +213,75 @@ describe('unlimited size', () => { | |
| Math.ceil(itemRect.bottom), | ||
| ); | ||
| }); | ||
|
|
||
| it('should add more items on size increase', () => { | ||
| const index = virtualizer.size - 1; | ||
| virtualizer.scrollToIndex(index); | ||
| expect(getLastRenderedIndex()).to.equal(index); | ||
|
|
||
| virtualizer.size += 2000; | ||
| expect(getLastRenderedIndex()).to.be.above(index); | ||
| }); | ||
|
|
||
| it('should remove exceeding items on size decrease', () => { | ||
| const index = virtualizer.size - 1; | ||
| virtualizer.scrollToIndex(index); | ||
| expect(getLastRenderedIndex()).to.equal(index); | ||
|
|
||
| virtualizer.size -= 1000; | ||
| expect(getLastRenderedIndex()).to.be.below(index); | ||
| }); | ||
|
|
||
| it('should set scroll to end when size decrease affects a visible index', async () => { | ||
| virtualizer.scrollToIndex(virtualizer.size - 1000); | ||
| virtualizer.size = virtualizer.firstVisibleIndex - 20; | ||
| await oneEvent(scrollTarget, 'scroll'); | ||
| const lastItem = elementsContainer.querySelector(`#item-${virtualizer.size - 1}`); | ||
| expect(lastItem.getBoundingClientRect().bottom).to.be.closeTo(scrollTarget.getBoundingClientRect().bottom, 1); | ||
| }); | ||
|
|
||
| it('should preserve scroll position when size decrease affects a buffered index', async () => { | ||
| // Make sure there are at least 2 buffered items at the end. | ||
| expect(getLastRenderedIndex() - virtualizer.lastVisibleIndex).to.be.greaterThanOrEqual(2); | ||
|
|
||
| // Scroll to an index and add an additional scroll offset. | ||
| const index = virtualizer.size - 1000; | ||
| virtualizer.scrollToIndex(index); | ||
| scrollTarget.scrollTop += 10; | ||
|
|
||
| // Decrease the size so that the last buffered index exceeds the new size bounds. | ||
| virtualizer.size = virtualizer.lastVisibleIndex + 1; | ||
| await oneEvent(scrollTarget, 'scroll'); | ||
|
|
||
| const item = elementsContainer.querySelector(`#item-${index}`); | ||
| expect(item.getBoundingClientRect().top).to.be.closeTo(scrollTarget.getBoundingClientRect().top - 10, 1); | ||
| }); | ||
|
|
||
| // FIXME: Fails due to a scroll offset reset caused by _adjustVirtualIndexOffset on scroll event. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a related issue to fix this?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good question. My plan was to report it after the merge because it's only reproducible with the scroll restoration logic. |
||
| it.skip('should preserve scroll position when size decrease does not affect any rendered indexes', async () => { | ||
| // Scroll to an index and add an additional scroll offset. | ||
| const index = virtualizer.size - 2000; | ||
| virtualizer.scrollToIndex(index); | ||
| scrollTarget.scrollTop += 10; | ||
|
|
||
| // Decrease the size so that no rendered indexes are affected. | ||
| virtualizer.size -= 1000; | ||
| await oneEvent(scrollTarget, 'scroll'); | ||
|
|
||
| const item = elementsContainer.querySelector(`#item-${index}`); | ||
| expect(item.getBoundingClientRect().top).to.be.closeTo(scrollTarget.getBoundingClientRect().top - 10, 1); | ||
| }); | ||
|
|
||
| // FIXME: Fails due to a scroll offset reset caused by _adjustVirtualIndexOffset on scroll event. | ||
| it.skip('should preserve scroll position on size increase', async () => { | ||
| const index = virtualizer.size - 2000; | ||
| virtualizer.scrollToIndex(index); | ||
| scrollTarget.scrollTop += 10; | ||
|
|
||
| virtualizer.size += 1000; | ||
| await oneEvent(scrollTarget, 'scroll'); | ||
|
|
||
| const item = elementsContainer.querySelector(`#item-${index}`); | ||
| expect(item.getBoundingClientRect().top).to.be.closeTo(scrollTarget.getBoundingClientRect().top - 10, 1); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,7 +15,7 @@ describe('virtualizer', () => { | |
| } | ||
|
|
||
| scrollTarget = fixtureSync(` | ||
| <div style="height: 100px;"> | ||
| <div style="height: 250px;"> | ||
| <div></div> | ||
| </div> | ||
| `); | ||
|
|
@@ -30,6 +30,13 @@ describe('virtualizer', () => { | |
| el.index = index; | ||
| el.id = `item-${index}`; | ||
| el.textContent = el.id; | ||
| el.style.right = '0'; | ||
| el.style.left = '0'; | ||
| el.style.display = 'flex'; | ||
| el.style.alignItems = 'center'; | ||
| el.style.background = index % 2 === 0 ? '#e7e7e7' : '#d0d0d0'; | ||
| el.style.height = '30px'; | ||
| el.style.padding = '0 10px'; | ||
| }), | ||
| scrollTarget, | ||
| scrollContainer, | ||
|
|
@@ -40,6 +47,10 @@ describe('virtualizer', () => { | |
|
|
||
| beforeEach(() => init({})); | ||
|
|
||
| function getLastRenderedIndex() { | ||
| return [...elementsContainer.children].reduce((max, el) => Math.max(max, el.index), 0); | ||
| } | ||
|
|
||
| it('should have the first item at the top', () => { | ||
| const item = elementsContainer.querySelector('#item-0'); | ||
| expect(item.getBoundingClientRect().top).to.equal(scrollTarget.getBoundingClientRect().top); | ||
|
|
@@ -174,7 +185,7 @@ describe('virtualizer', () => { | |
| expect(item.getBoundingClientRect().top).to.equal(scrollTarget.getBoundingClientRect().top); | ||
| }); | ||
|
|
||
| it('should restore scroll position on size change', () => { | ||
| it('should preserve scroll position on size increase', () => { | ||
| // Scroll to item 50 and an additional 10 pixels | ||
| virtualizer.scrollToIndex(50); | ||
| scrollTarget.scrollTop += 10; | ||
|
|
@@ -184,40 +195,43 @@ describe('virtualizer', () => { | |
| expect(item.getBoundingClientRect().top).to.equal(scrollTarget.getBoundingClientRect().top - 10); | ||
| }); | ||
|
|
||
| it('should not request item updates on size increase', () => { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note: After this PR is merged, I'll create an issue for further research on how to prevent item updates on size change without sacrificing the scroll restoration. |
||
| const updateElement = sinon.spy((el, index) => { | ||
| el.textContent = `item-${index}`; | ||
| }); | ||
| init({ size: 100, updateElement }); | ||
|
|
||
| // Scroll halfway down the list | ||
| updateElement.resetHistory(); | ||
| it('should set scroll to end when size decrease affects a visible index', async () => { | ||
| virtualizer.scrollToIndex(50); | ||
| virtualizer.size = virtualizer.firstVisibleIndex - 20; | ||
| await oneEvent(scrollTarget, 'scroll'); | ||
| const lastItem = elementsContainer.querySelector(`#item-${virtualizer.size - 1}`); | ||
| expect(lastItem.getBoundingClientRect().bottom).to.be.closeTo(scrollTarget.getBoundingClientRect().bottom, 1); | ||
| }); | ||
|
|
||
| // Increase the size so it shouldn't affect the current viewport items | ||
| updateElement.resetHistory(); | ||
| virtualizer.size = 200; | ||
| it('should preserve scroll position when size decrease affects a buffered index', async () => { | ||
| // Make sure there are at least 2 buffered items at the end. | ||
| expect(getLastRenderedIndex() - virtualizer.lastVisibleIndex).to.be.greaterThanOrEqual(2); | ||
|
|
||
| expect(updateElement.called).to.be.false; | ||
| }); | ||
| // Scroll to an index and add an additional scroll offset. | ||
| const index = 50; | ||
| virtualizer.scrollToIndex(index); | ||
| scrollTarget.scrollTop += 10; | ||
|
|
||
| it('should not request item updates on size increase when scrolled', async () => { | ||
| const updateElement = sinon.spy((el, index) => { | ||
| el.textContent = `item-${index}`; | ||
| }); | ||
| init({ size: 100, updateElement }); | ||
| // Decrease the size so that the last buffered index exceeds the new size bounds. | ||
| virtualizer.size = virtualizer.lastVisibleIndex + 1; | ||
| await oneEvent(scrollTarget, 'scroll'); | ||
|
|
||
| // Scroll halfway down the list | ||
| virtualizer.scrollToIndex(50); | ||
| // Manually scroll down a bit | ||
| scrollTarget.scrollTop += 100; | ||
| await nextFrame(); | ||
| const item = elementsContainer.querySelector(`#item-${index}`); | ||
| expect(item.getBoundingClientRect().top).to.be.closeTo(scrollTarget.getBoundingClientRect().top - 10, 1); | ||
| }); | ||
|
|
||
| // Increase the size so it shouldn't affect the current viewport items | ||
| updateElement.resetHistory(); | ||
| virtualizer.size = 200; | ||
| it('should preserve scroll position when size decrease does not affect any rendered indexes', async () => { | ||
| // Scroll to an index and add an additional scroll offset. | ||
| const index = 50; | ||
| virtualizer.scrollToIndex(index); | ||
| scrollTarget.scrollTop += 10; | ||
|
|
||
| // Decrease the size so that no rendered indexes are affected. | ||
| virtualizer.size = 80; | ||
| await oneEvent(scrollTarget, 'scroll'); | ||
|
|
||
| expect(updateElement.called).to.be.false; | ||
| const item = elementsContainer.querySelector(`#item-${index}`); | ||
| expect(item.getBoundingClientRect().top).to.be.closeTo(scrollTarget.getBoundingClientRect().top - 10, 1); | ||
| }); | ||
|
|
||
| it('should request a different set of items on size decrease', () => { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: Most of the styles are added to make debugging easier.