Skip to content

Commit 43f46eb

Browse files
authored
feat(combo): Added defaultValue support (#1494)
* refactor(combo): Synchronous form flow for combo This commit migrates most of the properties related to form submission to be sync instead of the old `@watch` handlers. Additionally there are several other changes in order to "modernize" the combo code base: * moved any `@property` decorators over the setters * deletegated the click handlers of the combo items to a single one on the parent list * removed some old code which was no longer needed * feat(combo): Added defaultValue support * fix(combo): Form reset hooks
1 parent 970dcce commit 43f46eb

File tree

9 files changed

+662
-519
lines changed

9 files changed

+662
-519
lines changed

src/components/combo/combo-item.ts

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { property } from 'lit/decorators.js';
33

44
import { themes } from '../../theming/theming-decorator.js';
55
import IgcCheckboxComponent from '../checkbox/checkbox.js';
6-
import { watch } from '../common/decorators/watch.js';
76
import { registerComponent } from '../common/definitions/register.js';
87
import { all } from '../dropdown/themes/item.js';
98
import { styles as shared } from '../dropdown/themes/shared/item/dropdown-item.common.css.js';
@@ -21,16 +20,25 @@ export default class IgcComboItemComponent extends LitElement {
2120
}
2221

2322
private _internals: ElementInternals;
23+
private _selected = false;
2424

2525
@property({ attribute: false })
2626
public index!: number;
2727

2828
/**
2929
* Determines whether the item is selected.
3030
* @attr selected
31+
* @default false
3132
*/
3233
@property({ type: Boolean, reflect: true })
33-
public selected = false;
34+
public set selected(value: boolean) {
35+
this._selected = value;
36+
this._internals.ariaSelected = this._selected.toString();
37+
}
38+
39+
public get selected(): boolean {
40+
return this._selected;
41+
}
3442

3543
/**
3644
* Determines whether the item is active.
@@ -42,19 +50,15 @@ export default class IgcComboItemComponent extends LitElement {
4250
* Determines whether the item is active.
4351
* @attr hide-checkbox
4452
*/
45-
@property({ attribute: 'hide-checkbox', type: Boolean, reflect: false })
53+
@property({ type: Boolean, attribute: 'hide-checkbox' })
4654
public hideCheckbox = false;
4755

48-
@watch('selected')
49-
protected selectedChange() {
50-
this._internals.ariaSelected = `${this.selected}`;
51-
}
52-
5356
constructor() {
5457
super();
5558

5659
this._internals = this.attachInternals();
5760
this._internals.role = 'option';
61+
this._internals.ariaSelected = 'false';
5862
}
5963

6064
public override connectedCallback() {
@@ -63,13 +67,15 @@ export default class IgcComboItemComponent extends LitElement {
6367
}
6468

6569
private renderCheckbox() {
66-
return html`<section part="prefix">
67-
<igc-checkbox
68-
inert
69-
?checked=${this.selected}
70-
exportparts="control: checkbox, indicator: checkbox-indicator, checked"
71-
></igc-checkbox>
72-
</section>`;
70+
return html`
71+
<section part="prefix">
72+
<igc-checkbox
73+
.inert=${true}
74+
?checked=${this.selected}
75+
exportparts="control: checkbox, indicator: checkbox-indicator, checked"
76+
></igc-checkbox>
77+
</section>
78+
`;
7379
}
7480

7581
protected override render() {

src/components/combo/combo.spec.ts

Lines changed: 77 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { elementUpdated, expect, fixture, html } from '@open-wc/testing';
22
import { spy } from 'sinon';
33

44
import { defineComponents } from '../common/definitions/defineComponents.js';
5+
import { first } from '../common/util.js';
56
import {
67
type ValidationContainerTestsParams,
78
createFormAssociatedTestBed,
@@ -335,7 +336,7 @@ describe('Combo', () => {
335336
const cityNames: string[] = [];
336337

337338
items(combo).forEach((item) => {
338-
cityNames.push(item.textContent!);
339+
cityNames.push(item.innerText);
339340
});
340341

341342
cities.forEach((city) => {
@@ -373,7 +374,7 @@ describe('Combo', () => {
373374
});
374375

375376
const selected = items(combo).find((item) => item.selected);
376-
expect(selected?.textContent).to.equal(item[combo.displayKey!]);
377+
expect(selected?.innerText).to.equal(item[combo.displayKey!]);
377378

378379
combo.deselect([item[combo.valueKey!]]);
379380

@@ -401,7 +402,7 @@ describe('Combo', () => {
401402
});
402403

403404
const selected = items(combo).find((item) => item.selected);
404-
expect(selected?.textContent).to.equal(item[combo.displayKey!]);
405+
expect(selected?.innerText).to.equal(item[combo.displayKey!]);
405406

406407
combo.deselect([item]);
407408

@@ -807,7 +808,7 @@ describe('Combo', () => {
807808
await filterCombo('sof');
808809

809810
expect(items(combo).length).to.equal(1);
810-
expect(items(combo)[0].textContent).to.equal('Sofia');
811+
expect(items(combo)[0].innerText).to.equal('Sofia');
811812
});
812813

813814
it('should select the first matched item upon pressing enter after search', async () => {
@@ -975,7 +976,7 @@ describe('Combo', () => {
975976
const selected = items(combo).filter((i) => i.selected);
976977

977978
expect(selected.length).to.equal(1);
978-
expect(selected[0].textContent).to.equal(match?.name);
979+
expect(selected[0].innerText).to.equal(match?.name);
979980
});
980981

981982
it('should deselect a single item using valueKey as argument with the Selection API', async () => {
@@ -1017,7 +1018,7 @@ describe('Combo', () => {
10171018
const selected = items(combo).filter((i) => i.selected);
10181019

10191020
expect(selected.length).to.equal(1);
1020-
expect(selected[0].textContent).to.equal(item?.name);
1021+
expect(selected[0].innerText).to.equal(item?.name);
10211022
});
10221023

10231024
it('should deselect the item passed as argument with the Selection API', async () => {
@@ -1056,7 +1057,7 @@ describe('Combo', () => {
10561057

10571058
// Verify we can only see one item in the list
10581059
expect(items(combo).length).to.equal(1);
1059-
expect(items(combo)[0].textContent).to.equal('Sofia');
1060+
expect(items(combo)[0].innerText).to.equal('Sofia');
10601061

10611062
// Select an item not visible in the list using the API
10621063
const selection = 'US01';
@@ -1079,7 +1080,7 @@ describe('Combo', () => {
10791080
expect(selected.length).to.equal(1);
10801081

10811082
// It should match the one selected via the API
1082-
expect(selected[0].textContent).to.equal('New York');
1083+
expect(selected[0].innerText).to.equal('New York');
10831084
});
10841085

10851086
it('should deselect item(s) even if the list of items has been filtered', async () => {
@@ -1097,7 +1098,7 @@ describe('Combo', () => {
10971098
expect(selected.length).to.equal(1);
10981099

10991100
// It should match the one selected via the API
1100-
expect(selected[0].textContent).to.equal('New York');
1101+
expect(selected[0].innerText).to.equal('New York');
11011102
expect(combo.value[0]).to.equal(selection);
11021103

11031104
// Filter the list of items
@@ -1108,7 +1109,7 @@ describe('Combo', () => {
11081109

11091110
// Verify we can only see one item in the list
11101111
expect(items(combo).length).to.equal(1);
1111-
expect(items(combo)[0].textContent).to.equal('Sofia');
1112+
expect(items(combo)[0].innerText).to.equal('Sofia');
11121113

11131114
// Deselect the previously selected item while the list is filtered
11141115
combo.deselect(selection);
@@ -1258,16 +1259,14 @@ describe('Combo', () => {
12581259
expect(spec.element.form).to.equal(spec.form);
12591260
});
12601261

1261-
it('is not associated on submit if no value', async () => {
1262-
// FIXME: The combo value setter does not update the form state in a synchronous manner
1263-
// FIXME: delegating to a @watch callback.
1264-
await spec.setProperties({ value: [] });
1262+
it('is not associated on submit if no value', () => {
1263+
spec.setProperties({ value: [] });
12651264
spec.assertSubmitHasValue(null);
12661265
});
12671266

1268-
it('is associated on submit with value-key (single)', async () => {
1269-
await spec.setProperties({ singleSelect: true });
1270-
await spec.setProperties({ value: ['BG01', 'BG02'] });
1267+
it('is associated on submit with value-key (single)', () => {
1268+
spec.setProperties({ singleSelect: true });
1269+
spec.setProperties({ value: ['BG01', 'BG02'] });
12711270
spec.assertSubmitHasValue('BG01');
12721271
});
12731272

@@ -1276,64 +1275,62 @@ describe('Combo', () => {
12761275
spec.assertSubmitHasValues(['BG01', 'BG02']);
12771276
});
12781277

1279-
it('is associated on submit without value-key (single)', async () => {
1278+
it('is associated on submit without value-key (single)', () => {
12801279
const [first, second, _] = cities;
12811280

1282-
await spec.setProperties({ valueKey: undefined, singleSelect: true });
1281+
spec.setProperties({ valueKey: undefined, singleSelect: true });
12831282
spec.element.select([first, second]);
1284-
await elementUpdated(spec.element);
12851283

12861284
expect(spec.formData.getAll(spec.element.name)).lengthOf(1);
12871285
spec.assertSubmitPasses();
12881286
});
12891287

1290-
it('is associated on submit without value-key (multiple)', async () => {
1288+
it('is associated on submit without value-key (multiple)', () => {
12911289
const [first, second, _] = cities;
12921290

1293-
await spec.setProperties({ valueKey: undefined });
1291+
spec.setProperties({ valueKey: undefined });
12941292
spec.element.select([first, second]);
1295-
await elementUpdated(spec.element);
12961293

12971294
expect(spec.formData.getAll(spec.element.name)).lengthOf(2);
12981295
spec.assertSubmitPasses();
12991296
});
13001297

1301-
it('is correctly reset on form reset (multiple)', async () => {
1298+
it('is correctly reset on form reset (multiple)', () => {
13021299
const initial = spec.element.value;
13031300

1304-
await spec.setAttributes({ value: JSON.stringify(['BG01', 'BG02']) });
1305-
await spec.setProperties({ value: [] });
1301+
spec.setAttributes({ value: JSON.stringify(['BG01', 'BG02']) });
1302+
spec.setProperties({ value: [] });
13061303

13071304
spec.reset();
13081305
expect(spec.element.value).to.eql(initial);
13091306
});
13101307

1311-
it('is correctly reset on form reset (single)', async () => {
1308+
it('is correctly reset on form reset (single)', () => {
13121309
// Initial value is a multiple array. The combo defaults to the first item
1313-
const initial = [spec.element.value[0]];
1310+
const initial = [first(spec.element.value)];
13141311

1315-
await spec.setProperties({ singleSelect: true });
1316-
await spec.setProperties({ value: ['US01'] });
1312+
spec.setProperties({ singleSelect: true });
1313+
spec.setProperties({ value: ['US01'] });
13171314

13181315
expect(spec.element.value).to.eql(['US01']);
13191316

13201317
spec.reset();
13211318
expect(spec.element.value).to.eql(initial);
13221319
});
13231320

1324-
it('should reset to the new default value after setAttribute() call (multiple)', async () => {
1325-
await spec.setAttributes({ value: JSON.stringify(['US01', 'US02']) });
1326-
await spec.setProperties({ value: [] });
1321+
it('should reset to the new default value after setAttribute() call (multiple)', () => {
1322+
spec.setAttributes({ value: JSON.stringify(['US01', 'US02']) });
1323+
spec.setProperties({ value: [] });
13271324

13281325
spec.reset();
13291326
expect(spec.element.value).to.eql(['US01', 'US02']);
13301327
spec.assertSubmitHasValues(spec.element.value);
13311328
});
13321329

1333-
it('should reset to the new default value after setAttribute() call (single)', async () => {
1334-
await spec.setProperties({ singleSelect: true });
1335-
await spec.setAttributes({ value: JSON.stringify(['US01']) });
1336-
await spec.setProperties({ value: [] });
1330+
it('should reset to the new default value after setAttribute() call (single)', () => {
1331+
spec.setProperties({ singleSelect: true });
1332+
spec.setAttributes({ value: JSON.stringify(['US01']) });
1333+
spec.setProperties({ value: [] });
13371334

13381335
spec.reset();
13391336

@@ -1349,11 +1346,11 @@ describe('Combo', () => {
13491346
expect(spec.element.disabled).to.be.false;
13501347
});
13511348

1352-
it('fulfils required constraint', async () => {
1353-
await spec.setProperties({ value: [], required: true });
1349+
it('fulfils required constraint', () => {
1350+
spec.setProperties({ value: [], required: true });
13541351
spec.assertSubmitFails();
13551352

1356-
await spec.setProperties({ value: ['BG01', 'BG02'] });
1353+
spec.setProperties({ value: ['BG01', 'BG02'] });
13571354
spec.assertSubmitPasses();
13581355
});
13591356

@@ -1369,7 +1366,40 @@ describe('Combo', () => {
13691366
describe('defaultValue', () => {
13701367
const value = ['BG01', 'BG02'];
13711368

1372-
describe('Form integration', () => {
1369+
describe('Form integration (single)', () => {
1370+
const spec = createFormAssociatedTestBed<IgcComboComponent<City>>(html`
1371+
<igc-combo
1372+
name="combo"
1373+
.data=${cities}
1374+
.defaultValue=${value}
1375+
single-select
1376+
value-key="id"
1377+
display-key="name"
1378+
></igc-combo>
1379+
`);
1380+
1381+
beforeEach(async () => {
1382+
await spec.setup(IgcComboComponent.tagName);
1383+
});
1384+
1385+
it('correct initial state', () => {
1386+
spec.assertIsPristine();
1387+
expect(spec.element.value).to.eql([first(value)]);
1388+
});
1389+
1390+
it('is correctly submitted', () => {
1391+
spec.assertSubmitHasValue(first(value));
1392+
});
1393+
1394+
it('is correctly reset', () => {
1395+
spec.setProperties({ value: [] });
1396+
spec.reset();
1397+
1398+
expect(spec.element.value).to.eql([first(value)]);
1399+
});
1400+
});
1401+
1402+
describe('Form integration (multiple)', () => {
13731403
const spec = createFormAssociatedTestBed<IgcComboComponent<City>>(html`
13741404
<igc-combo
13751405
name="combo"
@@ -1393,8 +1423,8 @@ describe('Combo', () => {
13931423
spec.assertSubmitHasValues(value);
13941424
});
13951425

1396-
it('is correctly reset', async () => {
1397-
await spec.setProperties({ value: [] });
1426+
it('is correctly reset', () => {
1427+
spec.setProperties({ value: [] });
13981428
spec.reset();
13991429

14001430
expect(spec.element.value).to.eql(value);
@@ -1407,6 +1437,7 @@ describe('Combo', () => {
14071437
name="combo"
14081438
.data=${cities}
14091439
.defaultValue=${[]}
1440+
required
14101441
value-key="id"
14111442
display-key="name"
14121443
></igc-combo>
@@ -1421,8 +1452,8 @@ describe('Combo', () => {
14211452
spec.assertSubmitFails();
14221453
});
14231454

1424-
it('passes required validation', async () => {
1425-
await spec.setProperties({ defaultValue: value });
1455+
it('passes required validation', () => {
1456+
spec.setProperties({ defaultValue: value });
14261457
spec.assertIsPristine();
14271458

14281459
spec.assertSubmitHasValues(value);
@@ -1431,7 +1462,7 @@ describe('Combo', () => {
14311462
});
14321463

14331464
describe('Validation message slots', () => {
1434-
it('', async () => {
1465+
it('', () => {
14351466
const testParameters: ValidationContainerTestsParams<IgcComboComponent>[] =
14361467
[
14371468
{ slots: ['valueMissing'], props: { required: true } }, // value-missing slot

0 commit comments

Comments
 (0)