Code change:
<ins> /*--------------------------------------------------------------------------------------------- *  Copyright (c) Microsoft Corporation. All rights reserved. *  Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { range } from 'vs/base/common/arrays'; import { timeout } from 'vs/base/common/async'; suite('ListWidget', function () { test('Page up and down', async function () { const element = document.createElement('div'); element.style.height = '200px'; element.style.width = '200px'; const delegate: IListVirtualDelegate<number> = { getHeight() { return 20; }, getTemplateId() { return 'template'; } }; let templatesCount = 0; const renderer: IListRenderer<number, void> = { templateId: 'template', renderTemplate() { templatesCount++; }, renderElement() { }, disposeTemplate() { templatesCount--; } }; const listWidget = new List<number>('test', element, delegate, [renderer]); listWidget.layout(200); assert.strictEqual(templatesCount, 0, 'no templates have been allocated'); listWidget.splice(0, 0, range(100)); listWidget.focusFirst(); listWidget.focusNextPage(); assert.strictEqual(listWidget.getFocus()[0], 9, 'first page down moves focus to element at bottom'); // scroll to next page is async listWidget.focusNextPage(); await timeout(0); assert.strictEqual(listWidget.getFocus()[0], 19, 'page down to next page'); listWidget.focusPreviousPage(); assert.strictEqual(listWidget.getFocus()[0], 10, 'first page up moves focus to element at top'); // scroll to previous page is async listWidget.focusPreviousPage(); await timeout(0); assert.strictEqual(listWidget.getFocus()[0], 0, 'page down to previous page'); listWidget.dispose(); }); test('Page up and down with item taller than viewport #149502', async function () { const element = document.createElement('div'); element.style.height = '200px'; element.style.width = '200px'; const delegate: IListVirtualDelegate<number> = { getHeight() { return 200; }, getTemplateId() { return 'template'; } }; let templatesCount = 0; const renderer: IListRenderer<number, void> = { templateId: 'template', renderTemplate() { templatesCount++; }, renderElement() { }, disposeTemplate() { templatesCount--; } }; const listWidget = new List<number>('test', element, delegate, [renderer]); listWidget.layout(200); assert.strictEqual(templatesCount, 0, 'no templates have been allocated'); listWidget.splice(0, 0, range(100)); listWidget.focusFirst(); assert.strictEqual(listWidget.getFocus()[0], 0, 'initial focus is first element'); // scroll to next page is async listWidget.focusNextPage(); await timeout(0); assert.strictEqual(listWidget.getFocus()[0], 1, 'page down to next page'); // scroll to previous page is async listWidget.focusPreviousPage(); await timeout(0); assert.strictEqual(listWidget.getFocus()[0], 0, 'page up to next page'); listWidget.dispose(); }); }); </ins>

Related discussion:
text: Open Select the top cell and page down a few times Focus hits a cell that is longer than the viewport, which is maybe important, then the next page down moves to a higher cell
When a list item is larger than the viewport, and it's also the only element in the viewport, pressing page down will reveal to the previous element, this is because we did a here (the catch is, the last page element is also the first page element)