Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
16 changes: 14 additions & 2 deletions src/BaseSelect/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export interface BaseSelectProps extends BaseSelectPrivateProps, React.AriaAttri
tagRender?: (props: CustomTagProps) => React.ReactElement;
direction?: 'ltr' | 'rtl';
maxLength?: number;

showScrollBar?: boolean | 'optional';
// MISC
tabIndex?: number;
autoFocus?: boolean;
Expand Down Expand Up @@ -223,6 +223,7 @@ const BaseSelect = React.forwardRef<BaseSelectRef, BaseSelectProps>((props, ref)
className,
showSearch,
tagRender,
showScrollBar = 'optional',
direction,
omitDomProps,

Expand Down Expand Up @@ -693,8 +694,19 @@ const BaseSelect = React.forwardRef<BaseSelectRef, BaseSelectProps>((props, ref)
showSearch: mergedShowSearch,
multiple,
toggleOpen: onToggleOpen,
showScrollBar,
}),
[props, notFoundContent, triggerOpen, mergedOpen, id, mergedShowSearch, multiple, onToggleOpen],
[
props,
notFoundContent,
triggerOpen,
mergedOpen,
id,
mergedShowSearch,
multiple,
onToggleOpen,
showScrollBar,
],
);

// ==================================================================
Expand Down
2 changes: 2 additions & 0 deletions src/OptionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const OptionList: React.ForwardRefRenderFunction<RefOptionListProps, {}> = (_, r
toggleOpen,
notFoundContent,
onPopupScroll,
showScrollBar,
} = useBaseProps();
const {
maxCount,
Expand Down Expand Up @@ -325,6 +326,7 @@ const OptionList: React.ForwardRefRenderFunction<RefOptionListProps, {}> = (_, r
virtual={virtual}
direction={direction}
innerProps={virtual ? null : a11yProps}
showScrollBar={showScrollBar}
>
{(item, itemIndex) => {
const { group, groupOption, data, label, value } = item;
Expand Down
95 changes: 95 additions & 0 deletions tests/ListScrollBar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React from 'react';
import { render, waitFor } from '@testing-library/react';
import { spyElementPrototypes } from './utils/domHook';
import Select from '../src';

jest.mock('../src/utils/platformUtil');
// Mock VirtualList
jest.mock('rc-virtual-list', () => {
const OriReact = jest.requireActual('react');
const OriList = jest.requireActual('rc-virtual-list').default;

return OriReact.forwardRef((props, ref) => {
const oriRef = OriReact.useRef();

OriReact.useImperativeHandle(ref, () => ({
...oriRef.current,
scrollTo: (arg) => {
global.scrollToArgs = arg;
oriRef.current.scrollTo(arg);
},
}));

return <OriList {...props} ref={oriRef} />;
});
});

describe('List.Scroll', () => {
let mockElement;
let boundingRect = {
top: 0,
bottom: 0,
width: 100,
height: 50,
};

beforeAll(() => {
mockElement = spyElementPrototypes(HTMLElement, {
offsetHeight: {
get: () => 100,
},
clientHeight: {
get: () => 50,
},
getBoundingClientRect: () => boundingRect,
offsetParent: {
get: () => document.body,
},
});
});

afterAll(() => {
mockElement.mockRestore();
});

beforeEach(() => {
boundingRect = {
top: 0,
bottom: 0,
width: 100,
height: 50,
};
jest.useFakeTimers();
});

afterEach(() => {
jest.useRealTimers();
});

it('should show scrollbar when showScrollBar is true', async () => {
const options = Array.from({ length: 10 }, (_, index) => ({
label: `${index + 1}`,
value: `${index + 1}`,
}));

const { container } = render(<Select open showScrollBar options={options} />);

await waitFor(() => {
const scrollbarElement = container.querySelector('.rc-virtual-list-scrollbar-visible');
expect(scrollbarElement).not.toBeNull();
});
});
it('should not have scrollbar when showScrollBar is false', async () => {
const options = Array.from({ length: 10 }, (_, index) => ({
label: `${index + 1}`,
value: `${index + 1}`,
}));

const { container } = render(<Select open showScrollBar={false} options={options} />);

await waitFor(() => {
const scrollbarElement = container.querySelector('.rc-virtual-list-scrollbar-visible');
expect(scrollbarElement).toBeNull();
});
});
});
61 changes: 61 additions & 0 deletions tests/utils/domHook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/* eslint-disable no-param-reassign */
const NO_EXIST = { __NOT_EXIST: true };

export function spyElementPrototypes(Element, properties) {
const propNames = Object.keys(properties);
const originDescriptors = {};

propNames.forEach((propName) => {
const originDescriptor = Object.getOwnPropertyDescriptor(Element.prototype, propName);
originDescriptors[propName] = originDescriptor || NO_EXIST;

const spyProp = properties[propName];

if (typeof spyProp === 'function') {
// If is a function
Element.prototype[propName] = function spyFunc(...args) {
return spyProp.call(this, originDescriptor, ...args);
};
} else {
// Otherwise tread as a property
Object.defineProperty(Element.prototype, propName, {
...spyProp,
set(value) {

Check warning on line 23 in tests/utils/domHook.ts

View check run for this annotation

Codecov / codecov/patch

tests/utils/domHook.ts#L23

Added line #L23 was not covered by tests
if (spyProp.set) {
return spyProp.set.call(this, originDescriptor, value);

Check warning on line 25 in tests/utils/domHook.ts

View check run for this annotation

Codecov / codecov/patch

tests/utils/domHook.ts#L25

Added line #L25 was not covered by tests
}
return originDescriptor.set(value);

Check warning on line 27 in tests/utils/domHook.ts

View check run for this annotation

Codecov / codecov/patch

tests/utils/domHook.ts#L27

Added line #L27 was not covered by tests
},
get() {
if (spyProp.get) {
return spyProp.get.call(this, originDescriptor);
}
return originDescriptor.get();

Check warning on line 33 in tests/utils/domHook.ts

View check run for this annotation

Codecov / codecov/patch

tests/utils/domHook.ts#L33

Added line #L33 was not covered by tests
},
configurable: true,
});
}
});

return {
mockRestore() {
propNames.forEach((propName) => {
const originDescriptor = originDescriptors[propName];
if (originDescriptor === NO_EXIST) {
delete Element.prototype[propName];
} else if (typeof originDescriptor === 'function') {
Element.prototype[propName] = originDescriptor;

Check warning on line 47 in tests/utils/domHook.ts

View check run for this annotation

Codecov / codecov/patch

tests/utils/domHook.ts#L47

Added line #L47 was not covered by tests
} else {
Object.defineProperty(Element.prototype, propName, originDescriptor);
}
});
},
};
}

export function spyElementPrototype(Element, propName, property) {
return spyElementPrototypes(Element, {

Check warning on line 57 in tests/utils/domHook.ts

View check run for this annotation

Codecov / codecov/patch

tests/utils/domHook.ts#L56-L57

Added lines #L56 - L57 were not covered by tests
[propName]: property,
});
}
/* eslint-enable no-param-reassign */
Loading