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:
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 });
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user