[MM-64618] ResizeObserver callback fires after component unmount during channel switching (#32668)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
d2188ce1dd
Коммит
0d9c4810f4
@@ -188,11 +188,11 @@
|
||||
"build": "cross-env NODE_ENV=production webpack",
|
||||
"run": "webpack --progress --watch",
|
||||
"dev-server": "webpack serve --mode development",
|
||||
"test": "cross-env TZ=Etc/UTC jest",
|
||||
"test:watch": "cross-env TZ=Etc/UTC jest --watch",
|
||||
"test:updatesnapshot": "cross-env TZ=Etc/UTC jest --updateSnapshot",
|
||||
"test:debug": "cross-env TZ=Etc/UTC jest --forceExit --detectOpenHandles --verbose",
|
||||
"test-ci": "cross-env TZ=Etc/UTC jest --ci --maxWorkers=100% --coverage",
|
||||
"test": "cross-env TZ=Etc/UTC LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 jest",
|
||||
"test:watch": "cross-env TZ=Etc/UTC LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 jest --watch",
|
||||
"test:updatesnapshot": "cross-env TZ=Etc/UTC LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 jest --updateSnapshot",
|
||||
"test:debug": "cross-env TZ=Etc/UTC LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 jest --forceExit --detectOpenHandles --verbose",
|
||||
"test-ci": "cross-env TZ=Etc/UTC LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 jest --ci --maxWorkers=100% --coverage",
|
||||
"clean": "rm -rf dist node_modules .eslintcache .stylelintcache tsconfig.tsbuildinfo",
|
||||
"stats": "cross-env NODE_ENV=production webpack --profile --json > webpack_stats.json",
|
||||
"mmjstool": "mmjstool",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import memoizeOne from 'memoize-one';
|
||||
import {createElement, PureComponent} from 'react';
|
||||
|
||||
import ListItem from './item_row_shared';
|
||||
import ListItem from './list_item';
|
||||
|
||||
const atBottomMargin = 10;
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {render, screen} from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
jest.mock('./list_item_size_observer', () => {
|
||||
const mockObserve = jest.fn(() => jest.fn());
|
||||
const mockUnobserve = jest.fn();
|
||||
|
||||
return {
|
||||
ListItemSizeObserver: {
|
||||
getInstance: jest.fn(() => ({
|
||||
observe: mockObserve,
|
||||
unobserve: mockUnobserve,
|
||||
})),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('lodash/debounce', () => {
|
||||
return jest.fn((fn) => {
|
||||
const debouncedFn = (...args: any[]) => fn(...args);
|
||||
debouncedFn.cancel = jest.fn();
|
||||
debouncedFn.flush = jest.fn();
|
||||
return debouncedFn;
|
||||
});
|
||||
});
|
||||
|
||||
import ListItem from './list_item';
|
||||
|
||||
describe('ListItem', () => {
|
||||
const defaultProps = {
|
||||
item: <div data-testid='test-item'>{'Test Item Content'}</div>,
|
||||
itemId: 'test-item-1',
|
||||
index: 0,
|
||||
height: 100,
|
||||
width: 300,
|
||||
onHeightChange: jest.fn(),
|
||||
onUnmount: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('renders the item content correctly', () => {
|
||||
render(<ListItem {...defaultProps}/>);
|
||||
|
||||
expect(screen.getByTestId('test-item')).toBeInTheDocument();
|
||||
expect(screen.getByText('Test Item Content')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('applies correct attributes to the wrapper div', () => {
|
||||
render(<ListItem {...defaultProps}/>);
|
||||
|
||||
const wrapper = screen.getByRole('listitem');
|
||||
expect(wrapper).toHaveClass('item_measurer');
|
||||
expect(wrapper).toHaveAttribute('role', 'listitem');
|
||||
});
|
||||
|
||||
test('calls onHeightChange on mount with initial height', () => {
|
||||
const mockOnHeightChange = jest.fn();
|
||||
|
||||
Object.defineProperty(HTMLElement.prototype, 'offsetHeight', {
|
||||
configurable: true,
|
||||
value: 120,
|
||||
});
|
||||
|
||||
render(
|
||||
<ListItem
|
||||
{...defaultProps}
|
||||
onHeightChange={mockOnHeightChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(mockOnHeightChange).toHaveBeenCalledWith('test-item-1', 120, false);
|
||||
});
|
||||
|
||||
test('handles zero offsetHeight gracefully', () => {
|
||||
const mockOnHeightChange = jest.fn();
|
||||
|
||||
Object.defineProperty(HTMLElement.prototype, 'offsetHeight', {
|
||||
configurable: true,
|
||||
value: 0,
|
||||
});
|
||||
|
||||
render(
|
||||
<ListItem
|
||||
{...defaultProps}
|
||||
onHeightChange={mockOnHeightChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(mockOnHeightChange).toHaveBeenCalledWith('test-item-1', 0, false);
|
||||
});
|
||||
});
|
||||
@@ -5,7 +5,7 @@ import debounce from 'lodash/debounce';
|
||||
import type {ReactNode} from 'react';
|
||||
import React, {memo, useEffect, useRef} from 'react';
|
||||
|
||||
import {ListItemSizeObserver} from './item_row_size_observer';
|
||||
import {ListItemSizeObserver} from './list_item_size_observer';
|
||||
|
||||
const RESIZE_DEBOUNCE_TIME = 200; // in ms
|
||||
|
||||
@@ -50,9 +50,15 @@ const ListItem = (props: Props) => {
|
||||
// This effects adds the observer which calls height change callback debounced
|
||||
useEffect(() => {
|
||||
const debouncedOnHeightChange = debounce((changedHeight: number) => {
|
||||
// Check if component is still mounted as it may have been
|
||||
// unmounted by the time the debounced function is called
|
||||
if (!rowRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If width of container has changed then scroll bar position will be out of sync
|
||||
// so we need to force a scroll correction
|
||||
const forceScrollCorrection = rowRef.current?.offsetWidth !== widthRef.current;
|
||||
const forceScrollCorrection = rowRef.current.offsetWidth !== widthRef.current;
|
||||
|
||||
heightRef.current = changedHeight;
|
||||
|
||||
@@ -76,11 +82,10 @@ const ListItem = (props: Props) => {
|
||||
cleanupSizeObserver = listItemSizeObserver.observe(props.itemId, rowRef.current, itemRowSizeObserverCallback);
|
||||
}
|
||||
|
||||
// We remove the observer here from a row
|
||||
return () => {
|
||||
if (cleanupSizeObserver) {
|
||||
cleanupSizeObserver();
|
||||
}
|
||||
// We remove the observer here from a row
|
||||
cleanupSizeObserver?.();
|
||||
debouncedOnHeightChange?.cancel();
|
||||
props.onUnmount(props.itemId, indexRef.current);
|
||||
};
|
||||
}, [props.itemId]);
|
||||
Ссылка в новой задаче
Block a user