chore: 🤖 Create select field render for the Form::Field::ListWrapper (#2112)

* chore: 🤖 Create `select` field render for the `Form::Field::ListWrapper`
component

Create a `select` field render for the `Form::Field::ListWrapper`
component.

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-12271

* refactor: 💡 Added additional test

Added additional testing around removing and adding an option, and
refactored for better usability

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-12271

* refactor: 💡 Refactored getter to check for option and model arg

Refactore getter to check for options and model args.

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-12271

* refactor: 💡 Use select field with `signing_algorithms`

Use select field with `signing_algorithms`

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-12271

* refactor: 💡 fix test

fix test

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-12271

* refactor: 💡 fix failing test

fix failing test

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-12271

* refactor: 💡 fix update auth-method test

fix update auth-method test

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-12271
This commit is contained in:
Devon Anderson authored and GitHub committed 2024-02-09 08:42:15 -05:00
1 parent d2d332b7d1
commit c79a1fca79
7 files changed
+238 -113

No files matched your search

@@ -144,115 +144,33 @@
{{/unless}}
{{! Signing Algorithms }}
<Rose::Form::Fieldset>
<:title>{{t 'form.signing_algorithms.label'}}</:title>
<:description>{{t 'form.signing_algorithms.help'}}</:description>
<:body>
<Form::Field::ListWrapper @layout='horizontal' @disabled={{form.disabled}}>
<:fieldset as |F|>
<F.Legend>
{{t 'form.signing_algorithms.label'}}
</F.Legend>
<F.HelperText>
{{t 'form.signing_algorithms.help'}}
</F.HelperText>
{{#if @model.errors.signing_algorithms}}
<Rose::Form::Errors as |errors|>
<F.Error as |E|>
{{#each @model.errors.signing_algorithms as |error|}}
<errors.message>{{error.message}}</errors.message>
<E.Message>{{error.message}}</E.Message>
{{/each}}
</Rose::Form::Errors>
</F.Error>
{{/if}}
<Rose::Table @style='condensed' as |table|>
<table.header as |header|>
<header.row @hidden={{true}} as |row|>
<row.headerCell>{{t 'titles.value'}}</row.headerCell>
<row.headerCell>{{t 'titles.actions'}}</row.headerCell>
</header.row>
</table.header>
<table.body as |body|>
{{#each @model.signing_algorithms as |algorithm i|}}
<body.row as |row|>
<row.cell>
</:fieldset>
<Hds::Form::Select::Field
@value={{algorithm.value}}
@isInvalid={{@model.errors.signing_algorithms}}
disabled={{form.disabled}}
{{on 'change' (set-from-event algorithm 'value')}}
as |F|
>
<F.Label class='sr-only'>{{t
'form.algorithm.label'
}}</F.Label>
<F.Options>
{{#each this.signingAlgorithms as |algorithmOption|}}
<option
value={{algorithmOption}}
selected={{eq algorithmOption algorithm.value}}
>{{algorithmOption}}</option>
{{/each}}
</F.Options>
</Hds::Form::Select::Field>
</row.cell>
<row.cell @shrink={{true}}>
<Rose::Button
@style='warning'
@iconOnly='flight-icons/svg/trash-16'
title={{t 'actions.remove'}}
@disabled={{if
@model.isNew
false
(if form.isEditable false true)
}}
{{on 'click' (fn @removeItemByIndex 'signing_algorithms' i)}}
>
{{t 'actions.remove'}}
</Rose::Button>
</row.cell>
</body.row>
{{/each}}
<body.row as |row|>
<row.cell>
<Hds::Form::Select::Field
@value={{this.newSigningAlgorithm}}
disabled={{form.disabled}}
{{on 'change' (set-from-event this 'newSigningAlgorithm')}}
as |F|
>
<F.Label class='sr-only'>{{t
'resources.auth-method.titles.new-signing-algorithm'
}}</F.Label>
<F.Options>
{{#each this.signingAlgorithms as |algorithmOption|}}
<option
value={{algorithmOption}}
selected={{eq algorithmOption this.newSigningAlgorithm}}
>{{algorithmOption}}</option>
{{/each}}
</F.Options>
</Hds::Form::Select::Field>
</row.cell>
<row.cell @shrink={{true}}>
<Rose::Button
@style='secondary'
title={{t 'actions.add'}}
@disabled={{if
@model.isNew
false
(if form.isEditable false true)
}}
{{on
'click'
(fn
@addStringItem 'signing_algorithms' this.newSigningAlgorithm
)
}}
>
{{t 'actions.add'}}
</Rose::Button>
</row.cell>
</body.row>
</table.body>
</Rose::Table>
</:body>
</Rose::Form::Fieldset>
<:field as |F|>
<F.Select
@name='signing_algorithms'
@model={{@model}}
@selectOptions={{this.signingAlgorithms}}
@width='100%'
/>
</:field>
</Form::Field::ListWrapper>
{{! Allowed Audiences }}
<Form::Field::ListWrapper
@@ -24,6 +24,7 @@
Textarea=(component
'form/field/list-wrapper/textarea' disabled=@disabled
)
Select=(component 'form/field/list-wrapper/select' disabled=@disabled)
)
to='field'
}}
@@ -0,0 +1,85 @@
<Hds::Table class='list-wrapper-field' name={{@name}}>
<:body as |B|>
{{#each this.options as |select index|}}
<B.Tr>
<B.Td>
<Hds::Form::Select::Base
@value={{select.value}}
@width={{@width}}
disabled={{@disabled}}
aria-label={{t 'titles.value'}}
{{on 'change' (set-from-event select 'value')}}
as |F|
>
<F.Options>
{{#each @selectOptions as |selectOption|}}
<option
value={{selectOption}}
selected={{eq select.value selectOption}}
>
{{selectOption}}
</option>
{{/each}}
</F.Options>
</Hds::Form::Select::Base>
</B.Td>
<B.Td>
{{#if this.removeOptionByIndex}}
<Hds::Button
data-test-remove-option-button={{select.value}}
@text={{t 'actions.remove'}}
@color='critical'
@icon='trash'
@isIconOnly={{true}}
disabled={{@disabled}}
{{on 'click' (fn this.removeOptionByIndex index)}}
/>
{{/if}}
</B.Td>
</B.Tr>
{{/each}}
<B.Tr>
<B.Td>
<Hds::Form::Select::Base
@value={{this.newOptionValue}}
@width={{@width}}
disabled={{@disabled}}
aria-label={{t 'titles.value'}}
{{on 'change' (set-from-event this 'newOptionValue')}}
as |F|
>
<F.Options>
<option disabled={{@disabled}} hidden selected value=''>
{{t 'titles.choose-an-option'}}
</option>
{{#each @selectOptions as |selectOption|}}
<option
value={{selectOption}}
selected={{eq selectOption this.newOptionValue}}
>
{{selectOption}}
</option>
{{/each}}
</F.Options>
</Hds::Form::Select::Base>
</B.Td>
<B.Td>
<Hds::Button
data-test-add-option-button
@text={{t 'actions.add'}}
@color='secondary'
type='button'
disabled={{or @disabled (not this.newOptionValue)}}
{{on 'click' this.addOption}}
/>
</B.Td>
</B.Tr>
</:body>
</Hds::Table>
@@ -0,0 +1,56 @@
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { set } from '@ember/object';
export default class MappingListSelectComponent extends Component {
// =attributes
/**
* @type {string}
*/
@tracked newOptionValue = '';
get options() {
return this.args.options || this.args.model[this.args.name];
}
/**
* If a new input is entered and an addOption method was specified,
* calls addOption with the new input. Resets previous value.
* Otherwise use the model argument to create the array and update the model
*/
@action
addOption() {
if (this.args.addOption && this.newOptionValue) {
this.args.addOption({
value: this.newOptionValue,
});
} else {
const field = this.args.name;
const existingArray = this.args.model[field] ?? [];
const newArray = [...existingArray, { value: this.newOptionValue }];
set(this.args.model, field, newArray);
}
this.newOptionValue = '';
}
/**
* If removeOptionByIndex method was passed, use that.
* Otherwise, use the model to remove an option by index. We recreate a new array after
* splicing out the item so that ember is aware that the array has been modified.
* @param index {number}
*/
@action
removeOptionByIndex(index) {
if (this.args.removeOptionByIndex) {
this.args.removeOptionByIndex(index);
} else {
const field = this.args.name;
const newArray = this.args.model[field].filter((_, i) => i !== index);
set(this.args.model, field, newArray);
}
}
}
@@ -118,7 +118,7 @@ module('Acceptance | auth-methods | create', function (hooks) {
await fillIn('[name="client_id"]', 'client_id');
await fillIn('[name="client_secret"]', 'client_secret');
await select('form fieldset:nth-of-type(1) select', 'RS384');
await click('form fieldset:nth-of-type(1) [title="Add"]');
await click('[data-test-add-option-button]');
await fillIn(ALLOWED_AUDIENCES_INPUT_SELECTOR, 'allowed_audiences');
await click(ALLOWED_AUDIENCES_BTN_SELECTOR, 'allowed_audiences');
await fillIn(CLAIMS_SCOPES_INPUT_SELECTOR, 'claims_scopes');
@@ -53,6 +53,12 @@ module('Acceptance | auth-methods | update', function (hooks) {
const CLAIMS_SCOPES_INPUT_SELECTOR = '[name="claims_scopes"] input';
const TOGGLE_SELECTOR = '[name="prompts"]';
const SIGNING_ALGORITHMS_REMOVE_BTN_SELECTOR =
'[data-test-remove-option-button]';
const SIGNING_ALGORITHMS_INPUT_SELECTOR =
'.list-wrapper-field tbody tr:last-child select';
const SIGNING_ALGORITHMS_ADD_BTN_SELECTOR = '[data-test-add-option-button]';
const instances = {
scopes: {
global: null,
@@ -123,14 +129,16 @@ module('Acceptance | auth-methods | update', function (hooks) {
await fillIn('[name="issuer"]', 'issuer');
await fillIn('[name="client_id"]', 'client_id');
await fillIn('[name="client_secret"]', 'client_secret');
// Remove all signing algorithms
await Promise.all(
findAll('form fieldset:nth-of-type(1) [title="Remove"]').map((element) =>
click(element),
),
);
await select('form fieldset:nth-of-type(1) select', 'RS384');
await click('form fieldset:nth-of-type(1) [title="Add"]');
let removeButtons = findAll(SIGNING_ALGORITHMS_REMOVE_BTN_SELECTOR);
for (const element of removeButtons) {
await click(element);
}
await select(SIGNING_ALGORITHMS_INPUT_SELECTOR, 'RS384');
await click(SIGNING_ALGORITHMS_ADD_BTN_SELECTOR);
// Remove all allowed audiences
const allowedAudiencesList = findAll(ALLOWED_AUDIENCES_REMOVE_BTN_SELECTOR);
@@ -5,7 +5,7 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { click, render, select } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { setupIntl } from 'ember-intl/test-support';
@@ -106,4 +106,61 @@ module('Integration | Component | list-wrapper', function (hooks) {
assert.dom('tbody tr').exists({ count: 3 });
});
test('it renders multiple options with a single select field', async function (assert) {
this.set('options', ['option1', 'option2', 'option3']);
this.set('model', { select: [{ value: 'option2' }, { value: 'option3' }] });
await render(hbs`
<Form::Field::ListWrapper>
<:field as |F|>
<F.Select
@name="select"
@selectOptions={{this.options}}
@options={{this.model.select}}
@model={{this.model}}
@width='100%'
/>
</:field>
</Form::Field::ListWrapper>
`);
assert
.dom(
`.list-wrapper-field [data-test-remove-option-button=${this.model.select[0].value}]`,
)
.exists({ count: 1 });
assert.dom('tbody tr').exists({ count: 3 });
});
test('select field allows you to add and remove options', async function (assert) {
this.set('options', ['option1', 'option2', 'option3']);
this.set('model', { select: [{ value: 'option2' }, { value: 'option3' }] });
await render(hbs`
<Form::Field::ListWrapper>
<:field as |F|>
<F.Select
@name="select"
@selectOptions={{this.options}}
@options={{this.model.select}}
@model={{this.model}}
@width='100%'
/>
</:field>
</Form::Field::ListWrapper>
`);
assert.dom('tbody tr').exists({ count: 3 });
// Add an option
await select('.list-wrapper-field tbody tr:last-child select', 'option1');
await click('.list-wrapper-field [data-test-add-option-button]');
assert.dom('tbody tr').exists({ count: 4 });
// Remove an option
await click('.list-wrapper-field [data-test-remove-option-button=option1]');
assert.dom('tbody tr').exists({ count: 3 });
});
});