Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@
"@tanstack/react-query": "5.96.0",
"@tanstack/react-query-devtools": "5.96.0",
"@tanstack/react-query-persist-client": "5.96.0",
"@tanstack/react-virtual": "^3.13.6",
"@tanstack/react-virtual": "3.14.6",
"@types/gtag.js": "^0.0.12",
"@types/history": "^3.2.5",
"@types/invariant": "^2.2.35",
Expand Down
18 changes: 9 additions & 9 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,9 @@ import {
MOCK_EXCEPTION_ENTRY,
} from 'sentry/components/events/breadcrumbs/testUtils';
import {EntryType} from 'sentry/types/event';
import {mockElementSize} from 'sentry/utils/fixtures/virtualization';

// Needed to mock useVirtualizer lists.
jest.spyOn(window.Element.prototype, 'getBoundingClientRect').mockImplementation(() => ({
x: 0,
y: 0,
width: 0,
height: 30,
left: 0,
top: 0,
right: 0,
bottom: 0,
toJSON: jest.fn(),
}));
mockElementSize({width: 0, height: 30});

describe('BreadcrumbsDataSection', () => {
it('renders a summary of breadcrumbs with a button to view them all', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,10 @@ import {
MOCK_BREADCRUMBS,
MOCK_DATA_SECTION_PROPS,
} from 'sentry/components/events/breadcrumbs/testUtils';
import {mockElementSize} from 'sentry/utils/fixtures/virtualization';

async function renderBreadcrumbDrawer() {
// Needed to mock useVirtualizer lists.
jest
.spyOn(window.Element.prototype, 'getBoundingClientRect')
.mockImplementation(() => ({
x: 0,
y: 0,
width: 0,
height: 30,
left: 0,
top: 0,
right: 0,
bottom: 0,
toJSON: jest.fn(),
}));
mockElementSize({width: 0, height: 30});
render(<BreadcrumbsDataSection {...MOCK_DATA_SECTION_PROPS} />);
await userEvent.click(screen.getByRole('button', {name: 'View 2 more'}));
return screen.getByRole('complementary', {name: 'breadcrumb drawer'});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,11 @@ import {
MOCK_DATA_SECTION_PROPS_ONE_EXTRA_FLAG,
MOCK_FLAGS,
} from 'sentry/components/events/featureFlags/testUtils';
import {mockElementSize} from 'sentry/utils/fixtures/virtualization';
import {GroupDataContextProvider} from 'sentry/views/issueDetails/groupDataContext';

async function renderFlagDrawer() {
// Needed to mock useVirtualizer lists.
jest
.spyOn(window.Element.prototype, 'getBoundingClientRect')
.mockImplementation(() => ({
x: 0,
y: 0,
width: 0,
height: 30,
left: 0,
top: 0,
right: 0,
bottom: 0,
toJSON: jest.fn(),
}));
mockElementSize({width: 0, height: 30});
render(
<GroupDataContextProvider
group={MOCK_DATA_SECTION_PROPS_ONE_EXTRA_FLAG.group}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,10 @@ import {
NO_FLAG_CONTEXT_SECTION_PROPS,
NO_FLAG_CONTEXT_WITH_FLAGS_SECTION_PROPS,
} from 'sentry/components/events/featureFlags/testUtils';
import {mockElementSize} from 'sentry/utils/fixtures/virtualization';
import {GroupDataContextProvider} from 'sentry/views/issueDetails/groupDataContext';

// Needed to mock useVirtualizer lists.
jest.spyOn(window.Element.prototype, 'getBoundingClientRect').mockImplementation(() => ({
x: 0,
y: 0,
width: 0,
height: 30,
left: 0,
top: 0,
right: 0,
bottom: 0,
toJSON: jest.fn(),
}));
mockElementSize({width: 0, height: 30});

describe('EventFeatureFlagList', () => {
beforeEach(() => {
Expand Down
33 changes: 23 additions & 10 deletions static/app/utils/fixtures/virtualization.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
interface MockElementSizeOptions {
height?: number;
width?: number;
}

/**
* Tanstack Virtual renders zero items in the default zero-sized JSDom environment.
* This forces all elements to have a non-zero size to render at least a few rows.
* https://github.com/TanStack/virtual/issues/641
* TanStack Virtual renders zero items in the default zero-sized JSDOM environment.
* Mock the element dimensions read by the virtualizer so it can calculate a range.
* The bounding rect remains mocked for components that perform their own layout reads.
*/
export function mockGetBoundingClientRect() {
Element.prototype.getBoundingClientRect = jest.fn(() => ({
width: 500,
height: 500,
export function mockElementSize({
width = 500,
height = 500,
}: MockElementSizeOptions = {}) {
// TanStack Virtual uses offset dimensions to measure its viewport and items.
jest.spyOn(HTMLElement.prototype, 'offsetWidth', 'get').mockReturnValue(width);
jest.spyOn(HTMLElement.prototype, 'offsetHeight', 'get').mockReturnValue(height);

// Keep component layout reads consistent with the dimensions seen by the virtualizer.
jest.spyOn(Element.prototype, 'getBoundingClientRect').mockReturnValue({
width,
height,
top: 0,
left: 0,
bottom: 500,
right: 500,
bottom: height,
right: width,
x: 0,
y: 0,
toJSON: () => {},
}));
});
}
6 changes: 4 additions & 2 deletions static/app/views/explore/logs/content.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ import {ProjectsStore} from 'sentry/stores/projectsStore';
import {TeamStore} from 'sentry/stores/teamStore';
import type {Organization} from 'sentry/types/organization';
import type {Project} from 'sentry/types/project';
import {mockGetBoundingClientRect} from 'sentry/utils/fixtures/virtualization';
import {mockElementSize} from 'sentry/utils/fixtures/virtualization';
import {LOGS_AUTO_REFRESH_KEY} from 'sentry/views/explore/contexts/logs/logsAutoRefreshContext';
import type {OurLogsResponseItem} from 'sentry/views/explore/logs/types';

import LogsPage from './content';

beforeEach(mockGetBoundingClientRect);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sus

@scttcper scttcper Jul 14, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just calls it with defaults, made it more explicit

beforeEach(() => {
mockElementSize();
});

describe('LogsPage', () => {
let organization: Organization;
Expand Down
6 changes: 4 additions & 2 deletions static/app/views/explore/logs/logsTab.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrar

import type {DatePageFilterProps} from 'sentry/components/pageFilters/date/datePageFilter';
import {LogsAnalyticsPageSource} from 'sentry/utils/analytics/logsAnalyticsEvent';
import {mockGetBoundingClientRect} from 'sentry/utils/fixtures/virtualization';
import {mockElementSize} from 'sentry/utils/fixtures/virtualization';
import {LOGS_AUTO_REFRESH_KEY} from 'sentry/views/explore/contexts/logs/logsAutoRefreshContext';
import {LogsPageDataProvider} from 'sentry/views/explore/contexts/logs/logsPageData';
import {
Expand Down Expand Up @@ -37,7 +37,9 @@ const datePageFilterProps: DatePageFilterProps = {
}),
};

beforeEach(mockGetBoundingClientRect);
beforeEach(() => {
mockElementSize();
});

describe('LogsTabContent', () => {
const {organization, project, setupPageFilters} = initializeLogsTest();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,12 +303,17 @@ export function LogsInfiniteTable({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchString, localOnlyItemFilters?.filterText]);

const getItemKey = useCallback(
(index: number) => data?.[index]?.[OurLogKnownFieldKey.ID] ?? index,
[data]
);

const virtualizer = useVirtualizer<HTMLElement, Element>({
count: data?.length ?? 0,
estimateSize,
overscan: 35,
getScrollElement: () => tableBodyRef?.current,
getItemKey: (index: number) => data?.[index]?.[OurLogKnownFieldKey.ID] ?? index,
getItemKey,
});

useLayoutEffect(() => {
Expand Down
6 changes: 5 additions & 1 deletion static/app/views/explore/replays/detail/console/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,17 @@ export function Console() {
const clearSearchTerm = () => setSearchTerm('');

const scrollContainerRef = useRef<HTMLDivElement | null>(null);
const getItemKey = useCallback(
(index: number) => getVirtualItemKey(items[index], index),
[items]
);

const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => scrollContainerRef.current,
estimateSize: () => ESTIMATED_ROW_HEIGHT,
overscan: 12,
getItemKey: index => getVirtualItemKey(items[index], index),
getItemKey,
useAnimationFrameWithResizeObserver: true,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
within,
} from 'sentry-test/reactTestingLibrary';

import {mockGetBoundingClientRect} from 'sentry/utils/fixtures/virtualization';
import {mockElementSize} from 'sentry/utils/fixtures/virtualization';
import {LOGS_QUERY_KEY} from 'sentry/views/explore/contexts/logs/logsPageParams';
import {OurLogKnownFieldKey} from 'sentry/views/explore/logs/types';
import {
Expand All @@ -26,7 +26,9 @@ function Component({traceSlug}: {traceSlug: string}) {
);
}

beforeEach(mockGetBoundingClientRect);
beforeEach(() => {
mockElementSize();
});

describe('TraceViewLogsSection', () => {
beforeEach(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {render, screen} from 'sentry-test/reactTestingLibrary';

import {mockElementSize} from 'sentry/utils/fixtures/virtualization';
import type {
SidebarItem,
SnapshotDiffPair,
Expand Down Expand Up @@ -69,17 +70,7 @@ const erroredItem: SidebarItem = {
describe('SnapshotListView', () => {
beforeEach(() => {
jest.clearAllMocks();
jest.spyOn(Element.prototype, 'getBoundingClientRect').mockReturnValue({
width: 900,
height: 600,
top: 0,
left: 0,
bottom: 600,
right: 900,
x: 0,
y: 0,
toJSON: jest.fn(),
});
mockElementSize({width: 900, height: 600});
// jsdom returns empty padding strings; parseFloat('') is NaN, which would
// propagate into the virtualizer's height math. Force numeric padding.
jest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,12 +234,13 @@ export const SnapshotListView = memo(function SnapshotListView({
}, []);

const groups = useMemo(() => buildGroups(items, contentWidth), [items, contentWidth]);
const getItemKey = useCallback((index: number) => groups[index]!.id, [groups]);

const virtualizer = useVirtualizer({
count: groups.length,
getScrollElement: () => scrollRef.current,
estimateSize: i => groups[i]!.estimatedHeight,
getItemKey: i => groups[i]!.id,
getItemKey,
overscan: 5,
scrollPaddingEnd: 8,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,14 @@
import {render, screen} from 'sentry-test/reactTestingLibrary';

import {mockElementSize} from 'sentry/utils/fixtures/virtualization';
import {DiffStatus} from 'sentry/views/preprod/types/snapshotTypes';

import {SnapshotSidebarContent, type SidebarSection} from './snapshotSidebarContent';

const noop = () => {};

beforeEach(() => {
jest.spyOn(Element.prototype, 'getBoundingClientRect').mockReturnValue({
width: 350,
height: 600,
top: 0,
left: 0,
bottom: 600,
right: 350,
x: 0,
y: 0,
toJSON: jest.fn(),
});
mockElementSize({width: 350, height: 600});
});

const statusCounts: Record<DiffStatus, number> = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,12 @@ import {ProjectFixture} from 'sentry-fixture/project';

import {render, screen} from 'sentry-test/reactTestingLibrary';

import {mockElementSize} from 'sentry/utils/fixtures/virtualization';
import type {ProjectionSamplePeriod} from 'sentry/views/settings/dynamicSampling/utils/useProjectSampleCounts';

import {ProjectsTable} from './projectsTable';

jest.spyOn(window.Element.prototype, 'getBoundingClientRect').mockReturnValue({
height: 400,
width: 500,
x: 0,
y: 0,
top: 0,
left: 0,
right: 500,
bottom: 400,
toJSON: jest.fn(),
});
mockElementSize({width: 500, height: 400});

describe('ProjectsTable', () => {
const organization = OrganizationFixture({
Expand Down
Loading
Loading