Skip to content
This repository was archived by the owner on Jan 13, 2025. It is now read-only.

Commit 5ab68fe

Browse files
Matt Gookfranqueiro
authored andcommitted
fix(checkbox): remove adapter.getNativeCb and move property hooks to component (#4073)
BREAKING CHANGE: The component is now responsible for calling MDCCheckboxFoundation#handleChange when the checked and indeterminate properties change.
1 parent 0da5a54 commit 5ab68fe

File tree

5 files changed

+99
-141
lines changed

5 files changed

+99
-141
lines changed

packages/mdc-checkbox/adapter.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,6 @@ class MDCCheckboxAdapter {
6262
*/
6363
removeNativeControlAttr(attr) {}
6464

65-
/** @return {!MDCSelectionControlState} */
66-
getNativeControl() {}
67-
6865
forceLayout() {}
6966

7067
/** @return {boolean} */

packages/mdc-checkbox/foundation.js

Lines changed: 0 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,6 @@ import MDCCheckboxAdapter from './adapter';
2828
/* eslint-enable no-unused-vars */
2929
import {cssClasses, strings, numbers} from './constants';
3030

31-
/** @const {!Array<string>} */
32-
const CB_PROTO_PROPS = ['checked', 'indeterminate'];
33-
3431
/**
3532
* @extends {MDCFoundation<!MDCCheckboxAdapter>}
3633
*/
@@ -57,7 +54,6 @@ class MDCCheckboxFoundation extends MDCFoundation {
5754
removeClass: (/* className: string */) => {},
5855
setNativeControlAttr: (/* attr: string, value: string */) => {},
5956
removeNativeControlAttr: (/* attr: string */) => {},
60-
getNativeControl: () => /* !MDCSelectionControlState */ {},
6157
forceLayout: () => {},
6258
isAttachedToDOM: () => /* boolean */ {},
6359
isIndeterminate: () => /* boolean */ {},
@@ -88,12 +84,10 @@ class MDCCheckboxFoundation extends MDCFoundation {
8884
this.currentCheckState_ = this.determineCheckState_();
8985
this.updateAriaChecked_();
9086
this.adapter_.addClass(cssClasses.UPGRADED);
91-
this.installPropertyChangeHooks_();
9287
}
9388

9489
/** @override */
9590
destroy() {
96-
this.uninstallPropertyChangeHooks_();
9791
clearTimeout(this.animEndLatchTimer_);
9892
}
9993

@@ -128,44 +122,6 @@ class MDCCheckboxFoundation extends MDCFoundation {
128122
this.transitionCheckState_();
129123
}
130124

131-
/** @private */
132-
installPropertyChangeHooks_() {
133-
const nativeCb = this.getNativeControl_();
134-
const cbProto = Object.getPrototypeOf(nativeCb);
135-
136-
CB_PROTO_PROPS.forEach((controlState) => {
137-
const desc = Object.getOwnPropertyDescriptor(cbProto, controlState);
138-
// We have to check for this descriptor, since some browsers (Safari) don't support its return.
139-
// See: https://bugs.webkit.org/show_bug.cgi?id=49739
140-
if (validDescriptor(desc)) {
141-
const nativeCbDesc = /** @type {!ObjectPropertyDescriptor} */ ({
142-
get: desc.get,
143-
set: (state) => {
144-
desc.set.call(nativeCb, state);
145-
this.transitionCheckState_();
146-
},
147-
configurable: desc.configurable,
148-
enumerable: desc.enumerable,
149-
});
150-
Object.defineProperty(nativeCb, controlState, nativeCbDesc);
151-
}
152-
});
153-
}
154-
155-
/** @private */
156-
uninstallPropertyChangeHooks_() {
157-
const nativeCb = this.getNativeControl_();
158-
const cbProto = Object.getPrototypeOf(nativeCb);
159-
160-
CB_PROTO_PROPS.forEach((controlState) => {
161-
const desc = /** @type {!ObjectPropertyDescriptor} */ (
162-
Object.getOwnPropertyDescriptor(cbProto, controlState));
163-
if (validDescriptor(desc)) {
164-
Object.defineProperty(nativeCb, controlState, desc);
165-
}
166-
});
167-
}
168-
169125
/** @private */
170126
transitionCheckState_() {
171127
if (!this.adapter_.hasNativeControl()) {
@@ -265,27 +221,6 @@ class MDCCheckboxFoundation extends MDCFoundation {
265221
this.adapter_.removeNativeControlAttr(strings.ARIA_CHECKED_ATTR);
266222
}
267223
}
268-
269-
/**
270-
* @return {!MDCSelectionControlState}
271-
* @private
272-
*/
273-
getNativeControl_() {
274-
return this.adapter_.getNativeControl() || {
275-
checked: false,
276-
indeterminate: false,
277-
disabled: false,
278-
value: null,
279-
};
280-
}
281-
}
282-
283-
/**
284-
* @param {ObjectPropertyDescriptor|undefined} inputPropDesc
285-
* @return {boolean}
286-
*/
287-
function validDescriptor(inputPropDesc) {
288-
return !!inputPropDesc && typeof inputPropDesc.set === 'function';
289224
}
290225

291226
export default MDCCheckboxFoundation;

packages/mdc-checkbox/index.js

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ import MDCCheckboxFoundation from './foundation';
3030
import {MDCRipple, MDCRippleFoundation} from '@material/ripple/index';
3131
import {getMatchesProperty} from '@material/ripple/util';
3232

33+
/** @const {!Array<string>} */
34+
const CB_PROTO_PROPS = ['checked', 'indeterminate'];
35+
3336
/**
3437
* @extends MDCComponent<!MDCCheckboxFoundation>
3538
* @implements {MDCSelectionControl}
@@ -41,12 +44,12 @@ class MDCCheckbox extends MDCComponent {
4144

4245
/**
4346
* Returns the state of the native control element, or null if the native control element is not present.
44-
* @return {?MDCSelectionControlState}
47+
* @return {!MDCSelectionControlState}
4548
* @private
4649
*/
4750
get nativeCb_() {
4851
const {NATIVE_CONTROL_SELECTOR} = MDCCheckboxFoundation.strings;
49-
const cbEl = /** @type {?MDCSelectionControlState} */ (
52+
const cbEl = /** @type {!MDCSelectionControlState} */ (
5053
this.root_.querySelector(NATIVE_CONTROL_SELECTOR));
5154
return cbEl;
5255
}
@@ -67,6 +70,7 @@ class MDCCheckbox extends MDCComponent {
6770
this.handleAnimationEnd_= () => this.foundation_.handleAnimationEnd();
6871
this.nativeCb_.addEventListener('change', this.handleChange_);
6972
this.listen(getCorrectEventName(window, 'animationend'), this.handleAnimationEnd_);
73+
this.installPropertyChangeHooks_();
7074
}
7175

7276
/**
@@ -85,14 +89,51 @@ class MDCCheckbox extends MDCComponent {
8589
return new MDCRipple(this.root_, foundation);
8690
}
8791

92+
/** @private */
93+
installPropertyChangeHooks_() {
94+
const nativeCb = this.nativeCb_;
95+
const cbProto = Object.getPrototypeOf(nativeCb);
96+
97+
CB_PROTO_PROPS.forEach((controlState) => {
98+
const desc = Object.getOwnPropertyDescriptor(cbProto, controlState);
99+
// We have to check for this descriptor, since some browsers (Safari) don't support its return.
100+
// See: https://bugs.webkit.org/show_bug.cgi?id=49739
101+
if (validDescriptor(desc)) {
102+
const nativeCbDesc = /** @type {!ObjectPropertyDescriptor} */ ({
103+
get: desc.get,
104+
set: (state) => {
105+
desc.set.call(nativeCb, state);
106+
this.foundation_.handleChange();
107+
},
108+
configurable: desc.configurable,
109+
enumerable: desc.enumerable,
110+
});
111+
Object.defineProperty(nativeCb, controlState, nativeCbDesc);
112+
}
113+
});
114+
}
115+
116+
/** @private */
117+
uninstallPropertyChangeHooks_() {
118+
const nativeCb = this.nativeCb_;
119+
const cbProto = Object.getPrototypeOf(nativeCb);
120+
121+
CB_PROTO_PROPS.forEach((controlState) => {
122+
const desc = /** @type {!ObjectPropertyDescriptor} */ (
123+
Object.getOwnPropertyDescriptor(cbProto, controlState));
124+
if (validDescriptor(desc)) {
125+
Object.defineProperty(nativeCb, controlState, desc);
126+
}
127+
});
128+
}
129+
88130
/** @return {!MDCCheckboxFoundation} */
89131
getDefaultFoundation() {
90132
return new MDCCheckboxFoundation({
91133
addClass: (className) => this.root_.classList.add(className),
92134
removeClass: (className) => this.root_.classList.remove(className),
93135
setNativeControlAttr: (attr, value) => this.nativeCb_.setAttribute(attr, value),
94136
removeNativeControlAttr: (attr) => this.nativeCb_.removeAttribute(attr),
95-
getNativeControl: () => this.nativeCb_,
96137
isIndeterminate: () => this.indeterminate,
97138
isChecked: () => this.checked,
98139
hasNativeControl: () => !!this.nativeCb_,
@@ -151,8 +192,17 @@ class MDCCheckbox extends MDCComponent {
151192
this.ripple_.destroy();
152193
this.nativeCb_.removeEventListener('change', this.handleChange_);
153194
this.unlisten(getCorrectEventName(window, 'animationend'), this.handleAnimationEnd_);
195+
this.uninstallPropertyChangeHooks_();
154196
super.destroy();
155197
}
156198
}
157199

200+
/**
201+
* @param {ObjectPropertyDescriptor|undefined} inputPropDesc
202+
* @return {boolean}
203+
*/
204+
function validDescriptor(inputPropDesc) {
205+
return !!inputPropDesc && typeof inputPropDesc.set === 'function';
206+
}
207+
158208
export {MDCCheckboxFoundation, MDCCheckbox};

test/unit/mdc-checkbox/foundation.test.js

Lines changed: 10 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ const DESC_UNDEFINED = {
4141
function setupTest() {
4242
const {foundation, mockAdapter} = setupFoundationTest(MDCCheckboxFoundation);
4343
const nativeControl = bel`<input type="checkbox">`;
44-
td.when(mockAdapter.getNativeControl()).thenReturn(nativeControl);
4544
return {foundation, mockAdapter, nativeControl};
4645
}
4746

@@ -80,11 +79,9 @@ function setupChangeHandlerTest() {
8079
foundation.init();
8180

8281
const change = (newState) => {
83-
td.when(mockAdapter.hasNativeControl()).thenReturn(!!newState);
84-
if (newState) {
85-
td.when(mockAdapter.isChecked()).thenReturn(newState.checked);
86-
td.when(mockAdapter.isIndeterminate()).thenReturn(newState.indeterminate);
87-
}
82+
td.when(mockAdapter.hasNativeControl()).thenReturn(true);
83+
td.when(mockAdapter.isChecked()).thenReturn(newState.checked);
84+
td.when(mockAdapter.isIndeterminate()).thenReturn(newState.indeterminate);
8885
foundation.handleChange();
8986
};
9087

@@ -116,7 +113,7 @@ test('exports numbers', () => {
116113

117114
test('defaultAdapter returns a complete adapter implementation', () => {
118115
verifyDefaultAdapter(MDCCheckboxFoundation, [
119-
'addClass', 'removeClass', 'setNativeControlAttr', 'removeNativeControlAttr', 'getNativeControl',
116+
'addClass', 'removeClass', 'setNativeControlAttr', 'removeNativeControlAttr',
120117
'forceLayout', 'isAttachedToDOM', 'isIndeterminate', 'isChecked', 'hasNativeControl', 'setNativeControlDisabled',
121118
]);
122119
});
@@ -136,12 +133,6 @@ test('#init adds aria-checked="mixed" if checkbox is initially indeterminate', (
136133
td.verify(mockAdapter.setNativeControlAttr('aria-checked', strings.ARIA_CHECKED_INDETERMINATE_VALUE));
137134
});
138135

139-
test('#init handles case where getNativeControl() does not return anything', () => {
140-
const {foundation, mockAdapter} = setupTest();
141-
td.when(mockAdapter.getNativeControl()).thenReturn(undefined);
142-
assert.doesNotThrow(() => foundation.init());
143-
});
144-
145136
test('#init handles case when WebIDL attrs cannot be overridden (Safari)', () => {
146137
const {foundation, nativeControl} = setupTest();
147138
withMockCheckboxDescriptorReturning(DESC_UNDEFINED, () => {
@@ -159,14 +150,6 @@ test('#init handles case when property descriptors are not returned at all (Andr
159150
});
160151
});
161152

162-
test('#destroy handles case where getNativeControl() does not return anything', () => {
163-
const {foundation, mockAdapter} = setupTest();
164-
foundation.init();
165-
166-
td.when(mockAdapter.getNativeControl()).thenReturn(undefined);
167-
assert.doesNotThrow(() => foundation.destroy());
168-
});
169-
170153
test('#destroy handles case when WebIDL attrs cannot be overridden (Safari)', () => {
171154
const {foundation} = setupTest();
172155
withMockCheckboxDescriptorReturning(DESC_UNDEFINED, () => {
@@ -328,46 +311,10 @@ test('change handler does not add animation classes for bogus changes (init -> u
328311
td.verify(mockAdapter.addClass(animClassArg), {times: 0});
329312
});
330313

331-
test('change handler gracefully exits when getNativeControl() returns nothing', () => {
332-
const {change} = setupChangeHandlerTest();
333-
assert.doesNotThrow(() => change(undefined));
334-
});
335-
336-
test('"checked" property change hook works correctly', () => {
337-
const {foundation, mockAdapter, nativeControl} = setupTest();
338-
td.when(mockAdapter.isAttachedToDOM()).thenReturn(true);
339-
td.when(mockAdapter.hasNativeControl()).thenReturn(true);
340-
341-
withMockCheckboxDescriptorReturning({
342-
get: () => {},
343-
set: () => {},
344-
enumerable: false,
345-
configurable: true,
346-
}, () => {
347-
foundation.init();
348-
td.when(mockAdapter.isChecked()).thenReturn(true);
349-
td.when(mockAdapter.isIndeterminate()).thenReturn(false);
350-
nativeControl.checked = !nativeControl.checked;
351-
td.verify(mockAdapter.addClass(cssClasses.ANIM_UNCHECKED_CHECKED));
352-
});
353-
});
354-
355-
test('"indeterminate" property change hook works correctly', () => {
356-
const {foundation, mockAdapter, nativeControl} = setupTest();
357-
td.when(mockAdapter.isAttachedToDOM()).thenReturn(true);
358-
td.when(mockAdapter.hasNativeControl()).thenReturn(true);
359-
360-
withMockCheckboxDescriptorReturning({
361-
get: () => {},
362-
set: () => {},
363-
enumerable: false,
364-
configurable: true,
365-
}, () => {
366-
foundation.init();
367-
td.when(mockAdapter.isChecked()).thenReturn(false);
368-
td.when(mockAdapter.isIndeterminate()).thenReturn(true);
369-
370-
nativeControl.indeterminate = !nativeControl.indeterminate;
371-
td.verify(mockAdapter.addClass(cssClasses.ANIM_UNCHECKED_INDETERMINATE));
372-
});
314+
test('change handler does not do anything if checkbox element is not found', () => {
315+
const {foundation, mockAdapter} = setupTest();
316+
td.when(mockAdapter.hasNativeControl()).thenReturn(false);
317+
assert.doesNotThrow(() => foundation.handleChange());
318+
td.verify(mockAdapter.setNativeControlAttr(td.matchers.isA(String), td.matchers.isA(String)), {times: 0});
319+
td.verify(mockAdapter.removeNativeControlAttr(td.matchers.isA(String)), {times: 0});
373320
});

0 commit comments

Comments
 (0)