+ );
+};
+
+OnOffToggle.propTypes = {
+ /**
+ * `true` if turned on, `false` if off.
+ */
+ value: PropTypes.bool.isRequired,
+ /**
+ * Function to call when the value is changed.
+ * Will receive the new `boolean` value.
+ */
+ setValue: PropTypes.func.isRequired,
+ /**
+ * Used as the HTML `name` attribute of the form elements,
+ * and also used to formulate the `id`s.
+ */
+ name: PropTypes.string.isRequired,
+ /**
+ * Common prefix for looking up the "On" and "Off" ARIA labels.
+ * If the ARIA label is t('Preferences.LintWarningOnARIA'),
+ * then the `translationKey` is `LintWarning`.
+ */
+ translationKey: PropTypes.string,
+ /**
+ * Can insert additional elements in the same
as the inputs.
+ * Typically not used.
+ */
+ children: PropTypes.node
+};
+
+OnOffToggle.defaultProps = {
+ translationKey: '',
+ children: null
+};
+
+export default OnOffToggle;
diff --git a/client/modules/IDE/components/Preferences/OnOffToggle.test.jsx b/client/modules/IDE/components/Preferences/OnOffToggle.test.jsx
new file mode 100644
index 0000000000..05f76bb0b7
--- /dev/null
+++ b/client/modules/IDE/components/Preferences/OnOffToggle.test.jsx
@@ -0,0 +1,60 @@
+import React from 'react';
+import { render, screen, fireEvent } from '../../../../test-utils';
+import OnOffToggle from './OnOffToggle';
+
+describe('OnOffToggle', () => {
+ const setValue = jest.fn();
+
+ const subject = (initialValue = false) => {
+ const result = render(
+
+ );
+ return {
+ ...result,
+ onButton: screen.getByLabelText('On'),
+ offButton: screen.getByLabelText('Off')
+ };
+ };
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('highlights "On" when value is true', () => {
+ const { onButton, offButton } = subject(true);
+
+ expect(onButton).toBeChecked();
+ expect(offButton).not.toBeChecked();
+ });
+
+ it('highlights "Off" when value is false', () => {
+ const { onButton, offButton } = subject(false);
+
+ expect(onButton).not.toBeChecked();
+ expect(offButton).toBeChecked();
+ });
+
+ it('does nothing when clicking the active value', () => {
+ const { onButton } = subject(true);
+
+ fireEvent.click(onButton);
+
+ expect(setValue).not.toHaveBeenCalled();
+ });
+
+ it('calls setValue when clicking the inactive value', () => {
+ const { offButton } = subject(true);
+
+ fireEvent.click(offButton);
+
+ expect(setValue).toHaveBeenCalledTimes(1);
+ expect(setValue).toHaveBeenCalledWith(false);
+
+ // Note: the checked state will not change until the `value` prop changes.
+ });
+});
diff --git a/client/modules/IDE/components/Preferences/index.jsx b/client/modules/IDE/components/Preferences/index.jsx
index fa5859400e..c5ef87b4a8 100644
--- a/client/modules/IDE/components/Preferences/index.jsx
+++ b/client/modules/IDE/components/Preferences/index.jsx
@@ -18,6 +18,7 @@ import {
setAutocompleteHinter,
setLinewrap
} from '../../actions/preferences';
+import OnOffToggle from './OnOffToggle';
export default function Preferences() {
const { t } = useTranslation();
@@ -196,143 +197,43 @@ export default function Preferences() {