From 71254a4e0bc839d6eef64aae91105e56d3fdebe0 Mon Sep 17 00:00:00 2001 From: Cristiano Rastelli Date: Wed, 14 May 2025 16:45:53 +0100 Subject: [PATCH] [VAULT-34808] UI: move the `radio` block in `FormField` under the HDS block (#30555) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [UI] moved template logic for `radio` editType in `FormField` under the `isHdsField` block (#34742) * [UI] added integration tests for `FormField` with editType=‘radio’ (#34742) * [UI] fix broken tests (#34742) --- ui/lib/core/addon/components/form-field.hbs | 80 +++---- ui/lib/core/addon/components/form-field.js | 20 +- .../secrets/backend/totp/key-test.js | 2 +- .../sync/secrets/destinations-test.js | 2 +- ui/tests/helpers/general-selectors.ts | 1 + ui/tests/helpers/sync/sync-selectors.js | 8 +- .../integration/components/form-field-test.js | 215 +++++++++++++----- .../page/role/create-and-edit-test.js | 2 +- .../ldap/page/library/create-and-edit-test.js | 6 +- .../components/mfa/method-form-test.js | 2 +- .../components/oidc/client-form-test.js | 6 +- .../components/totp/key-form-test.js | 4 +- ui/tests/pages/components/form-field.js | 1 - 13 files changed, 225 insertions(+), 124 deletions(-) diff --git a/ui/lib/core/addon/components/form-field.hbs b/ui/lib/core/addon/components/form-field.hbs index b494632128..d25c840dd4 100644 --- a/ui/lib/core/addon/components/form-field.hbs +++ b/ui/lib/core/addon/components/form-field.hbs @@ -10,7 +10,39 @@ {{! HDS COMPONENTS - START }} {{! •••••••••••••••••••••••••••••••••••••••••••••••••••••••• }} {{#if @attr.options.possibleValues}} - {{#if (eq @attr.options.editType "checkboxList")}} + {{#if (eq @attr.options.editType "radio")}} + + {{#each (path-or-array @attr.options.possibleValues @model) as |val|}} + + + {{or val.label val.value val}} + + {{! Note: if we have both `subText` and `helpText`, we display only the `subText` because in these situations, the helpText is likely there to clarify or improve upon the OpenAPI-generated text }} + {{#if this.hasRadioSubText}} + {{val.subText}} + {{else if this.hasRadioHelpText}} + {{val.helpText}} + {{/if}} + + {{/each}} + {{#if this.validationError}} + {{this.validationError}} + {{/if}} + + {{else if (eq @attr.options.editType "checkboxList")}} {{#if this.labelString}} {{this.labelString}} @@ -143,51 +175,7 @@ @docLink={{@attr.options.docLink}} /> {{/unless}} - {{#if @attr.options.possibleValues}} - {{#if (eq @attr.options.editType "radio")}} -
- {{#each (path-or-array @attr.options.possibleValues @model) as |val|}} -
- -
- -
-
- {{/each}} -
- {{/if}} - {{else if (eq @attr.options.editType "dateTimeLocal")}} + {{#if (eq @attr.options.editType "dateTimeLocal")}} 0) { - // we still have to migrate the `radio` use case - if (options?.editType === 'radio') { - return false; - } else { - return true; - } + return true; } else { if (type === 'number' || type === 'string') { if (options?.editType === 'password') { @@ -116,10 +111,15 @@ export default class FormFieldComponent extends Component { } get hasRadioSubText() { - // for 'radio' editType, check to see if every of the possibleValues has a subText and label + // for 'radio' editType, check to see if any of the possibleValues has a subText return this.args?.attr?.options?.possibleValues?.any((v) => v.subText); } + get hasRadioHelpText() { + // for 'radio' editType, check to see if any of the possibleValues has a helpText + return this.args?.attr?.options?.possibleValues?.any((v) => v.helpText); + } + get hideLabel() { const { type, options } = this.args.attr; if (type === 'boolean' || type === 'object' || options?.isSectionHeader) { @@ -205,6 +205,12 @@ export default class FormFieldComponent extends Component { this.setAndBroadcast(updatedValue); } @action + setAndBroadcastRadio(item) { + // we want to read the original value instead of `event.target.value` so we have `false` (boolean) and not `"false"` (string) + const valueToSet = this.radioValue(item); + this.setAndBroadcast(valueToSet); + } + @action setAndBroadcastTtl(value) { const alwaysSendValue = this.valuePath === 'expiry' || this.valuePath === 'safetyBuffer'; const attrOptions = this.args.attr.options || {}; diff --git a/ui/tests/acceptance/secrets/backend/totp/key-test.js b/ui/tests/acceptance/secrets/backend/totp/key-test.js index 06d2168eaa..179f6f2a05 100644 --- a/ui/tests/acceptance/secrets/backend/totp/key-test.js +++ b/ui/tests/acceptance/secrets/backend/totp/key-test.js @@ -27,7 +27,7 @@ module('Acceptance | totp key backend', function (hooks) { }; const createNonVaultKey = async (keyName, issuer, accountName, url, key) => { - await click('[data-test-radio="Other service"]'); + await click(GENERAL.radioByAttr('Other service')); await fillIn(GENERAL.inputByAttr('name'), keyName); await fillIn(GENERAL.inputByAttr('issuer'), issuer); await fillIn(GENERAL.inputByAttr('accountName'), accountName); diff --git a/ui/tests/acceptance/sync/secrets/destinations-test.js b/ui/tests/acceptance/sync/secrets/destinations-test.js index f34e3694c9..e9e1c7b362 100644 --- a/ui/tests/acceptance/sync/secrets/destinations-test.js +++ b/ui/tests/acceptance/sync/secrets/destinations-test.js @@ -78,7 +78,7 @@ module('Acceptance | sync | destinations (plural)', function (hooks) { // check default values const attr = 'granularity'; assert - .dom(`${ts.inputByAttr(attr)} input#${defaultValues[attr]}`) + .dom(`${ts.inputGroupByAttr(attr)} input#${defaultValues[attr]}`) .isChecked(`${defaultValues[attr]} is checked`); }); } diff --git a/ui/tests/helpers/general-selectors.ts b/ui/tests/helpers/general-selectors.ts index 2ea6cccd4c..b28ef3c66e 100644 --- a/ui/tests/helpers/general-selectors.ts +++ b/ui/tests/helpers/general-selectors.ts @@ -52,6 +52,7 @@ export const GENERAL = { inputGroupByAttr: (attr: string) => `[data-test-input-group="${attr}"]`, labelById: (id: string) => `label[id="${id}"]`, labelByGroupControlIndex: (index: number) => `.hds-form-group__control-field:nth-of-type(${index}) label`, + radioByAttr: (attr: string) => `[data-test-radio="${attr}"]`, selectByAttr: (attr: string) => `[data-test-select="${attr}"]`, textToggle: '[data-test-text-toggle]', textToggleTextarea: '[data-test-text-file-textarea]', diff --git a/ui/tests/helpers/sync/sync-selectors.js b/ui/tests/helpers/sync/sync-selectors.js index 25233e1791..64b812ae8c 100644 --- a/ui/tests/helpers/sync/sync-selectors.js +++ b/ui/tests/helpers/sync/sync-selectors.js @@ -92,7 +92,7 @@ export const PAGE = { // for handling more complex form input elements by attr name switch (attr) { case 'granularity': - return await click(`[data-test-radio="secret-key"]`); + return await click(`${GENERAL.radioByAttr('secret-key')}`); case 'credentials': await click('[data-test-text-toggle]'); return fillIn('[data-test-text-file-textarea]', value); @@ -100,9 +100,9 @@ export const PAGE = { await fillIn('[data-test-kv-key="0"]', 'foo'); return fillIn('[data-test-kv-value="0"]', value); case 'deploymentEnvironments': - await click('[data-test-input-group="deploymentEnvironments"] input#development'); - await click('[data-test-input-group="deploymentEnvironments"] input#preview'); - return await click('[data-test-input-group="deploymentEnvironments"] input#production'); + await click(`${GENERAL.inputGroupByAttr('deploymentEnvironments')} input#development`); + await click(`${GENERAL.inputGroupByAttr('deploymentEnvironments')} input#preview`); + return await click(`${GENERAL.inputGroupByAttr('deploymentEnvironments')} input#production`); default: return fillIn(`[data-test-input="${attr}"]`, value); } diff --git a/ui/tests/integration/components/form-field-test.js b/ui/tests/integration/components/form-field-test.js index cdfb4cdbd8..5eee4dd620 100644 --- a/ui/tests/integration/components/form-field-test.js +++ b/ui/tests/integration/components/form-field-test.js @@ -202,61 +202,6 @@ module('Integration | Component | form field', function (hooks) { assert.ok(spy.calledWith('foo', expectedSeconds), 'onChange called with correct args'); }); - test('it renders: radio buttons for possible values', async function (assert) { - const [model, spy] = await setup.call( - this, - createAttr('foo', null, { editType: 'radio', possibleValues: ['SHA1', 'SHA256'] }) - ); - assert.ok(component.hasRadio, 'renders radio buttons'); - const selectedValue = 'SHA256'; - await component.selectRadioInput(selectedValue); - assert.strictEqual(model.get('foo'), selectedValue); - assert.ok(spy.calledWith('foo', selectedValue), 'onChange called with correct args'); - }); - test('it renders: radio buttons for possible values, labels, and subtext', async function (assert) { - const [model, spy] = await setup.call( - this, - createAttr('foo', null, { - editType: 'radio', - possibleValues: [ - { label: 'Label 1', subText: 'Some subtext 1', value: 'SHA1' }, - { label: 'Label 2', subText: 'Some subtext 2', value: 'SHA256' }, - { subText: 'Some subtext 3', value: 'SHA256' }, - ], - }) - ); - assert.ok(component.hasRadio, 'renders radio buttons'); - const selectedValue = 'SHA256'; - await component.selectRadioInput(selectedValue); - assert.dom('[data-test-radio-label="Label 1"]').hasTextContaining('Label 1'); - assert.dom('[data-test-radio-label="Label 2"]').hasTextContaining('Label 2'); - assert.dom('[data-test-radio-label="SHA256"]').hasTextContaining('SHA256'); - assert.dom('[data-test-radio-subText="Some subtext 1"]').hasText('Some subtext 1'); - assert.dom('[data-test-radio-subText="Some subtext 2"]').hasText('Some subtext 2'); - assert.dom('[data-test-radio-subText="Some subtext 3"]').hasText('Some subtext 3'); - assert.strictEqual(model.get('foo'), selectedValue); - assert.ok(spy.calledWith('foo', selectedValue), 'onChange called with correct args'); - }); - test('it renders: radio buttons false value and id', async function (assert) { - const [model, spy] = await setup.call( - this, - createAttr('foo', null, { - editType: 'radio', - possibleValues: [ - { label: 'True option', value: true, id: 'true-option' }, - { label: 'False option', value: false, id: 'false-option' }, - ], - }) - ); - - assert.dom('[data-test-radio-label="True option"]').hasTextContaining('True option'); - assert.dom('[data-test-radio-label="False option"]').hasTextContaining('False option'); - assert.dom('[data-test-radio="true-option"]').hasAttribute('id', 'true-option'); - assert.dom('[data-test-radio="false-option"]').hasAttribute('id', 'false-option'); - await component.selectRadioInput('false-option'); - assert.false(model.get('foo')); - assert.ok(spy.calledWith('foo', false), 'onChange called with correct args'); - }); test('it renders: datetimelocal', async function (assert) { const [model] = await setup.call( this, @@ -360,6 +305,166 @@ module('Integration | Component | form field', function (hooks) { // Note: some tests may be duplicative of the generic tests above // + // ––––– editType === 'radio' / possibleValues ––––– + + test('it renders: editType=radio / possibleValues - as Hds::Form::Radio::Group', async function (assert) { + const possibleValues = ['foo', 'bar', 'baz']; + await setup.call(this, createAttr('myfield', '-', { editType: 'radio', possibleValues })); + const labels = findAll(`${GENERAL.inputGroupByAttr('myfield')} label`); + const inputs = findAll(`${GENERAL.inputGroupByAttr('myfield')} input[type="radio"]`); + assert + .dom('.field fieldset[class^="hds-form-group"] input[type="radio"].hds-form-radio') + .exists('renders as Hds::Form::Radio::Group'); + assert.strictEqual(inputs.length, 3, 'renders a fieldset element with 3 radio elements'); + possibleValues.forEach((possibleValue, index) => { + assert + .dom(labels[index]) + .hasAttribute('id', `label-${possibleValue}`, 'label has correct id') + .hasText(possibleValue, 'label has correct text'); + assert + .dom(inputs[index]) + .hasAttribute('id', possibleValue, 'input[type="radio"] has correct id') + .hasAttribute( + 'data-test-radio', + possibleValue, + 'input[type="radio"] has correct `data-test-radio` attribute' + ); + }); + }); + + test('it renders: editType=radio / possibleValues - with no selected radio', async function (assert) { + const possibleValues = ['foo', 'bar', 'baz']; + await setup.call(this, createAttr('myfield', '-', { editType: 'radio', possibleValues })); + possibleValues.forEach((possibleValue) => { + assert + .dom(GENERAL.radioByAttr(possibleValue)) + .isNotChecked(`input[type="radio"] "${possibleValue}" is not checked`); + }); + }); + + test('it renders: editType=radio / possibleValues - with selected value and changes it', async function (assert) { + const [model, spy] = await setup.call( + this, + createAttr('myfield', '-', { + editType: 'radio', + possibleValues: ['foo', 'bar', 'baz'], + defaultValue: 'baz', + }) + ); + assert.dom(GENERAL.radioByAttr('baz')).isChecked(`input[type="radio"] "baz" is checked`); + await click(GENERAL.radioByAttr('foo')); + assert.strictEqual(model.get('myfield'), 'foo'); + assert.ok(spy.calledWith('myfield', 'foo'), 'onChange called with correct args'); + }); + + test('it renders: editType=radio / possibleValues - with `true/false` boolean values', async function (assert) { + const [model, spy] = await setup.call( + this, + createAttr('myfield', '-', { + editType: 'radio', + // we need to pass custom ID or the `true` value will not be assigned as `for` argument to the label + // see bug in HDS: https://github.com/hashicorp/design-system/pull/2863 + // once the bug is fixed, we can change this to `possibleValues: [true, false],` + possibleValues: [ + { value: true, id: 'true-option' }, + { value: false, id: 'false-option' }, + ], + defaultValue: true, + }) + ); + assert.dom(GENERAL.radioByAttr('true-option')).isChecked(`input[type="radio"] "true" is checked`); + await click(GENERAL.radioByAttr('false-option')); + // eslint-disable-next-line qunit/no-assert-equal-boolean + assert.strictEqual(model.get('myfield'), false); + assert.ok(spy.calledWith('myfield', false), 'onChange called with correct args'); + }); + + test('it renders: editType=radio / possibleValues - with passed custom id, label, subtext, helptext', async function (assert) { + await setup.call( + this, + createAttr('myfield', '-', { + editType: 'radio', + possibleValues: [ + { value: 'foo', id: 'custom-id-1' }, + { value: 'bar', label: 'Custom label 2', subText: 'Some subtext 2' }, + { value: 'baz', label: 'Custom label 3', helpText: 'Some helptext 3' }, + { value: 'qux', label: 'Custom label 4', subText: 'Some subtext 4', helpText: 'Some helptext 2' }, + ], + }) + ); + // first item should have custom ID, label `foo`, and no subText/helpText + assert + .dom(GENERAL.radioByAttr('custom-id-1')) + .hasAttribute('id', 'custom-id-1', 'renders the radio input with a custom `id` attribute'); + assert.dom(GENERAL.labelByGroupControlIndex(1)).hasText('foo', 'renders default label from `foo` value'); + assert + .dom(GENERAL.helpTextByGroupControlIndex(1)) + .doesNotExist('does not render subtext/helptext for `foo`'); + // second item should have custom label and subText but no helpText + assert + .dom(GENERAL.labelByGroupControlIndex(2)) + .hasText('Custom label 2', 'renders the custom label for `bar` from options'); + assert + .dom(GENERAL.helpTextByGroupControlIndex(2)) + .hasText('Some subtext 2', 'renders the right subtext string for `bar` from options'); + // third item should have custom label and no subText/helpText (helpText is visible only if no subText is defined for any of the items) + assert + .dom(GENERAL.labelByGroupControlIndex(3)) + .hasText('Custom label 3', 'renders the custom label for `baz` from options'); + assert.dom(GENERAL.helpTextByGroupControlIndex(3)).doesNotExist('does not render the helptext for `baz`'); + // fourth item should have custom label and subText but no helpText (helpText is visible only if no subText is defined for any of the items) + assert + .dom(GENERAL.labelByGroupControlIndex(4)) + .hasText('Custom label 4', 'renders the custom label for `qux` from options'); + assert + .dom(GENERAL.helpTextByGroupControlIndex(4)) + .exists({ count: 1 }, 'renders only the subtext for `qux` and not the helptext') + .hasText('Some subtext 4', 'renders the right subtext string for `qux` from options'); + }); + + test('it renders: editType=radio / possibleValues - with passed helptext', async function (assert) { + await setup.call( + this, + createAttr('myfield', '-', { + editType: 'radio', + possibleValues: [{ value: 'foo' }, { value: 'bar', helpText: 'Some helptext 2' }], + }) + ); + // first item should not have helpText + assert.dom(GENERAL.helpTextByGroupControlIndex(1)).doesNotExist('does not render helptext for `foo`'); + // second item should have helpText + assert + .dom(GENERAL.helpTextByGroupControlIndex(2)) + .hasText('Some helptext 2', 'renders the right helptext string for `bar` from options'); + }); + + test('it renders: editType=radio / possibleValues - with validation errors and warnings', async function (assert) { + this.setProperties({ + attr: createAttr('myfield', '-', { editType: 'radio', possibleValues: ['foo', 'bar', 'baz'] }), + model: { myfield: 'bar' }, + modelValidations: { + myfield: { + isValid: false, + errors: ['Error message #1', 'Error message #2'], + warnings: ['Warning message #1', 'Warning message #2'], + }, + }, + onChange: () => {}, + }); + + await render( + hbs`` + ); + assert + .dom(GENERAL.validationErrorByAttr('myfield')) + .exists('Validation error renders') + .hasText('Error message #1 Error message #2', 'Validation errors are combined'); + assert + .dom(GENERAL.validationWarningByAttr('myfield')) + .exists('Validation warning renders') + .hasText('Warning message #1 Warning message #2', 'Validation warnings are combined'); + }); + // ––––– editType === 'checkboxList' / possibleValues ––––– test('it renders: editType=checkboxList / possibleValues - as Hds::Form::Checkbox::Group', async function (assert) { diff --git a/ui/tests/integration/components/kubernetes/page/role/create-and-edit-test.js b/ui/tests/integration/components/kubernetes/page/role/create-and-edit-test.js index 99f87eaa11..a8ddd05278 100644 --- a/ui/tests/integration/components/kubernetes/page/role/create-and-edit-test.js +++ b/ui/tests/integration/components/kubernetes/page/role/create-and-edit-test.js @@ -122,7 +122,7 @@ module('Integration | Component | kubernetes | Page::Role::CreateAndEdit', funct 'Kubernetes role name cleared when switching from expanded to full' ); - await click('[data-test-input="kubernetesRoleType"] input'); + await click('[data-test-input-group="kubernetesRoleType"] input'); await click('[data-test-toggle-input="show-nameTemplate"]'); await fillIn('[data-test-input="nameTemplate"]', 'bar'); await fillIn('[data-test-select-template]', '6'); diff --git a/ui/tests/integration/components/ldap/page/library/create-and-edit-test.js b/ui/tests/integration/components/ldap/page/library/create-and-edit-test.js index 17958d743a..6b9d912c31 100644 --- a/ui/tests/integration/components/ldap/page/library/create-and-edit-test.js +++ b/ui/tests/integration/components/ldap/page/library/create-and-edit-test.js @@ -66,7 +66,7 @@ module('Integration | Component | ldap | Page::Library::CreateAndEdit', function assert.dom('[data-test-ttl-value="Max lease TTL"]').hasAnyValue('Max lease ttl renders'); const checkInValue = this.libraryData.disable_check_in_enforcement ? 'Disabled' : 'Enabled'; assert - .dom(`[data-test-input="disable_check_in_enforcement"] input#${checkInValue}`) + .dom(`[data-test-input-group="disable_check_in_enforcement"] input#${checkInValue}`) .isChecked('Correct radio is checked for check-in enforcement'); }); @@ -121,7 +121,7 @@ module('Integration | Component | ldap | Page::Library::CreateAndEdit', function await click('[data-test-string-list-button="add"]'); await fillIn('[data-test-string-list-input="1"]', 'bar@baz.com'); await click('[data-test-string-list-button="add"]'); - await click('[data-test-input="disable_check_in_enforcement"] input#Disabled'); + await click('[data-test-input-group="disable_check_in_enforcement"] input#Disabled'); await click('[data-test-save]'); assert.ok( @@ -149,7 +149,7 @@ module('Integration | Component | ldap | Page::Library::CreateAndEdit', function await this.renderComponent(); await click('[data-test-string-list-row="0"] [data-test-string-list-button="delete"]'); - await click('[data-test-input="disable_check_in_enforcement"] input#Disabled'); + await click('[data-test-input-group="disable_check_in_enforcement"] input#Disabled'); await click('[data-test-save]'); assert.ok( diff --git a/ui/tests/integration/components/mfa/method-form-test.js b/ui/tests/integration/components/mfa/method-form-test.js index ef3f970a1c..f891a85cac 100644 --- a/ui/tests/integration/components/mfa/method-form-test.js +++ b/ui/tests/integration/components/mfa/method-form-test.js @@ -33,7 +33,7 @@ module('Integration | Component | mfa-method-form', function (hooks) { assert.dom('[data-test-input="period"]').exists('Period field ttl renders'); assert.dom('[data-test-input="key_size"]').exists('Key size field input renders'); assert.dom('[data-test-input="qr_size"]').exists('QR size field input renders'); - assert.dom('[data-test-input="algorithm"]').exists(`Algorithm field radio input renders`); + assert.dom('[data-test-input-group="algorithm"]').exists(`Algorithm field radio input renders`); assert .dom('[data-test-input="max_validation_attempts"]') .exists(`Max validation attempts field input renders`); diff --git a/ui/tests/integration/components/oidc/client-form-test.js b/ui/tests/integration/components/oidc/client-form-test.js index 861f328c8d..6fe9b4f0c4 100644 --- a/ui/tests/integration/components/oidc/client-form-test.js +++ b/ui/tests/integration/components/oidc/client-form-test.js @@ -153,9 +153,11 @@ module('Integration | Component | oidc/client-form', function (hooks) { assert.dom('[data-test-input="name"]').hasValue('test-app', 'Name input is populated with model value'); assert.dom('[data-test-input="key"]').isDisabled('Signing key input is disabled'); assert.dom('[data-test-input="key"]').hasValue('default', 'Key input populated with default'); - assert.dom('[data-test-input="clientType"] input').isDisabled('client type input is disabled on edit'); assert - .dom('[data-test-input="clientType"] input#confidential') + .dom('[data-test-input-group="clientType"] input') + .isDisabled('client type input is disabled on edit'); + assert + .dom('[data-test-input-group="clientType"] input#confidential') .isChecked('Correct radio button is selected'); assert.dom('[data-test-oidc-radio="allow-all"] input').isChecked('Allow all radio button is selected'); await click(SELECTORS.clientSaveButton); diff --git a/ui/tests/integration/components/totp/key-form-test.js b/ui/tests/integration/components/totp/key-form-test.js index cef582befd..36d67c738a 100644 --- a/ui/tests/integration/components/totp/key-form-test.js +++ b/ui/tests/integration/components/totp/key-form-test.js @@ -116,7 +116,7 @@ module('Integration | Component | totp/key-form', function (hooks) { assert.dom('[data-test-secret-header]').hasText('Create a TOTP key', 'Form title renders'); // switch to non-generated form fields - await click('[data-test-radio="Other service"]'); + await click(GENERAL.radioByAttr('Other service')); // check validation errors await click(GENERAL.saveButton); @@ -156,7 +156,7 @@ module('Integration | Component | totp/key-form', function (hooks) { assert.dom(SELECTORS.toggleGroup('Provider Options')).exists('Generated exclusive group is shown'); // switch to non-generated form fields - await click('[data-test-radio="Other service"]'); + await click(GENERAL.radioByAttr('Other service')); // check non generated groups assert.dom(SELECTORS.toggleGroup('TOTP Code Options')).exists('Common group is shown'); diff --git a/ui/tests/pages/components/form-field.js b/ui/tests/pages/components/form-field.js index 94a26ac7d1..c8df723925 100644 --- a/ui/tests/pages/components/form-field.js +++ b/ui/tests/pages/components/form-field.js @@ -28,7 +28,6 @@ export default { hasMaskedInput: isPresent('[data-test-masked-input]'), hasTooltip: isPresent('[data-test-component=info-tooltip]'), tooltipTrigger: focusable('[data-test-tool-tip-trigger]'), - hasRadio: isPresent('[data-test-radio-input]'), radioButtons: collection('input[type=radio]', { select: clickable(), id: attribute('id'),