MM-63648 - markdown images sometimes do not show the more button (#30716)
* MM-63648 - markdown images sometimes do not show the more button * migrate test to testing-library and remove unnecesary props --------- Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
@@ -1,231 +1,19 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`components/post_view/ShowMore should match snapshot 1`] = `
|
exports[`components/post_view/ShowMore should match snapshot 1`] = `
|
||||||
<div
|
<div>
|
||||||
className="post-message post-message--collapsed"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className="post-message__text-container"
|
class="post-message post-message--collapsed"
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"maxHeight": 200,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<p>
|
|
||||||
text
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`components/post_view/ShowMore should match snapshot, PostAttachment on collapsed view 1`] = `
|
|
||||||
<div
|
|
||||||
className="post-message post-message--collapsed post-message--overflow"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="post-message__text-container"
|
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"maxHeight": 200,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="post-collapse"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="post-attachment-collapse__show-more"
|
class="post-message__text-container"
|
||||||
|
style="max-height: 200px;"
|
||||||
>
|
>
|
||||||
<div
|
<div>
|
||||||
className="post-collapse__show-more-line"
|
<p>
|
||||||
/>
|
text
|
||||||
<button
|
</p>
|
||||||
className="post-collapse__show-more-button"
|
</div>
|
||||||
id="showMoreButton"
|
|
||||||
onClick={[Function]}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="fa fa-angle-down"
|
|
||||||
/>
|
|
||||||
<MemoizedFormattedMessage
|
|
||||||
defaultMessage="Show more"
|
|
||||||
id="post_info.message.show_more"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<div
|
|
||||||
className="post-collapse__show-more-line"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`components/post_view/ShowMore should match snapshot, PostAttachment on expanded view 1`] = `
|
|
||||||
<div
|
|
||||||
className="post-message post-message--expanded post-message--overflow"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="post-message__text-container"
|
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"maxHeight": undefined,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="post-collapse"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="post-attachment-collapse__show-more"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="post-collapse__show-more-line"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
className="post-collapse__show-more-button"
|
|
||||||
id="showMoreButton"
|
|
||||||
onClick={[Function]}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="fa fa-angle-up"
|
|
||||||
/>
|
|
||||||
<MemoizedFormattedMessage
|
|
||||||
defaultMessage="Show less"
|
|
||||||
id="post_info.message.show_less"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<div
|
|
||||||
className="post-collapse__show-more-line"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`components/post_view/ShowMore should match snapshot, PostMessageView on collapsed view 1`] = `
|
|
||||||
<div
|
|
||||||
className="post-message post-message--collapsed post-message--overflow"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="post-message__text-container"
|
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"maxHeight": 200,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="post-collapse"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="post-collapse__show-more"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="post-collapse__show-more-line"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
className="post-collapse__show-more-button"
|
|
||||||
id="showMoreButton"
|
|
||||||
onClick={[Function]}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="fa fa-angle-down"
|
|
||||||
/>
|
|
||||||
<MemoizedFormattedMessage
|
|
||||||
defaultMessage="Show more"
|
|
||||||
id="post_info.message.show_more"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<div
|
|
||||||
className="post-collapse__show-more-line"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`components/post_view/ShowMore should match snapshot, PostMessageView on expanded view 1`] = `
|
|
||||||
<div
|
|
||||||
className="post-message post-message--expanded post-message--overflow"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="post-message__text-container"
|
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"maxHeight": undefined,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="post-collapse"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="post-collapse__show-more"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="post-collapse__show-more-line"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
className="post-collapse__show-more-button"
|
|
||||||
id="showMoreButton"
|
|
||||||
onClick={[Function]}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="fa fa-angle-up"
|
|
||||||
/>
|
|
||||||
<MemoizedFormattedMessage
|
|
||||||
defaultMessage="Show less"
|
|
||||||
id="post_info.message.show_less"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<div
|
|
||||||
className="post-collapse__show-more-line"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`components/post_view/ShowMore should match snapshot, PostMessageView on expanded view with compactDisplay 1`] = `
|
|
||||||
<div
|
|
||||||
className="post-message post-message--expanded post-message--overflow"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="post-message__text-container"
|
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"maxHeight": undefined,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="post-collapse"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="post-collapse__show-more"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="post-collapse__show-more-line"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
className="post-collapse__show-more-button"
|
|
||||||
id="showMoreButton"
|
|
||||||
onClick={[Function]}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="fa fa-angle-up"
|
|
||||||
/>
|
|
||||||
<MemoizedFormattedMessage
|
|
||||||
defaultMessage="Show less"
|
|
||||||
id="post_info.message.show_less"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<div
|
|
||||||
className="post-collapse__show-more-line"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,102 +1,397 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {shallow} from 'enzyme';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import ShowMore from 'components/post_view/show_more/show_more';
|
import ShowMore from 'components/post_view/show_more/show_more';
|
||||||
|
|
||||||
|
import {renderWithContext, screen, fireEvent, act} from 'tests/react_testing_utils';
|
||||||
|
|
||||||
describe('components/post_view/ShowMore', () => {
|
describe('components/post_view/ShowMore', () => {
|
||||||
const children = (<div><p>{'text'}</p></div>);
|
const children = (<div><p>{'text'}</p></div>);
|
||||||
const baseProps = {
|
const baseProps = {
|
||||||
checkOverflow: 0,
|
checkOverflow: 0,
|
||||||
isAttachmentText: false,
|
isAttachmentText: false,
|
||||||
isRHSExpanded: false,
|
|
||||||
isRHSOpen: false,
|
|
||||||
maxHeight: 200,
|
maxHeight: 200,
|
||||||
text: 'text',
|
text: 'text',
|
||||||
compactDisplay: false,
|
compactDisplay: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper function to mock the text container's scrollHeight
|
||||||
|
const mockTextContainerScrollHeight = (scrollHeight: number) => {
|
||||||
|
// Mock the scrollHeight property
|
||||||
|
Object.defineProperty(HTMLElement.prototype, 'scrollHeight', {
|
||||||
|
configurable: true,
|
||||||
|
value: scrollHeight,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to restore the original scrollHeight behavior
|
||||||
|
const restoreTextContainerScrollHeight = () => {
|
||||||
|
// Delete the mocked scrollHeight property
|
||||||
|
delete (HTMLElement.prototype as any).scrollHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
restoreTextContainerScrollHeight();
|
||||||
|
});
|
||||||
|
|
||||||
test('should match snapshot', () => {
|
test('should match snapshot', () => {
|
||||||
const wrapper = shallow(<ShowMore {...baseProps}>{children}</ShowMore>);
|
const {container} = renderWithContext(<ShowMore {...baseProps}>{children}</ShowMore>);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should match snapshot, PostMessageView on collapsed view', () => {
|
test('should render collapsed view when content overflows', () => {
|
||||||
const wrapper = shallow(<ShowMore {...baseProps}/>);
|
// Setup fake timers
|
||||||
wrapper.setState({isOverflow: true, isCollapsed: true});
|
jest.useFakeTimers();
|
||||||
expect(wrapper).toMatchSnapshot();
|
try {
|
||||||
|
// Mock scrollHeight to be greater than maxHeight to simulate overflow
|
||||||
|
mockTextContainerScrollHeight(baseProps.maxHeight + 50);
|
||||||
|
|
||||||
|
const {container} = renderWithContext(
|
||||||
|
<ShowMore {...baseProps}>
|
||||||
|
<div style={{height: '300px'}}>{'Tall content that will overflow'}</div>
|
||||||
|
</ShowMore>,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Manually trigger the overflow check
|
||||||
|
act(() => {
|
||||||
|
// Run the requestAnimationFrame callback
|
||||||
|
jest.runOnlyPendingTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify the "Show more" button is rendered
|
||||||
|
const showMoreButton = screen.getByText('Show more');
|
||||||
|
expect(showMoreButton).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Verify the collapsed class is applied
|
||||||
|
expect(container.querySelector('.post-message--collapsed')).toBeInTheDocument();
|
||||||
|
} finally {
|
||||||
|
jest.useRealTimers();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should match snapshot, PostMessageView on expanded view', () => {
|
test('should render expanded view when show more button is clicked', () => {
|
||||||
const wrapper = shallow(<ShowMore {...baseProps}/>);
|
// Setup fake timers
|
||||||
wrapper.setState({isOverflow: true, isCollapsed: false});
|
jest.useFakeTimers();
|
||||||
expect(wrapper).toMatchSnapshot();
|
try {
|
||||||
|
// Mock scrollHeight to be greater than maxHeight to simulate overflow
|
||||||
|
mockTextContainerScrollHeight(baseProps.maxHeight + 50);
|
||||||
|
|
||||||
|
const {container} = renderWithContext(
|
||||||
|
<ShowMore {...baseProps}>
|
||||||
|
<div style={{height: '300px'}}>{'Tall content that will overflow'}</div>
|
||||||
|
</ShowMore>,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Manually trigger the overflow check
|
||||||
|
act(() => {
|
||||||
|
// Run the requestAnimationFrame callback
|
||||||
|
jest.runOnlyPendingTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Find and click the "Show more" button
|
||||||
|
const showMoreButton = screen.getByText('Show more');
|
||||||
|
fireEvent.click(showMoreButton);
|
||||||
|
|
||||||
|
// Verify the "Show less" button is now rendered
|
||||||
|
const showLessButton = screen.getByText('Show less');
|
||||||
|
expect(showLessButton).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Verify the expanded class is applied
|
||||||
|
expect(container.querySelector('.post-message--expanded')).toBeInTheDocument();
|
||||||
|
} finally {
|
||||||
|
jest.useRealTimers();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should match snapshot, PostAttachment on collapsed view', () => {
|
test('should render attachment text in collapsed view', () => {
|
||||||
const wrapper = shallow(
|
// Setup fake timers
|
||||||
<ShowMore
|
jest.useFakeTimers();
|
||||||
{...baseProps}
|
try {
|
||||||
isAttachmentText={true}
|
// Mock scrollHeight to be greater than maxHeight to simulate overflow
|
||||||
/>,
|
mockTextContainerScrollHeight(baseProps.maxHeight + 50);
|
||||||
);
|
|
||||||
wrapper.setState({isOverflow: true, isCollapsed: true});
|
const {container} = renderWithContext(
|
||||||
expect(wrapper).toMatchSnapshot();
|
<ShowMore
|
||||||
|
{...baseProps}
|
||||||
|
isAttachmentText={true}
|
||||||
|
>
|
||||||
|
<div style={{height: '300px'}}>{'Attachment text that will overflow'}</div>
|
||||||
|
</ShowMore>,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Manually trigger the overflow check
|
||||||
|
act(() => {
|
||||||
|
// Run the requestAnimationFrame callback
|
||||||
|
jest.runOnlyPendingTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify the "Show more" button is rendered
|
||||||
|
const showMoreButton = screen.getByText('Show more');
|
||||||
|
expect(showMoreButton).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Verify the attachment-specific class is applied
|
||||||
|
expect(container.querySelector('.post-attachment-collapse__show-more')).toBeInTheDocument();
|
||||||
|
} finally {
|
||||||
|
jest.useRealTimers();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should match snapshot, PostAttachment on expanded view', () => {
|
test('should render with compactDisplay', () => {
|
||||||
const wrapper = shallow(
|
// Setup fake timers
|
||||||
<ShowMore
|
jest.useFakeTimers();
|
||||||
{...baseProps}
|
try {
|
||||||
isAttachmentText={true}
|
// Mock scrollHeight to be greater than maxHeight to simulate overflow
|
||||||
/>,
|
mockTextContainerScrollHeight(baseProps.maxHeight + 50);
|
||||||
);
|
|
||||||
wrapper.setState({isOverflow: true, isCollapsed: false});
|
const {container} = renderWithContext(
|
||||||
expect(wrapper).toMatchSnapshot();
|
<ShowMore
|
||||||
|
{...baseProps}
|
||||||
|
compactDisplay={true}
|
||||||
|
>
|
||||||
|
<div style={{height: '300px'}}>{'Content with compact display'}</div>
|
||||||
|
</ShowMore>,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Manually trigger the overflow check
|
||||||
|
act(() => {
|
||||||
|
// Run the requestAnimationFrame callback
|
||||||
|
jest.runOnlyPendingTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Expand the content
|
||||||
|
const showMoreButton = screen.getByText('Show more');
|
||||||
|
fireEvent.click(showMoreButton);
|
||||||
|
|
||||||
|
// Verify the component renders correctly with compact display
|
||||||
|
expect(container.querySelector('.post-message--expanded')).toBeInTheDocument();
|
||||||
|
} finally {
|
||||||
|
jest.useRealTimers();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should match snapshot, PostMessageView on expanded view with compactDisplay', () => {
|
test('should check overflow only when text or checkOverflow props change', () => {
|
||||||
const wrapper = shallow(
|
// Create a spy for requestAnimationFrame
|
||||||
<ShowMore
|
const originalRAF = window.requestAnimationFrame;
|
||||||
{...baseProps}
|
const rafSpy = jest.fn((cb) => {
|
||||||
compactDisplay={true}
|
cb(0);
|
||||||
/>,
|
return 0;
|
||||||
);
|
});
|
||||||
wrapper.setState({isOverflow: true, isCollapsed: false});
|
window.requestAnimationFrame = rafSpy;
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
|
try {
|
||||||
|
// Initial render with no overflow
|
||||||
|
mockTextContainerScrollHeight(baseProps.maxHeight - 50);
|
||||||
|
const {rerender} = renderWithContext(<ShowMore {...baseProps}>{children}</ShowMore>);
|
||||||
|
|
||||||
|
// Reset the RAF spy count
|
||||||
|
rafSpy.mockClear();
|
||||||
|
|
||||||
|
// Change props that SHOULD trigger overflow check
|
||||||
|
rafSpy.mockClear();
|
||||||
|
rerender(
|
||||||
|
<ShowMore
|
||||||
|
{...baseProps}
|
||||||
|
text={'text change'}
|
||||||
|
>{children}</ShowMore>,
|
||||||
|
);
|
||||||
|
expect(rafSpy).toHaveBeenCalled();
|
||||||
|
|
||||||
|
rafSpy.mockClear();
|
||||||
|
rerender(
|
||||||
|
<ShowMore
|
||||||
|
{...baseProps}
|
||||||
|
text={'text another change'}
|
||||||
|
>{children}</ShowMore>,
|
||||||
|
);
|
||||||
|
expect(rafSpy).toHaveBeenCalled();
|
||||||
|
|
||||||
|
rafSpy.mockClear();
|
||||||
|
rerender(
|
||||||
|
<ShowMore
|
||||||
|
{...baseProps}
|
||||||
|
checkOverflow={1}
|
||||||
|
>{children}</ShowMore>,
|
||||||
|
);
|
||||||
|
expect(rafSpy).toHaveBeenCalled();
|
||||||
|
|
||||||
|
// Same checkOverflow value should not trigger another check
|
||||||
|
rafSpy.mockClear();
|
||||||
|
rerender(
|
||||||
|
<ShowMore
|
||||||
|
{...baseProps}
|
||||||
|
checkOverflow={1}
|
||||||
|
>{children}</ShowMore>,
|
||||||
|
);
|
||||||
|
expect(rafSpy).not.toHaveBeenCalled();
|
||||||
|
} finally {
|
||||||
|
// Restore original requestAnimationFrame
|
||||||
|
window.requestAnimationFrame = originalRAF;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should call checkTextOverflow', () => {
|
describe('ResizeObserver functionality', () => {
|
||||||
const wrapper = shallow(<ShowMore {...baseProps}/>);
|
let originalResizeObserver: any;
|
||||||
const instance = wrapper.instance() as ShowMore;
|
|
||||||
instance.checkTextOverflow = jest.fn();
|
|
||||||
|
|
||||||
expect(instance.checkTextOverflow).not.toBeCalled();
|
beforeEach(() => {
|
||||||
|
// Store original implementation
|
||||||
|
originalResizeObserver = window.ResizeObserver;
|
||||||
|
|
||||||
wrapper.setProps({isRHSExpanded: true});
|
// Setup fake timers for requestAnimationFrame
|
||||||
expect(instance.checkTextOverflow).toBeCalledTimes(1);
|
jest.useFakeTimers();
|
||||||
|
});
|
||||||
|
|
||||||
wrapper.setProps({isRHSExpanded: false});
|
afterEach(() => {
|
||||||
expect(instance.checkTextOverflow).toBeCalledTimes(2);
|
// Restore original implementation
|
||||||
|
window.ResizeObserver = originalResizeObserver;
|
||||||
|
|
||||||
wrapper.setProps({isRHSOpen: true});
|
// Restore real timers
|
||||||
expect(instance.checkTextOverflow).toBeCalledTimes(3);
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
wrapper.setProps({isRHSOpen: false});
|
test('should set up ResizeObserver on mount', () => {
|
||||||
expect(instance.checkTextOverflow).toBeCalledTimes(4);
|
// Track observer creation
|
||||||
|
const observeMock = jest.fn();
|
||||||
|
const disconnectMock = jest.fn();
|
||||||
|
|
||||||
wrapper.setProps({text: 'text change'});
|
// Mock ResizeObserver
|
||||||
expect(instance.checkTextOverflow).toBeCalledTimes(5);
|
window.ResizeObserver = jest.fn().mockImplementation(() => ({
|
||||||
|
observe: observeMock,
|
||||||
|
disconnect: disconnectMock,
|
||||||
|
})) as unknown as typeof ResizeObserver;
|
||||||
|
|
||||||
wrapper.setProps({text: 'text another change'});
|
// Render component
|
||||||
expect(instance.checkTextOverflow).toBeCalledTimes(6);
|
renderWithContext(<ShowMore {...baseProps}>{children}</ShowMore>);
|
||||||
|
|
||||||
wrapper.setProps({checkOverflow: 1});
|
// Verify ResizeObserver was created
|
||||||
expect(instance.checkTextOverflow).toBeCalledTimes(7);
|
expect(window.ResizeObserver).toHaveBeenCalled();
|
||||||
|
|
||||||
wrapper.setProps({checkOverflow: 1});
|
// Verify observe was called (meaning the text container is being observed)
|
||||||
expect(instance.checkTextOverflow).toBeCalledTimes(7);
|
expect(observeMock).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should check overflow when ResizeObserver detects size changes', () => {
|
||||||
|
// Create a mock ResizeObserver that can be triggered manually
|
||||||
|
let resizeCallback: ResizeObserverCallback | undefined;
|
||||||
|
window.ResizeObserver = jest.fn().mockImplementation((callback) => {
|
||||||
|
resizeCallback = callback;
|
||||||
|
return {
|
||||||
|
observe: jest.fn(),
|
||||||
|
disconnect: jest.fn(),
|
||||||
|
};
|
||||||
|
}) as unknown as typeof ResizeObserver;
|
||||||
|
|
||||||
|
// Mock requestAnimationFrame to execute callback immediately
|
||||||
|
const originalRAF = window.requestAnimationFrame;
|
||||||
|
window.requestAnimationFrame = (cb) => {
|
||||||
|
cb(0);
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Mock scrollHeight to be greater than maxHeight after resize
|
||||||
|
mockTextContainerScrollHeight(baseProps.maxHeight - 50);
|
||||||
|
|
||||||
|
// Render component
|
||||||
|
const {container} = renderWithContext(<ShowMore {...baseProps}>{children}</ShowMore>);
|
||||||
|
|
||||||
|
// Verify no overflow initially
|
||||||
|
expect(container.querySelector('.post-message--overflow')).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
// Now simulate a resize that causes overflow
|
||||||
|
mockTextContainerScrollHeight(baseProps.maxHeight + 50);
|
||||||
|
|
||||||
|
// Trigger the ResizeObserver callback
|
||||||
|
if (resizeCallback && container.querySelector('.post-message__text-container')) {
|
||||||
|
const mockEntry = [{
|
||||||
|
target: container.querySelector('.post-message__text-container') as Element,
|
||||||
|
contentRect: {} as DOMRectReadOnly,
|
||||||
|
borderBoxSize: [] as ResizeObserverSize[],
|
||||||
|
contentBoxSize: [] as ResizeObserverSize[],
|
||||||
|
devicePixelContentBoxSize: [] as ResizeObserverSize[],
|
||||||
|
}];
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
resizeCallback!(mockEntry, {} as ResizeObserver);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify overflow is detected
|
||||||
|
expect(container.querySelector('.post-message--overflow')).toBeInTheDocument();
|
||||||
|
} finally {
|
||||||
|
// Restore original requestAnimationFrame
|
||||||
|
window.requestAnimationFrame = originalRAF;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should clean up ResizeObserver on unmount', () => {
|
||||||
|
// Create mock with disconnect spy
|
||||||
|
const disconnectSpy = jest.fn();
|
||||||
|
window.ResizeObserver = jest.fn(() => ({
|
||||||
|
observe: jest.fn(),
|
||||||
|
disconnect: disconnectSpy,
|
||||||
|
})) as unknown as typeof ResizeObserver;
|
||||||
|
|
||||||
|
// Render and unmount component
|
||||||
|
const {unmount} = renderWithContext(<ShowMore {...baseProps}>{children}</ShowMore>);
|
||||||
|
unmount();
|
||||||
|
|
||||||
|
// Verify disconnect was called
|
||||||
|
expect(disconnectSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle browsers without ResizeObserver support', () => {
|
||||||
|
// Remove ResizeObserver
|
||||||
|
delete (window as any).ResizeObserver;
|
||||||
|
|
||||||
|
// Render component should not throw error
|
||||||
|
expect(() => {
|
||||||
|
renderWithContext(<ShowMore {...baseProps}>{children}</ShowMore>);
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should update isOverflow state when content height changes', () => {
|
||||||
|
// Mock ResizeObserver before rendering
|
||||||
|
window.ResizeObserver = jest.fn().mockImplementation(() => {
|
||||||
|
return {
|
||||||
|
observe: jest.fn(),
|
||||||
|
disconnect: jest.fn(),
|
||||||
|
};
|
||||||
|
}) as unknown as typeof ResizeObserver;
|
||||||
|
|
||||||
|
// Mock requestAnimationFrame to execute callback immediately
|
||||||
|
const originalRAF = window.requestAnimationFrame;
|
||||||
|
window.requestAnimationFrame = (cb) => {
|
||||||
|
cb(0);
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Initial render with no overflow
|
||||||
|
mockTextContainerScrollHeight(baseProps.maxHeight - 50);
|
||||||
|
const {container, rerender} = renderWithContext(<ShowMore {...baseProps}>{children}</ShowMore>);
|
||||||
|
|
||||||
|
// Verify no overflow initially
|
||||||
|
expect(container.querySelector('.post-message--overflow')).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
// Now simulate content that overflows
|
||||||
|
mockTextContainerScrollHeight(baseProps.maxHeight + 50);
|
||||||
|
|
||||||
|
// Force a re-render to trigger checkTextOverflow
|
||||||
|
rerender(
|
||||||
|
<ShowMore
|
||||||
|
{...baseProps}
|
||||||
|
checkOverflow={1}
|
||||||
|
>{children}</ShowMore>,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify overflow is detected
|
||||||
|
expect(container.querySelector('.post-message--overflow')).toBeInTheDocument();
|
||||||
|
} finally {
|
||||||
|
// Restore original requestAnimationFrame
|
||||||
|
window.requestAnimationFrame = originalRAF;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ type Props = {
|
|||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
checkOverflow?: number;
|
checkOverflow?: number;
|
||||||
isAttachmentText?: boolean;
|
isAttachmentText?: boolean;
|
||||||
isRHSExpanded: boolean;
|
|
||||||
isRHSOpen: boolean;
|
|
||||||
text?: string;
|
text?: string;
|
||||||
compactDisplay: boolean;
|
compactDisplay: boolean;
|
||||||
overflowType?: AttachmentTextOverflowType;
|
overflowType?: AttachmentTextOverflowType;
|
||||||
@@ -30,6 +28,7 @@ export default class ShowMore extends React.PureComponent<Props, State> {
|
|||||||
private maxHeight: number;
|
private maxHeight: number;
|
||||||
private textContainer: React.RefObject<HTMLDivElement>;
|
private textContainer: React.RefObject<HTMLDivElement>;
|
||||||
private overflowRef?: number;
|
private overflowRef?: number;
|
||||||
|
private resizeObserver: ResizeObserver | null = null;
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -42,16 +41,17 @@ export default class ShowMore extends React.PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.checkTextOverflow();
|
this.setupResizeObserver();
|
||||||
|
|
||||||
window.addEventListener('resize', this.handleResize);
|
// Initial check for overflow
|
||||||
|
this.checkTextOverflow();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: Props) {
|
componentDidUpdate(prevProps: Props) {
|
||||||
|
// Only manually check for overflow when text content changes or when explicitly requested
|
||||||
|
// ResizeObserver will handle size changes caused by other factors
|
||||||
if (
|
if (
|
||||||
this.props.text !== prevProps.text ||
|
this.props.text !== prevProps.text ||
|
||||||
this.props.isRHSExpanded !== prevProps.isRHSExpanded ||
|
|
||||||
this.props.isRHSOpen !== prevProps.isRHSOpen ||
|
|
||||||
this.props.checkOverflow !== prevProps.checkOverflow
|
this.props.checkOverflow !== prevProps.checkOverflow
|
||||||
) {
|
) {
|
||||||
this.checkTextOverflow();
|
this.checkTextOverflow();
|
||||||
@@ -59,12 +59,39 @@ export default class ShowMore extends React.PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
window.removeEventListener('resize', this.handleResize);
|
|
||||||
if (this.overflowRef) {
|
if (this.overflowRef) {
|
||||||
window.cancelAnimationFrame(this.overflowRef);
|
window.cancelAnimationFrame(this.overflowRef);
|
||||||
}
|
}
|
||||||
|
this.cleanupResizeObserver();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupResizeObserver = () => {
|
||||||
|
if (!this.textContainer.current || !window.ResizeObserver) {
|
||||||
|
// ResizeObserver is not supported in this browser or the container is not available yet
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up any existing observer before creating a new one
|
||||||
|
// This prevents multiple observers in case setupResizeObserver is called more than once
|
||||||
|
this.cleanupResizeObserver();
|
||||||
|
|
||||||
|
// Create a new ResizeObserver to watch for size changes in the text container
|
||||||
|
this.resizeObserver = new ResizeObserver(() => {
|
||||||
|
// When the size of the text container changes, check if we need to show/hide the "Show More" button
|
||||||
|
this.checkTextOverflow();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start observing the text container
|
||||||
|
this.resizeObserver.observe(this.textContainer.current);
|
||||||
|
};
|
||||||
|
|
||||||
|
cleanupResizeObserver = () => {
|
||||||
|
if (this.resizeObserver) {
|
||||||
|
this.resizeObserver.disconnect();
|
||||||
|
this.resizeObserver = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
toggleCollapse = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
toggleCollapse = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -94,10 +121,6 @@ export default class ShowMore extends React.PureComponent<Props, State> {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
handleResize = () => {
|
|
||||||
this.checkTextOverflow();
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
isCollapsed,
|
isCollapsed,
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user