Skip to content

Commit

Permalink
fix(ExpandableSearch): compose prop handlers with internal (#12135) (#…
Browse files Browse the repository at this point in the history
…12143)

* fix(ExpandableSearch): compose prop handlers with internal (#12135)

* fix(ExpandableSearch): compose prop handlers with internal

* fix(Search): forward `onExpand` to icon `onClick` for event composition

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
gi and kodiakhq[bot] authored Oct 4, 2022
1 parent de54f57 commit d5d10a0
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
/**
* Copyright IBM Corp. 2016, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import { mount } from 'enzyme';
import React from 'react';
import Search from './ExpandableSearch';

const prefix = 'cds';

describe('ExpandableSearch', () => {
let wrapper;

const container = () => wrapper.find(`.${prefix}--search`);
const button = () => wrapper.find('button');
const input = () => wrapper.find('input');
const label = () => wrapper.find('label');

const render = (props) => {
if (wrapper) {
return wrapper.setProps(props);
}

wrapper = mount(<Search labelText="testlabel" {...props} />);

return wrapper;
};

describe('container', () => {
beforeEach(() => {
render();
});

it('has the class `${prefix}--search--expandable`', () => {
const value = container().hasClass(`${prefix}--search--expandable`);
expect(value).toEqual(true);
});

describe('expanded', () => {
const value = () => container().hasClass(`${prefix}--search--expanded`);

describe('when input has no content', () => {
beforeEach(() => {
input().simulate('change', { target: { value: '' } });
});

it('is false', () => {
expect(value()).toEqual(false);
});
});

describe.skip('when input has content', () => {
beforeEach(() => {
input().simulate('change', { target: { value: 'text' } });
});

it('is true', () => {
expect(value()).toEqual(true);
});

describe('when content is cleared', () => {
beforeEach(() => {
button().simulate('click');
});

it('is false', () => {
expect(value()).toEqual(false);
});
});
});
});
});

describe('label', () => {
beforeEach(() => {
render();
});

it('is rendered', () => {
expect(label().text()).toEqual('testlabel');
});
});

describe('onBlur', () => {
const onBlur = jest.fn();

beforeEach(() => {
render({ onBlur });
});

afterEach(() => {
onBlur.mockReset();
});

it('is called on blur', () => {
input().simulate('blur');
expect(onBlur).toHaveBeenCalled();
});
});

describe('onChange', () => {
const onChange = jest.fn();

beforeEach(() => {
render({ onChange });
});

afterEach(() => {
onChange.mockReset();
});

it('is called on change', () => {
input().simulate('change', { target: { value: 'text' } });
expect(onChange).toHaveBeenCalled();
});
});

describe('onClick', () => {
const onClick = jest.fn();

beforeEach(() => {
render({ onClick });
});

afterEach(() => {
onClick.mockReset();
});

it('is called on click', () => {
input().simulate('click');
expect(onClick).toHaveBeenCalled();
});
});

describe('onClear', () => {
const onClear = jest.fn();

beforeEach(() => {
render({ onClear });
});

afterEach(() => {
onClear.mockReset();
});

describe('when input has no content', () => {
beforeEach(() => {
input().simulate('change', { target: { value: '' } });
});

it('is called on clear', () => {
button().simulate('click');
expect(onClear).toHaveBeenCalled();
});
});

describe('when input has content', () => {
beforeEach(() => {
input().simulate('change', { target: { value: 'text' } });
});

it('is called on clear', () => {
button().simulate('click');
expect(onClear).toHaveBeenCalled();
});
});
});

describe('onExpand', () => {
const onExpand = jest.fn();

beforeEach(() => {
render({ onExpand });
});

afterEach(() => {
onExpand.mockReset();
});

// This won't work until v11
it.skip('is called on focus', () => {
input().simulate('focus');
expect(onExpand).toHaveBeenCalled();
});
});

describe('onFocus', () => {
const onFocus = jest.fn();

beforeEach(() => {
render({ onFocus });
});

afterEach(() => {
onFocus.mockReset();
});

it('is called on focus', () => {
input().simulate('focus');
expect(onFocus).toHaveBeenCalled();
});
});
});
23 changes: 14 additions & 9 deletions packages/react/src/components/ExpandableSearch/ExpandableSearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import React, { useState, useRef } from 'react';
import classnames from 'classnames';
import Search from '../Search';
import { usePrefix } from '../../internal/usePrefix';
import { composeEventHandlers } from '../../tools/events';

function ExpandableSearch(props) {
function ExpandableSearch({ onBlur, onChange, onExpand, onFocus, ...props }) {
const [expanded, setExpanded] = useState(false);
const [hasContent, setHasContent] = useState(false);
const searchRef = useRef(null);
Expand All @@ -32,6 +33,14 @@ function ExpandableSearch(props) {
}
}

function handleChange(evt) {
setHasContent(evt.target.value !== '');
}

function handleExpand() {
searchRef.current.focus?.();
}

const classes = classnames(
`${prefix}--search--expandable`,
{
Expand All @@ -45,14 +54,10 @@ function ExpandableSearch(props) {
{...props}
ref={searchRef}
className={classes}
onFocus={handleFocus}
onBlur={handleBlur}
onChange={(event) => {
setHasContent(event.target.value !== '');
}}
onExpand={() => {
searchRef.current.focus?.();
}}
onFocus={composeEventHandlers([onFocus, handleFocus])}
onBlur={composeEventHandlers([onBlur, handleBlur])}
onChange={composeEventHandlers([onChange, handleChange])}
onExpand={composeEventHandlers([onExpand, handleExpand])}
/>
);
}
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/components/Search/Search.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ export default class Search extends Component {
onKeyDown,
renderIcon,
onClear, // eslint-disable-line no-unused-vars
onExpand, // eslint-disable-line no-unused-vars, react/prop-types
...other
} = this.props;

Expand Down
8 changes: 2 additions & 6 deletions packages/react/src/components/Search/next/Search.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,17 +95,13 @@ const Search = React.forwardRef(function Search(

return (
<div role="search" aria-labelledby={searchId} className={searchClasses}>
{/* the magnifier is used in ExpandableSearch as a click target to expand,
{/* the magnifier is used in ExpandableSearch as a click target to expand,
however, it does not need a keyboard event bc the input element gets focus on keyboard nav and expands that way*/}
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}
<div
role={onExpand ? 'button' : null}
className={`${prefix}--search-magnifier`}
onClick={() => {
if (onExpand) {
onExpand();
}
}}>
onClick={onExpand}>
<CustomSearchIcon icon={renderIcon} />
</div>
<label id={searchId} htmlFor={uniqueId} className={`${prefix}--label`}>
Expand Down

0 comments on commit d5d10a0

Please sign in to comment.