Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(radio): remove getNativeControl from adapter #3785

Merged
merged 5 commits into from
Oct 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
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
29 changes: 12 additions & 17 deletions packages/mdc-radio/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ formField.input = radio;

## Variants

### Disabled
### Disabled

To disable a radio button, add the `mdc-radio--disabled` class to the root element and set the `disabled` attribute on the `<input>` element.
Disabled radio buttons cannot be interacted with and have no visual interaction effect.
Expand Down Expand Up @@ -116,29 +116,24 @@ In browsers that fully support CSS custom properties, the above mixins will work

Property | Value Type | Description
--- | --- | ---
`checked` | Boolean | Proxies to the foundation's `isChecked`/`setChecked` methods
`disabled` | Boolean | Proxies to the foundation's `isDisabled/setDisabled` methods
`value` | String | Proxies to the foundation's `getValue/setValue` methods
`checked` | Boolean | Setter/getter for the radio's checked state
`disabled` | Boolean | Setter/getter for the radio's disabled state. Setter proxies to foundation's `setDisabled` method
`value` | String | Setter/getter for the radio's value

## Usage within Web Frameworks

If you are using a JavaScript framework, such as React or Angular, you can create a Radio button for your framework. Depending on your needs, you can use the _Simple Approach: Wrapping MDC Web Vanilla Components_, or the _Advanced Approach: Using Foundations and Adapters_. Please follow the instructions [here](../../docs/integrating-into-frameworks.md).

### `MDCRadioAdapter`

| Method Signature | Description |
| --- | --- |
| `getNativeControl() => HTMLInputElement?` | Returns the native radio control, if available |
| `addClass(className: string) => void` | Adds a class to the root element |
| `removeClass(className: string) => void` | Removes a class from the root element |
Method Signature | Description
--- | ---
`setNativeControlDisabled(disabled: boolean) => void` | Sets the input's `disabled` property to the given value
`addClass(className: string) => void` | Adds a class to the root element
`removeClass(className: string) => void` | Removes a class from the root element

### `MDCRadioFoundation`

| Method Signature | Description |
| --- | --- |
| `isChecked() => boolean` | Returns whether the native control is checked, or `false` if there's no native control |
| `setChecked(checked: boolean) => void` | Sets the checked value of the native control |
| `isDisabled() => boolean` | Returns whether the native control is disabled, or `false` if there's no native control |
| `setDisabled(disabled: boolean) => void` | Sets the disabled value of the native control |
| `getValue() => string` | Returns the value of the native control, or `null` if there's no native control |
| `setValue(value: string) => void` | Sets the value of the native control |
Method Signature | Description
--- | ---
`setDisabled(disabled: boolean) => void` | Sets the disabled value of the native control
4 changes: 2 additions & 2 deletions packages/mdc-radio/adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ class MDCRadioAdapter {
/** @param {string} className */
removeClass(className) {}

/** @return {!MDCSelectionControlState} */
getNativeControl() {}
/** @param {boolean} disabled */
setNativeControlDisabled(disabled) {}
}

export default MDCRadioAdapter;
41 changes: 2 additions & 39 deletions packages/mdc-radio/foundation.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,57 +47,20 @@ class MDCRadioFoundation extends MDCFoundation {
return /** @type {!MDCRadioAdapter} */ ({
addClass: (/* className: string */) => {},
removeClass: (/* className: string */) => {},
getNativeControl: () => /* !MDCSelectionControlState */ {},
setNativeControlDisabled: (/* disabled: boolean */) => {},
});
}

/** @return {boolean} */
isChecked() {
return this.getNativeControl_().checked;
}

/** @param {boolean} checked */
setChecked(checked) {
this.getNativeControl_().checked = checked;
}

/** @return {boolean} */
isDisabled() {
return this.getNativeControl_().disabled;
}

/** @param {boolean} disabled */
setDisabled(disabled) {
const {DISABLED} = MDCRadioFoundation.cssClasses;
this.getNativeControl_().disabled = disabled;
this.adapter_.setNativeControlDisabled(disabled);
if (disabled) {
this.adapter_.addClass(DISABLED);
} else {
this.adapter_.removeClass(DISABLED);
}
}

