Skip to content
This repository has been archived by the owner on Nov 6, 2023. It is now read-only.

Commit

Permalink
Support react-gears Combobox component (#93)
Browse files Browse the repository at this point in the history
* Add Combobox component

* Handle Combobox in cy.select

* Handle Combobox in cy.clear

* Handle Combobox in cy.fill

* Clarify error messages

* Nuke deprecated click override
  • Loading branch information
xeger authored Jan 3, 2023
1 parent 8a70a62 commit eb9de43
Show file tree
Hide file tree
Showing 12 changed files with 291 additions and 86 deletions.
48 changes: 48 additions & 0 deletions cypress/component/commands/clear.cy.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import {
Combobox,
DateInput,
FormLabelGroup,
Input,
Expand Down Expand Up @@ -47,6 +48,53 @@ describe('cy.clear', () => {
);
});

context('Combobox component', () => {
function Testbed({ initialValue, onChange }) {
const options = ['alpha', 'bravo', 'charlie'].map((o) => ({
label: o,
value: o,
}));

const [value, setValue] = React.useState(initialValue);

return (
<FormLabelGroup label="some label">
<Combobox
options={options}
onChange={(v) => {
setValue(v);
onChange?.(v);
}}
value={value}
/>
</FormLabelGroup>
);
}

it('clears values', () => {
let selected = 'alpha';

cy.mount(
<Testbed
initialValue={selected}
onChange={(v) => {
selected = v;
}}
/>
);

cy.component(comp.Combobox, 'some label').clear();
eventually(() => selected === undefined);
});

it('dismisses popups', () => {
cy.mount(<Testbed initialValue="alpha" />);

cy.component(comp.Combobox, 'some label').clear();
cy.get('[data-testid=combobox-menu]').should('not.be.visible');
});
});

context('Select component', () => {
it('clears values', () => {
const options = ['alpha', 'bravo', 'charlie'].map((o) => ({
Expand Down
10 changes: 4 additions & 6 deletions cypress/component/commands/click.cy.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import React from 'react';
import {
BlockPanel,
Button,
Icon,
} from '@appfolio/react-gears';
import { BlockPanel, Button, Icon } from '@appfolio/react-gears';

// NB: this package will stop overriding cy.click; the test is still useful to
// prove that Cypress behavior does not regress (thereby requiring an override again).
describe('cy.click', () => {
it('deals with FontAwesome buttons', () => {
cy.mount(
<div>
<Button><Icon name="times"/></Button>
<Button>
<Icon name="times" />
</Button>
</div>
);

Expand Down
54 changes: 49 additions & 5 deletions cypress/component/commands/fill.cy.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import {
Combobox,
DateInput,
FormLabelGroup,
Input,
Expand All @@ -10,7 +11,7 @@ import * as comp from '../../../src/components';
import eventually from '../../support/eventually';

describe('cy.fill', () => {
context('Input component', () => {
context('with Input', () => {
beforeEach(() => {
cy.mount(
<FormLabelGroup label="some label">
Expand All @@ -33,7 +34,7 @@ describe('cy.fill', () => {
});
});

context('DateInput component', () => {
context('with DateInput', () => {
beforeEach(() => {
cy.mount(
<FormLabelGroup label="some label">
Expand All @@ -60,7 +61,7 @@ describe('cy.fill', () => {
});
});

context('textarea tag', () => {
context('with HTML textarea', () => {
beforeEach(() => {
cy.mount(
<FormLabelGroup label="some label">
Expand Down Expand Up @@ -88,7 +89,50 @@ describe('cy.fill', () => {
});
});

context('Select component', () => {
context('with Combobox', () => {
function Testbed({ onChange = () => undefined }) {
const options = ['alpha', 'bravo', 'charlie'].map((o) => ({
label: o,
value: o,
}));

const [value, setValue] = React.useState();

return (
<FormLabelGroup label="some label">
<Combobox
options={options}
onChange={(v) => {
setValue(v);
onChange(v);
}}
value={value}
/>
</FormLabelGroup>
);
}

it('works', () => {
let selected;

cy.mount(
<Testbed
onChange={(v) => {
selected = v;
}}
/>
);
cy.component(comp.Combobox, 'some label').fill('alpha');
eventually(() => selected === 'alpha');
});

// TODO: figure out how to intercept Cypress command errors
it.skip('requires a value', () => {
cy.component(comp.Combobox, 'some label').fill('');
});
});

context('with Select', () => {
const options = ['steve rogers', 'tony stark', 'natasha romanov'].map(
(o) => ({
label: o,
Expand Down Expand Up @@ -142,7 +186,7 @@ describe('cy.fill', () => {
});
});

context('select tag', () => {
context('with HTML select', () => {
beforeEach(() => {
cy.mount(
<FormLabelGroup label="best avenger">
Expand Down
78 changes: 60 additions & 18 deletions cypress/component/commands/select.cy.jsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,76 @@
import React from 'react';
import { FormLabelGroup, Select } from '@appfolio/react-gears';
import { Combobox, FormLabelGroup, Select } from '@appfolio/react-gears';

import * as comp from '../../../src/components';
import eventually from '../../support/eventually';

describe('cy.select', () => {
it('handles <select>', () => {
cy.mount(
<FormLabelGroup label="some label">
<select>
<option value="alpha">alpha</option>
<option value="bravo">bravo</option>
<option value="charlie">charlie</option>
</select>
</FormLabelGroup>
);
context('with HTML select', () => {
beforeEach(() => {
cy.mount(
<FormLabelGroup label="some label">
<select>
<option value="alpha">alpha</option>
<option value="bravo">bravo</option>
<option value="charlie">charlie</option>
</select>
</FormLabelGroup>
);
});

it('works', () => {
cy.get('select').select('alpha');
cy.get('select').should('have.value', 'alpha');
cy.get('select').select('bravo');
cy.get('select').should('have.value', 'bravo');
});
});

context('with Combobox', () => {
function Testbed({ onChange = () => undefined }) {
const options = ['alpha', 'bravo', 'charlie'].map((o) => ({
label: o,
value: o,
}));

const [value, setValue] = React.useState();

cy.get('select').select('alpha');
cy.get('select').should('have.value', 'alpha');
cy.get('select').select('bravo');
cy.get('select').should('have.value', 'bravo');
return (
<FormLabelGroup label="some label">
<Combobox
options={options}
onChange={(v) => {
setValue(v);
onChange(v);
}}
value={value}
/>
</FormLabelGroup>
);
}

it('works', () => {
let selected;

cy.mount(
<Testbed
onChange={(v) => {
selected = v;
}}
/>
);
cy.component(comp.Combobox, 'some label').select('alpha');
eventually(() => selected === 'alpha');
});
});

context('Select component', () => {
context('with Select', () => {
const options = ['alpha', 'bravo', 'charlie'].map((o) => ({
label: o,
value: o,
}));

it('clearable', () => {
it('handles clearable', () => {
let selected;
const onChange = (o) => (selected = o && o.value);

Expand All @@ -44,7 +86,7 @@ describe('cy.select', () => {
eventually(() => expect(selected).to.eq('bravo'));
});

it('non-clearable', () => {
it('handles non-clearable', () => {
let selected;
const onChange = (o) => {
expect(o && o.value).to.be.ok;
Expand Down
16 changes: 16 additions & 0 deletions cypress/component/components.cy.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
CardTitle,
CheckboxGroup,
CheckboxInput,
Combobox,
Datapair,
FormGroup,
FormLabelGroup,
Expand Down Expand Up @@ -58,6 +59,21 @@ describe('components', () => {
cy.component(comp.Card, 'other label').should('not.exist');
});

it('Combobox', () => {
const options = ['alpha', 'bravo', 'charlie'].map((o) => ({
label: o,
value: o,
}));
cy.mount(
<FormLabelGroup label="some label">
<Combobox options={options} />
</FormLabelGroup>
);
cy.component(comp.Combobox).should('be.visible');
cy.component(comp.Combobox, 'some label');
cy.component(comp.Combobox, 'other label').should('not.exist');
});

it('Datapair', () => {
cy.mount(<Datapair label="some label" value="some content" />);
cy.component(comp.Datapair).should('be.visible');
Expand Down
45 changes: 33 additions & 12 deletions src/commands/clear.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,35 @@ export function clear(
return originalFn(prevSubject, options);
}

if (prevSubject.hasClass('Select-control')) {
if (!options || options.log !== false)
Cypress.log({
name: 'clear',
$el: prevSubject,
consoleProps: () => ({
'Applied to': Cypress.dom.getElements(prevSubject),
}),
});
const isGearsCombobox = prevSubject.is(
'.dropdown:has([data-testid=combobox-input])'
);
const isGearsSelect = prevSubject.hasClass('Select-control');

if (!isGearsCombobox && !isGearsSelect) {
// For other inputs, use original but also dismiss popups (e.g. DateInput).
return originalFn(prevSubject, options).then(dismissAriaPopup);
}

if (!options || options.log !== false)
Cypress.log({
name: 'clear',
$el: prevSubject,
consoleProps: () => ({
'Applied to': Cypress.dom.getElements(prevSubject),
}),
});

if (isGearsCombobox) {
return cy
.wrap(prevSubject, QUIET)
.find('[data-testid=combobox-input]', QUIET)
.focus()
.type('{backspace}{backspace}', QUIET)
.then(blurIfNecessary);
}

if (isGearsSelect) {
const btn = prevSubject.find('button.close');

// Use the "X" button to clear Select components.
Expand All @@ -35,7 +54,7 @@ export function clear(
blurIfNecessary(prevSubject.find('input'));
return prevSubject;
});
// No "X" button; fall through to original cy.clear
// No "X" button (clearable=false); fall through to original cy.clear
else
return originalFn(prevSubject.find('input'), {
...options,
Expand All @@ -47,6 +66,8 @@ export function clear(
return prevSubject;
});
}
// For other inputs, use original cy.clear but also dismiss popups (e.g. DateInput).
return originalFn(prevSubject, options).then(dismissAriaPopup);

throw new Error(
'react-gears-cypress: internal error in clear command (conditionals exhausted); please report this as a GitHub issue.'
);
}
19 changes: 0 additions & 19 deletions src/commands/click.ts

This file was deleted.

Loading

0 comments on commit eb9de43

Please sign in to comment.