/** @return {?string} */
getValue() {
return this.getNativeControl_().value;
}

/** @param {?string} value */
setValue(value) {
this.getNativeControl_().value = value;
}

/**
* @return {!MDCSelectionControlState}
* @private
*/
getNativeControl_() {
return this.adapter_.getNativeControl() || {
checked: false,
disabled: false,
value: null,
};
}
}

export default MDCRadioFoundation;
12 changes: 6 additions & 6 deletions packages/mdc-radio/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,17 @@ class MDCRadio extends MDCComponent {

/** @return {boolean} */
get checked() {
return this.foundation_.isChecked();
return this.nativeControl_.checked;
}

/** @param {boolean} checked */
set checked(checked) {
this.foundation_.setChecked(checked);
this.nativeControl_.checked = checked;
}

/** @return {boolean} */
get disabled() {
return this.foundation_.isDisabled();
return this.nativeControl_.disabled;
}

/** @param {boolean} disabled */
Expand All @@ -59,12 +59,12 @@ class MDCRadio extends MDCComponent {

/** @return {?string} */
get value() {
return this.foundation_.getValue();
return this.nativeControl_.value;
}

/** @param {?string} value */
set value(value) {
this.foundation_.setValue(value);
this.nativeControl_.value = value;
}

/** @return {!MDCRipple} */
Expand Down Expand Up @@ -118,7 +118,7 @@ class MDCRadio extends MDCComponent {
return new MDCRadioFoundation({
addClass: (className) => this.root_.classList.add(className),
removeClass: (className) => this.root_.classList.remove(className),
getNativeControl: () => this.root_.querySelector(MDCRadioFoundation.strings.NATIVE_CONTROL_SELECTOR),
setNativeControlDisabled: (disabled) => this.nativeControl_.disabled = disabled,
});
}
}
Expand Down
82 changes: 3 additions & 79 deletions test/unit/mdc-radio/foundation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ test('defaultAdapter returns a complete adapter implementation', () => {
const methods = Object.keys(defaultAdapter).filter((k) => typeof defaultAdapter[k] === 'function');

assert.equal(methods.length, Object.keys(defaultAdapter).length, 'Every adapter key must be a function');
assert.deepEqual(methods, ['addClass', 'removeClass', 'getNativeControl']);
assert.deepEqual(methods, ['addClass', 'removeClass', 'setNativeControlDisabled']);
methods.forEach((m) => assert.doesNotThrow(defaultAdapter[m]));
});

Expand All @@ -51,96 +51,20 @@ function setupTest() {
return {foundation, mockAdapter};
}

test('#isChecked returns the value of nativeControl.checked', () => {
test('#setDisabled calls adapter.setNativeControlDisabled', () => {
const {foundation, mockAdapter} = setupTest();
td.when(mockAdapter.getNativeControl()).thenReturn({checked: true});
assert.isOk(foundation.isChecked());
});

test('#isChecked returns false if getNativeControl() does not return anything', () => {
const {foundation, mockAdapter} = setupTest();
td.when(mockAdapter.getNativeControl()).thenReturn(null);
assert.isNotOk(foundation.isChecked());
});

test('#setChecked sets the value of nativeControl.checked', () => {
const {foundation, mockAdapter} = setupTest();
const nativeControl = {checked: false};
td.when(mockAdapter.getNativeControl()).thenReturn(nativeControl);
foundation.setChecked(true);
assert.isOk(nativeControl.checked);
});

test('#setChecked exits gracefully if getNativeControl() does not return anything', () => {
const {foundation, mockAdapter} = setupTest();
td.when(mockAdapter.getNativeControl()).thenReturn(null);
assert.doesNotThrow(() => foundation.setChecked(true));
});

test('#isDisabled returns the value of nativeControl.disabled', () => {
const {foundation, mockAdapter} = setupTest();
td.when(mockAdapter.getNativeControl()).thenReturn({disabled: true});
assert.isOk(foundation.isDisabled());
});

test('#isDisabled returns false if getNativeControl() does not return anything', () => {
const {foundation, mockAdapter} = setupTest();
td.when(mockAdapter.getNativeControl()).thenReturn(null);
assert.isNotOk(foundation.isDisabled());
});

test('#setDisabled sets the value of nativeControl.disabled', () => {
const {foundation, mockAdapter} = setupTest();
const nativeControl = {disabled: false};
td.when(mockAdapter.getNativeControl()).thenReturn(nativeControl);
foundation.setDisabled(true);
assert.isOk(nativeControl.disabled);
td.verify(mockAdapter.setNativeControlDisabled(true), {times: 1});
});

test('#setDisabled adds mdc-radio--disabled to the radio element when set to true', () => {
const {foundation, mockAdapter} = setupTest();
const nativeControl = {disabled: false};
td.when(mockAdapter.getNativeControl()).thenReturn(nativeControl);
foundation.setDisabled(true);
td.verify(mockAdapter.addClass(MDCRadioFoundation.cssClasses.DISABLED));
});

test('#setDisabled removes mdc-radio--disabled from the radio element when set to false', () => {
const {foundation, mockAdapter} = setupTest();
const nativeControl = {disabled: true};
td.when(mockAdapter.getNativeControl()).thenReturn(nativeControl);
foundation.setDisabled(false);
td.verify(mockAdapter.removeClass(MDCRadioFoundation.cssClasses.DISABLED));
});

test('#setDisabled exits gracefully if getNativeControl() does not return anything', () => {
const {foundation, mockAdapter} = setupTest();
td.when(mockAdapter.getNativeControl()).thenReturn(null);
assert.doesNotThrow(() => foundation.setDisabled(true));
});

test('#getValue returns the value of nativeControl.value', () => {
const {foundation, mockAdapter} = setupTest();
td.when(mockAdapter.getNativeControl()).thenReturn({value: 'value'});
assert.equal(foundation.getValue(), 'value');
});

test('#getValue returns null if getNativeControl() does not return anything', () => {
const {foundation, mockAdapter} = setupTest();
td.when(mockAdapter.getNativeControl()).thenReturn(null);
assert.isNull(foundation.getValue());
});

test('#setValue sets the value of nativeControl.value', () => {
const {foundation, mockAdapter} = setupTest();
const nativeControl = {value: null};
td.when(mockAdapter.getNativeControl()).thenReturn(nativeControl);
foundation.setValue('new value');
assert.equal(nativeControl.value, 'new value');
});

test('#setValue exits gracefully if getNativeControl() does not return anything', () => {
const {foundation, mockAdapter} = setupTest();
td.when(mockAdapter.getNativeControl()).thenReturn(null);
assert.doesNotThrow(() => foundation.setValue('new value'));
});
31 changes: 27 additions & 4 deletions test/unit/mdc-radio/mdc-radio.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,32 @@ test('get ripple returns a MDCRipple instance', () => {
assert.isOk(component.ripple instanceof MDCRipple);
});

test('#adapter.getNativeControl() returns the native radio element', () => {
test('adapter#addClass adds a class to the root element', () => {
const {root, component} = setupTest();
assert.equal(
component.getDefaultFoundation().adapter_.getNativeControl(), root.querySelector(NATIVE_CONTROL_SELECTOR)
);
component.getDefaultFoundation().adapter_.addClass('foo');
assert.isTrue(root.classList.contains('foo'));
});

test('adapter#removeClass removes a class from the root element', () => {
const {root, component} = setupTest();
root.classList.add('foo');
component.getDefaultFoundation().adapter_.removeClass('foo');
assert.isFalse(root.classList.contains('foo'));
});

test('#adapter.setNativeControlDisabled sets the native control element\'s disabled property to true', () => {
const {root, component} = setupTest();
const radio = root.querySelector(NATIVE_CONTROL_SELECTOR);

component.getDefaultFoundation().adapter_.setNativeControlDisabled(true);
assert.isTrue(radio.disabled);
});

test('#adapter.setNativeControlDisabled sets the native control element\'s disabled property to false', () => {
const {root, component} = setupTest();
const radio = root.querySelector(NATIVE_CONTROL_SELECTOR);
radio.disabled = true;

component.getDefaultFoundation().adapter_.setNativeControlDisabled(false);
assert.isFalse(radio.disabled);
});