mirror of
https://github.com/hashicorp/vault.git
synced 2026-02-03 20:40:45 -05:00
[VAULT-34873] UI: improve FormField test coverage for fields migrated to HDS (#30345)
* [UI] added `data-test-form-field-doc-link` attribute to `<DocLink>` instances in `FormField` (#34873) * [UI] renamed a few `data-test` attributes in `FormField` for consistency (#34873) * [UI] added integration tests for `FormField` with editType='password' (#34873) * [UI] added integration tests for `FormField` with editType='select' (#34873) * [UI] added integration tests for `FormField` with editType='checkboxList' (#34873) * [UI] tweakings per review comments (#34873) * [UI] standardized template code and data attributes for `form-field` + added general selectors + updated/standardized integration tests (#34873) * fixed a couple of broken tests (selector needed to be updated)
This commit is contained in:
parent
7295887816
commit
e9faec3832
13 changed files with 410 additions and 57 deletions
|
|
@ -11,57 +11,58 @@
|
|||
{{! •••••••••••••••••••••••••••••••••••••••••••••••••••••••• }}
|
||||
{{#if @attr.options.possibleValues}}
|
||||
{{#if (eq @attr.options.editType "checkboxList")}}
|
||||
<Hds::Form::Checkbox::Group @name={{@attr.name}} data-test-input={{@attr.name}} as |G|>
|
||||
<Hds::Form::Checkbox::Group @name={{@attr.name}} data-test-input-group={{@attr.name}} as |G|>
|
||||
{{#if this.labelString}}
|
||||
<G.Legend data-test-form-field-label>{{this.labelString}}</G.Legend>
|
||||
{{/if}}
|
||||
{{#if this.helpTextString}}
|
||||
<G.HelperText data-test-help-text>{{this.helpTextString}}</G.HelperText>
|
||||
<G.HelperText data-test-help-text={{@attr.options.helpText}}>{{this.helpTextString}}</G.HelperText>
|
||||
{{/if}}
|
||||
{{#if @attr.options.subText}}
|
||||
<G.HelperText data-test-label-subtext>
|
||||
<G.HelperText data-test-help-text={{@attr.options.subText}}>
|
||||
{{@attr.options.subText}}
|
||||
{{#if @attr.options.docLink}}
|
||||
<DocLink @path={{@attr.options.docLink}}>See our documentation</DocLink>
|
||||
<DocLink @path={{@attr.options.docLink}} data-test-doc-link={{@attr.options.docLink}}>See our documentation</DocLink>
|
||||
for help.
|
||||
{{/if}}
|
||||
</G.HelperText>
|
||||
{{/if}}
|
||||
{{#each @attr.options.possibleValues as |option|}}
|
||||
<G.CheckboxField
|
||||
checked={{includes option (get @model this.valuePath)}}
|
||||
@value={{option}}
|
||||
@id={{option}}
|
||||
{{on "change" this.handleChecklist}}
|
||||
@value={{option}}
|
||||
checked={{includes option (get @model this.valuePath)}}
|
||||
{{on "change" this.setAndBroadcastChecklist}}
|
||||
data-test-checkbox={{option}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{option}}</F.Label>
|
||||
<F.Label data-test-input-group-item-label={{option}}>{{option}}</F.Label>
|
||||
</G.CheckboxField>
|
||||
{{/each}}
|
||||
{{#if this.validationError}}
|
||||
<G.Error data-test-field-validation={{this.valuePath}}>{{this.validationError}}</G.Error>
|
||||
<G.Error data-test-validation-error={{this.valuePath}}>{{this.validationError}}</G.Error>
|
||||
{{/if}}
|
||||
</Hds::Form::Checkbox::Group>
|
||||
{{else}}
|
||||
<Hds::Form::Select::Field
|
||||
name={{@attr.name}}
|
||||
@id={{@attr.name}}
|
||||
@isInvalid={{this.validationError}}
|
||||
{{on "change" this.onChangeWithEvent}}
|
||||
data-test-input={{@attr.name}}
|
||||
@isInvalid={{this.validationError}}
|
||||
as |F|
|
||||
>
|
||||
{{#if this.labelString}}
|
||||
<F.Label data-test-form-field-label>{{this.labelString}}</F.Label>
|
||||
{{/if}}
|
||||
{{#if this.helpTextString}}
|
||||
<F.HelperText data-test-help-text>{{this.helpTextString}}</F.HelperText>
|
||||
<F.HelperText data-test-help-text={{@attr.options.helpText}}>{{this.helpTextString}}</F.HelperText>
|
||||
{{/if}}
|
||||
{{#if @attr.options.subText}}
|
||||
<F.HelperText data-test-label-subtext>
|
||||
<F.HelperText data-test-help-text={{@attr.options.subText}}>
|
||||
{{@attr.options.subText}}
|
||||
{{#if @attr.options.docLink}}
|
||||
<DocLink @path={{@attr.options.docLink}}>See our documentation</DocLink>
|
||||
<DocLink @path={{@attr.options.docLink}} data-test-doc-link={{@attr.options.docLink}}>See our documentation</DocLink>
|
||||
for help.
|
||||
{{/if}}
|
||||
</F.HelperText>
|
||||
|
|
@ -79,7 +80,7 @@
|
|||
{{/each}}
|
||||
</F.Options>
|
||||
{{#if this.validationError}}
|
||||
<F.Error data-test-field-validation={{this.valuePath}}>{{this.validationError}}</F.Error>
|
||||
<F.Error data-test-validation-error={{this.valuePath}}>{{this.validationError}}</F.Error>
|
||||
{{/if}}
|
||||
</Hds::Form::Select::Field>
|
||||
{{/if}}
|
||||
|
|
@ -88,12 +89,13 @@
|
|||
{{#if (eq @attr.options.editType "password")}}
|
||||
<Hds::Form::TextInput::Field
|
||||
@type="password"
|
||||
name={{@attr.name}}
|
||||
{{! TODO: about this visibility toggle, see: https://hashicorp.atlassian.net/browse/VAULT-34870 }}
|
||||
@hasVisibilityToggle={{false}}
|
||||
@value={{get @model this.valuePath}}
|
||||
name={{@attr.name}}
|
||||
@isInvalid={{this.validationError}}
|
||||
{{! Prevents browsers from auto-filling }}
|
||||
placeholder={{@attr.options.placeholder}}
|
||||
autocomplete="new-password"
|
||||
spellcheck="false"
|
||||
{{on "input" this.onChangeWithEvent}}
|
||||
|
|
@ -105,20 +107,21 @@
|
|||
<F.Label data-test-form-field-label>{{this.labelString}}</F.Label>
|
||||
{{/if}}
|
||||
{{#if this.helpTextString}}
|
||||
<F.HelperText data-test-help-text>{{this.helpTextString}}</F.HelperText>
|
||||
<F.HelperText data-test-help-text={{@attr.options.helpText}}>{{this.helpTextString}}</F.HelperText>
|
||||
{{/if}}
|
||||
{{#if @attr.options.subText}}
|
||||
<F.HelperText data-test-label-subtext>
|
||||
<F.HelperText data-test-help-text={{@attr.options.subText}}>
|
||||
{{@attr.options.subText}}
|
||||
{{#if @attr.options.docLink}}
|
||||
<DocLink @path={{@attr.options.docLink}}>See our documentation</DocLink>
|
||||
<DocLink @path={{@attr.options.docLink}} data-test-doc-link={{@attr.options.docLink}}>See our
|
||||
documentation</DocLink>
|
||||
for help.
|
||||
{{/if}}
|
||||
</F.HelperText>
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
{{#if this.validationError}}
|
||||
<F.Error data-test-field-validation={{this.valuePath}}>{{this.validationError}}</F.Error>
|
||||
<F.Error data-test-validation-error={{this.valuePath}}>{{this.validationError}}</F.Error>
|
||||
{{/if}}
|
||||
</Hds::Form::TextInput::Field>
|
||||
{{/if}}
|
||||
|
|
@ -310,7 +313,7 @@
|
|||
<span>
|
||||
{{@attr.options.subText}}
|
||||
{{#if @attr.options.docLink}}
|
||||
<DocLink @path={{@attr.options.docLink}}>
|
||||
<DocLink @path={{@attr.options.docLink}} data-test-doc-link={{@attr.options.docLink}}>
|
||||
See our documentation
|
||||
</DocLink>
|
||||
for help.
|
||||
|
|
@ -320,7 +323,7 @@
|
|||
<span>
|
||||
{{or @attr.options.defaultSubText "Vault will use the engine default."}}
|
||||
{{#if @attr.options.docLink}}
|
||||
<DocLink @path={{@attr.options.docLink}}>
|
||||
<DocLink @path={{@attr.options.docLink}} data-test-doc-link={{@attr.options.docLink}}>
|
||||
See our documentation
|
||||
</DocLink>
|
||||
for help.
|
||||
|
|
@ -404,7 +407,7 @@
|
|||
<p class="sub-text">
|
||||
{{@attr.options.subText}}
|
||||
{{#if @attr.options.docLink}}
|
||||
<DocLink @path={{@attr.options.docLink}}>
|
||||
<DocLink @path={{@attr.options.docLink}} data-test-doc-link={{@attr.options.docLink}}>
|
||||
See our documentation
|
||||
</DocLink>
|
||||
for help.
|
||||
|
|
@ -451,7 +454,7 @@
|
|||
<p class="sub-text">
|
||||
{{@attr.options.subText}}
|
||||
{{#if @attr.options.docLink}}
|
||||
<DocLink @path={{@attr.options.docLink}}>
|
||||
<DocLink @path={{@attr.options.docLink}} data-test-doc-link={{@attr.options.docLink}}>
|
||||
Learn more here.
|
||||
</DocLink>
|
||||
{{/if}}
|
||||
|
|
@ -474,7 +477,7 @@
|
|||
@type="danger"
|
||||
@message={{this.validationError}}
|
||||
@paddingTop={{not-eq @attr.options.editType "ttl"}}
|
||||
data-test-field-validation={{this.valuePath}}
|
||||
data-test-validation-error={{this.valuePath}}
|
||||
class={{if (eq @attr.options.editType "stringArray") "has-top-margin-negative-xxl"}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
|
|
|||
|
|
@ -195,6 +195,16 @@ export default class FormFieldComponent extends Component {
|
|||
this.setAndBroadcast(valueToSet);
|
||||
}
|
||||
@action
|
||||
setAndBroadcastChecklist(event) {
|
||||
let updatedValue = this.args.model[this.valuePath];
|
||||
if (event.target.checked) {
|
||||
updatedValue = addToArray(updatedValue, event.target.value);
|
||||
} else {
|
||||
updatedValue = removeFromArray(updatedValue, event.target.value);
|
||||
}
|
||||
this.setAndBroadcast(updatedValue);
|
||||
}
|
||||
@action
|
||||
setAndBroadcastTtl(value) {
|
||||
const alwaysSendValue = this.valuePath === 'expiry' || this.valuePath === 'safetyBuffer';
|
||||
const attrOptions = this.args.attr.options || {};
|
||||
|
|
@ -243,15 +253,4 @@ export default class FormFieldComponent extends Component {
|
|||
const prop = event.target.type === 'checkbox' ? 'checked' : 'value';
|
||||
this.setAndBroadcast(event.target[prop]);
|
||||
}
|
||||
|
||||
@action
|
||||
handleChecklist(event) {
|
||||
let updatedValue = this.args.model[this.valuePath];
|
||||
if (event.target.checked) {
|
||||
updatedValue = addToArray(updatedValue, event.target.value);
|
||||
} else {
|
||||
updatedValue = removeFromArray(updatedValue, event.target.value);
|
||||
}
|
||||
this.setAndBroadcast(updatedValue);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@
|
|||
@type="danger"
|
||||
@message={{or @validationError this.uploadError}}
|
||||
class="has-top-padding-s"
|
||||
data-test-field-validation="text-file"
|
||||
data-test-inline-alert
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export const CUSTOM_MESSAGES = {
|
|||
field: (fieldName: string) => `[data-test-field="${fieldName}"]`,
|
||||
input: (input: string) => `[data-test-input="${input}"]`,
|
||||
button: (buttonName: string) => `[data-test-button="${buttonName}"]`,
|
||||
fieldValidation: (fieldName: string) => `[data-test-field-validation="${fieldName}"]`,
|
||||
fieldValidation: (fieldName: string) => `[data-test-validation-error="${fieldName}"]`,
|
||||
modal: (name: string) => `[data-test-modal="${name}"]`,
|
||||
modalTitle: (title: string) => `[data-test-modal-title="${title}"]`,
|
||||
modalBody: (name: string) => `[data-test-modal-body="${name}"]`,
|
||||
|
|
|
|||
|
|
@ -38,9 +38,20 @@ export const GENERAL = {
|
|||
listItem: '[data-test-list-item-link]',
|
||||
// FORMS
|
||||
checkboxByAttr: (attr: string) => `[data-test-checkbox="${attr}"]`,
|
||||
docLinkByAttr: (attr: string) => `[data-test-doc-link="${attr}"]`,
|
||||
enableField: (attr: string) => `[data-test-enable-field="${attr}"] button`,
|
||||
fieldByAttr: (attr: string) => `[data-test-field="${attr}"]`,
|
||||
fieldLabel: () => `[data-test-form-field-label]`,
|
||||
fieldLabelbyAttr: (attr: string) => `[data-test-form-field-label="${attr}"]`,
|
||||
helpText: () => `[data-test-help-text]`,
|
||||
helpTextByAttr: (attr: string) => `[data-test-help-text="${attr}"]`,
|
||||
helpTextByGroupControlIndex: (index: number) =>
|
||||
`.hds-form-group__control-field:nth-of-type(${index}) [data-test-help-text]`,
|
||||
inputByAttr: (attr: string) => `[data-test-input="${attr}"]`,
|
||||
groupControlByIndex: (index: number) => `.hds-form-group__control-field:nth-of-type(${index})`,
|
||||
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`,
|
||||
selectByAttr: (attr: string) => `[data-test-select="${attr}"]`,
|
||||
textToggle: '[data-test-text-toggle]',
|
||||
textToggleTextarea: '[data-test-text-file-textarea]',
|
||||
|
|
@ -54,8 +65,8 @@ export const GENERAL = {
|
|||
infoRowLabel: (label: string) => `[data-test-row-label="${label}"]`,
|
||||
infoRowValue: (label: string) => `[data-test-row-value="${label}"]`,
|
||||
// Validation
|
||||
validation: (attr: string) => `[data-test-field-validation=${attr}]`,
|
||||
validationWarning: (attr: string) => `[data-test-validation-warning=${attr}]`,
|
||||
validationErrorByAttr: (attr: string) => `[data-test-validation-error=${attr}]`,
|
||||
validationWarningByAttr: (attr: string) => `[data-test-validation-warning=${attr}]`,
|
||||
messageError: '[data-test-message-error]',
|
||||
notFound: '[data-test-not-found]',
|
||||
pageError: {
|
||||
|
|
|
|||
|
|
@ -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="deploymentEnvironments"] input#development');
|
||||
await click('[data-test-input="deploymentEnvironments"] input#preview');
|
||||
return await click('[data-test-input="deploymentEnvironments"] input#production');
|
||||
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');
|
||||
default:
|
||||
return fillIn(`[data-test-input="${attr}"]`, value);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,13 +6,15 @@
|
|||
import EmberObject from '@ember/object';
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render, click, fillIn } from '@ember/test-helpers';
|
||||
import { render, click, fillIn, findAll } from '@ember/test-helpers';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
import { create } from 'ember-cli-page-object';
|
||||
import sinon from 'sinon';
|
||||
import formFields from '../../pages/components/form-field';
|
||||
import { format, startOfDay } from 'date-fns';
|
||||
|
||||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||
|
||||
const component = create(formFields);
|
||||
|
||||
module('Integration | Component | form field', function (hooks) {
|
||||
|
|
@ -46,6 +48,10 @@ module('Integration | Component | form field', function (hooks) {
|
|||
assert.notOk(component.hasInput, 'renders only the label');
|
||||
});
|
||||
|
||||
// ------------------
|
||||
// LEGACY FORM FIELDS
|
||||
// ------------------
|
||||
|
||||
test('it renders: string', async function (assert) {
|
||||
const [model, spy] = await setup.call(this, createAttr('foo', 'string', { defaultValue: 'default' }));
|
||||
assert.strictEqual(component.fields.objectAt(0).labelValue, 'Foo', 'renders a label');
|
||||
|
|
@ -285,6 +291,8 @@ module('Integration | Component | form field', function (hooks) {
|
|||
assert.ok(spy.calledWith('password', 'secret'), 'onChange called with correct args');
|
||||
});
|
||||
|
||||
// --- common elements (legacy) ---
|
||||
|
||||
test('it uses a passed label', async function (assert) {
|
||||
await setup.call(this, createAttr('foo', 'string', { label: 'Not Foo' }));
|
||||
assert.strictEqual(component.fields.objectAt(0).labelValue, 'Not Foo', 'renders the label from options');
|
||||
|
|
@ -297,7 +305,7 @@ module('Integration | Component | form field', function (hooks) {
|
|||
);
|
||||
await component.tooltipTrigger();
|
||||
assert.ok(component.hasTooltip, 'renders the tooltip component');
|
||||
assert.dom('[data-test-input="foo"]').hasAttribute('placeholder', 'example::value');
|
||||
assert.dom(GENERAL.inputByAttr('foo')).hasAttribute('placeholder', 'example::value');
|
||||
});
|
||||
|
||||
test('it should not expand and toggle ttl when default 0s value is present', async function (assert) {
|
||||
|
|
@ -343,6 +351,337 @@ module('Integration | Component | form field', function (hooks) {
|
|||
await render(
|
||||
hbs`<FormField @attr={{this.attr}} @model={{this.model}} @modelValidations={{this.validations}} @onChange={{this.onChange}} />`
|
||||
);
|
||||
assert.dom('[data-test-validation-warning]').exists('Validation warning renders');
|
||||
assert.dom(GENERAL.validationWarningByAttr('path')).exists('Validation warning renders');
|
||||
});
|
||||
|
||||
// ---------------
|
||||
// HDS FORM FIELDS
|
||||
// ---------------
|
||||
// Note: some tests may be duplicative of the generic tests above
|
||||
//
|
||||
|
||||
// ––––– editType === 'checkboxList' / possibleValues –––––
|
||||
|
||||
test('it renders: editType=checkboxList / possibleValues - as Hds::Form::Checkbox::Group', async function (assert) {
|
||||
const possibleValues = ['foo', 'bar', 'baz'];
|
||||
await setup.call(this, createAttr('myfield', '-', { editType: 'checkboxList', possibleValues }));
|
||||
const labels = findAll(`${GENERAL.inputGroupByAttr('myfield')} label`);
|
||||
const inputs = findAll(`${GENERAL.inputGroupByAttr('myfield')} input[type="checkbox"]`);
|
||||
assert
|
||||
.dom('.field [class^="hds-form-group"] input[type="checkbox"].hds-form-checkbox')
|
||||
.exists('renders as Hds::Form::Checkbox::Group');
|
||||
assert.strictEqual(inputs.length, 3, 'renders a fieldset element with 3 checkbox elements');
|
||||
assert.dom(GENERAL.fieldLabel()).hasText('Myfield', 'renders the input group label');
|
||||
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="checkbox"] has correct id')
|
||||
.hasAttribute(
|
||||
'data-test-checkbox',
|
||||
possibleValue,
|
||||
'input[type="checkbox"] has correct `data-test-checkbox` attribute'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('it renders: editType=checkboxList / possibleValues - with no selected checkbox', async function (assert) {
|
||||
const possibleValues = ['foo', 'bar', 'baz'];
|
||||
await setup.call(this, createAttr('myfield', '-', { editType: 'checkboxList', possibleValues }));
|
||||
possibleValues.forEach((possibleValue) => {
|
||||
assert
|
||||
.dom(GENERAL.checkboxByAttr(possibleValue))
|
||||
.isNotChecked(`input[type="checkbox"] "${possibleValue}" is not checked`);
|
||||
});
|
||||
});
|
||||
|
||||
test('it renders: editType=checkboxList / possibleValues - with selected value and changes it', async function (assert) {
|
||||
const [model, spy] = await setup.call(
|
||||
this,
|
||||
createAttr('myfield', '-', {
|
||||
editType: 'checkboxList',
|
||||
possibleValues: ['foo', 'bar', 'baz'],
|
||||
defaultValue: ['baz'],
|
||||
})
|
||||
);
|
||||
assert.dom(GENERAL.checkboxByAttr('baz')).isChecked('input[type="checkbox"] "baz" is checked');
|
||||
// select the remaining items (they're appended to the model)
|
||||
await click(GENERAL.checkboxByAttr('foo'));
|
||||
await click(GENERAL.checkboxByAttr('bar'));
|
||||
// notice: we can't use `strictEqual` here because they're different objects
|
||||
assert.deepEqual(model.get('myfield'), ['baz', 'foo', 'bar']);
|
||||
assert.ok(spy.calledWith('myfield', ['baz', 'foo', 'bar']), 'onChange called with correct args');
|
||||
});
|
||||
|
||||
test('it renders: editType=checkboxList / possibleValues - with passed label, subtext, helptext, doclink', async function (assert) {
|
||||
await setup.call(
|
||||
this,
|
||||
createAttr('myfield', '-', {
|
||||
editType: 'checkboxList',
|
||||
possibleValues: ['foo', 'bar', 'baz'],
|
||||
label: 'Custom label',
|
||||
subText: 'Some subtext',
|
||||
helpText: 'Some helptext',
|
||||
docLink: '/docs',
|
||||
})
|
||||
);
|
||||
assert.dom(GENERAL.fieldLabel()).hasText('Custom label', 'renders the custom label from options');
|
||||
assert
|
||||
.dom(GENERAL.helpTextByAttr('Some subtext'))
|
||||
.exists('renders `subText` option as HelperText')
|
||||
.hasText(
|
||||
'Some subtext See our documentation for help.',
|
||||
'renders the right subtext string from options'
|
||||
);
|
||||
assert
|
||||
.dom(`${GENERAL.helpTextByAttr('Some subtext')} ${GENERAL.docLinkByAttr('/docs')}`)
|
||||
.exists('renders `docLink` option as as link inside the subtext');
|
||||
assert
|
||||
.dom(GENERAL.helpTextByAttr('Some helptext'))
|
||||
.exists('renders `helptext` option as HelperText')
|
||||
.hasText('Some helptext', 'renders the right help text string from options');
|
||||
});
|
||||
|
||||
test('it renders: editType=checkboxList / possibleValues - with validation errors and warnings', async function (assert) {
|
||||
this.setProperties({
|
||||
attr: createAttr('myfield', '-', { editType: 'checkboxList', 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`<FormField @attr={{this.attr}} @model={{this.model}} @modelValidations={{this.modelValidations}} @onChange={{this.onChange}} />`
|
||||
);
|
||||
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 === 'select' / possibleValues –––––
|
||||
|
||||
test('it renders: editType=select / possibleValues - as Hds::Form::Select', async function (assert) {
|
||||
const [model, spy] = await setup.call(
|
||||
this,
|
||||
createAttr('myfield', 'string', { editType: 'select', possibleValues: ['foo', 'bar', 'baz'] })
|
||||
);
|
||||
assert
|
||||
.dom('.field [class^="hds-form-field"] select.hds-form-select')
|
||||
.exists('renders as Hds::Form::Select');
|
||||
assert
|
||||
.dom('select')
|
||||
.hasAttribute('data-test-input', 'myfield', 'select has correct `data-test-input` attribute');
|
||||
assert.dom(GENERAL.fieldLabel()).hasText('Myfield', 'renders the select label');
|
||||
assert.dom(GENERAL.inputByAttr('myfield')).hasValue('foo', 'has first option value');
|
||||
await fillIn(GENERAL.inputByAttr('myfield'), 'bar');
|
||||
assert.dom(GENERAL.inputByAttr('myfield')).hasValue('bar', 'has selected option value');
|
||||
assert.strictEqual(model.get('myfield'), 'bar');
|
||||
assert.ok(spy.calledWith('myfield', 'bar'), 'onChange called with correct args');
|
||||
});
|
||||
|
||||
test('it renders: editType=select / possibleValues - with no default', async function (assert) {
|
||||
const [model, spy] = await setup.call(
|
||||
this,
|
||||
createAttr('myfield', 'string', {
|
||||
editType: 'select',
|
||||
possibleValues: ['foo', 'bar', 'baz'],
|
||||
noDefault: true,
|
||||
})
|
||||
);
|
||||
|
||||
assert.dom(GENERAL.inputByAttr('myfield')).hasValue('', 'has no initial value');
|
||||
await fillIn(GENERAL.inputByAttr('myfield'), 'foo');
|
||||
assert.dom(GENERAL.inputByAttr('myfield')).hasValue('foo', 'has selected option value');
|
||||
assert.strictEqual(model.get('myfield'), 'foo');
|
||||
assert.ok(spy.calledWith('myfield', 'foo'), 'onChange called with correct args');
|
||||
});
|
||||
|
||||
test('it renders: editType=select / possibleValues - with selected value', async function (assert) {
|
||||
const [model, spy] = await setup.call(
|
||||
this,
|
||||
createAttr('myfield', 'string', {
|
||||
editType: 'select',
|
||||
possibleValues: ['foo', 'bar', 'baz'],
|
||||
defaultValue: 'baz',
|
||||
})
|
||||
);
|
||||
|
||||
assert.dom(GENERAL.inputByAttr('myfield')).hasValue('baz', 'has initial value selected');
|
||||
await fillIn(GENERAL.inputByAttr('myfield'), 'foo');
|
||||
assert.dom(GENERAL.inputByAttr('myfield')).hasValue('foo', 'has selected option value');
|
||||
assert.strictEqual(model.get('myfield'), 'foo');
|
||||
assert.ok(spy.calledWith('myfield', 'foo'), 'onChange called with correct args');
|
||||
});
|
||||
|
||||
test('it renders: editType=select / possibleValues - with passed label, subtext, helptext, doclink', async function (assert) {
|
||||
await setup.call(
|
||||
this,
|
||||
createAttr('myfield', 'string', {
|
||||
editType: 'select',
|
||||
possibleValues: ['foo', 'bar', 'baz'],
|
||||
label: 'Custom label',
|
||||
subText: 'Some subtext',
|
||||
helpText: 'Some helptext',
|
||||
docLink: '/docs',
|
||||
})
|
||||
);
|
||||
assert.dom(GENERAL.fieldLabel()).hasText('Custom label', 'renders the custom label from options');
|
||||
assert
|
||||
.dom(GENERAL.helpTextByAttr('Some subtext'))
|
||||
.exists('renders `subText` option as HelperText')
|
||||
.hasText(
|
||||
'Some subtext See our documentation for help.',
|
||||
'renders the right subtext string from options'
|
||||
);
|
||||
assert
|
||||
.dom(`${GENERAL.helpTextByAttr('Some subtext')} ${GENERAL.docLinkByAttr('/docs')}`)
|
||||
.exists('renders `docLink` option as as link inside the subtext');
|
||||
assert
|
||||
.dom(GENERAL.helpTextByAttr('Some helptext'))
|
||||
.exists('renders `helptext` option as HelperText')
|
||||
.hasText('Some helptext', 'renders the right help text string from options');
|
||||
});
|
||||
|
||||
test('it renders: editType=select / possibleValues - with validation errors and warnings', async function (assert) {
|
||||
this.setProperties({
|
||||
attr: createAttr('myfield', 'string', { editType: 'select', 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`<FormField @attr={{this.attr}} @model={{this.model}} @modelValidations={{this.modelValidations}} @onChange={{this.onChange}} />`
|
||||
);
|
||||
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 === 'password' –––––
|
||||
|
||||
test('it renders: editType=password / type=string - as Hds::Form::TextInput [@type=password]', async function (assert) {
|
||||
const [model, spy] = await setup.call(
|
||||
this,
|
||||
createAttr('myfield', 'string', { editType: 'password', defaultValue: 'default' })
|
||||
);
|
||||
assert
|
||||
.dom('.field [class^="hds-form-field"] input.hds-form-text-input')
|
||||
.exists('renders as Hds::Form::TextInput');
|
||||
assert
|
||||
.dom(`input[type="password"]`)
|
||||
.exists('renders input with type=password')
|
||||
.hasAttribute(
|
||||
'data-test-input',
|
||||
'myfield',
|
||||
'input[type="password"] has correct `data-test-input` attribute'
|
||||
);
|
||||
assert.dom(GENERAL.fieldLabel()).hasText('Myfield', 'renders the input label');
|
||||
assert.dom(GENERAL.inputByAttr('myfield')).hasValue('default', 'renders default value');
|
||||
await fillIn(GENERAL.inputByAttr('myfield'), 'bar');
|
||||
assert.strictEqual(model.get('myfield'), 'bar');
|
||||
assert.ok(spy.calledWith('myfield', 'bar'), 'onChange called with correct args');
|
||||
});
|
||||
|
||||
test('it renders: editType=password / type=number - as Hds::Form::TextInput [@type=password]', async function (assert) {
|
||||
const [model, spy] = await setup.call(
|
||||
this,
|
||||
createAttr('myfield', 'number', { editType: 'password', defaultValue: 123 })
|
||||
);
|
||||
assert
|
||||
.dom('.field [class^="hds-form-field"] input.hds-form-text-input')
|
||||
.exists('renders as Hds::Form::TextInput');
|
||||
assert
|
||||
.dom(`input${GENERAL.inputByAttr('myfield')}[type="password"]`)
|
||||
.exists('renders input with type=password');
|
||||
assert.dom(GENERAL.fieldLabel()).hasText('Myfield', 'renders the input label');
|
||||
assert.dom(GENERAL.inputByAttr('myfield')).hasValue('123', 'renders default value');
|
||||
await fillIn(GENERAL.inputByAttr('myfield'), 987);
|
||||
assert.strictEqual(model.get('myfield'), '987');
|
||||
assert.ok(spy.calledWith('myfield', '987'), 'onChange called with correct args');
|
||||
});
|
||||
|
||||
test('it renders: editType=password / type=string - with passed label, placeholder, subtext, helptext, doclink', async function (assert) {
|
||||
await setup.call(
|
||||
this,
|
||||
createAttr('myfield', 'string', {
|
||||
editType: 'password',
|
||||
placeholder: 'Custom placeholder',
|
||||
label: 'Custom label',
|
||||
subText: 'Some subtext',
|
||||
helpText: 'Some helptext',
|
||||
docLink: '/docs',
|
||||
})
|
||||
);
|
||||
assert.dom(GENERAL.fieldLabel()).hasText('Custom label', 'renders the custom label from options');
|
||||
assert
|
||||
.dom(GENERAL.inputByAttr('myfield'))
|
||||
.hasAttribute('placeholder', 'Custom placeholder', 'renders the placeholder from options');
|
||||
assert
|
||||
.dom(GENERAL.helpTextByAttr('Some subtext'))
|
||||
.exists('renders `subText` option as HelperText')
|
||||
.hasText(
|
||||
'Some subtext See our documentation for help.',
|
||||
'renders the right subtext string from options'
|
||||
);
|
||||
assert
|
||||
.dom(`${GENERAL.helpTextByAttr('Some subtext')} ${GENERAL.docLinkByAttr('/docs')}`)
|
||||
.exists('renders `docLink` option as as link inside the subtext');
|
||||
assert
|
||||
.dom(GENERAL.helpTextByAttr('Some helptext'))
|
||||
.exists('renders `helptext` option as HelperText')
|
||||
.hasText('Some helptext', 'renders the right help text string from options');
|
||||
});
|
||||
|
||||
test('it renders: editType=password / type=string - with validation errors and warnings', async function (assert) {
|
||||
this.setProperties({
|
||||
attr: createAttr('myfield', 'string', { editType: 'password' }),
|
||||
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`<FormField @attr={{this.attr}} @model={{this.model}} @modelValidations={{this.modelValidations}} @onChange={{this.onChange}} />`
|
||||
);
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import hbs from 'htmlbars-inline-precompile';
|
|||
import { Response } from 'miragejs';
|
||||
import sinon from 'sinon';
|
||||
import { setRunOptions } from 'ember-a11y-testing/test-support';
|
||||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||
|
||||
module('Integration | Component | kubernetes | Page::Configure', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
|
@ -230,7 +231,7 @@ module('Integration | Component | kubernetes | Page::Configure', function (hooks
|
|||
await click('[data-test-config-save]');
|
||||
|
||||
assert
|
||||
.dom('[data-test-field-validation="kubernetesHost"] [data-test-inline-error-message]')
|
||||
.dom(`${GENERAL.validationErrorByAttr('kubernetesHost')} [data-test-inline-error-message]`)
|
||||
.hasText('Kubernetes host is required', 'Error renders for required field');
|
||||
assert.dom('[data-test-alert]').hasText('There is an error with this form.', 'Alert renders');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { setupMirage } from 'ember-cli-mirage/test-support';
|
|||
import { render, click, fillIn } from '@ember/test-helpers';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
import sinon from 'sinon';
|
||||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||
|
||||
module('Integration | Component | ldap | Page::Library::CreateAndEdit', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
|
@ -87,10 +88,10 @@ module('Integration | Component | ldap | Page::Library::CreateAndEdit', function
|
|||
await click('[data-test-save]');
|
||||
|
||||
assert
|
||||
.dom('[data-test-field-validation="name"]')
|
||||
.dom(GENERAL.validationErrorByAttr('name'))
|
||||
.hasText('Library name is required.', 'Name validation error renders');
|
||||
assert
|
||||
.dom('[data-test-field-validation="service_account_names"]')
|
||||
.dom(GENERAL.validationErrorByAttr('service_account_names'))
|
||||
.hasText('At least one service account is required.', 'Service account name validation error renders');
|
||||
assert
|
||||
.dom('[data-test-invalid-form-message]')
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ module('Integration | Component | pki-generate-csr', function (hooks) {
|
|||
await click('[data-test-save]');
|
||||
|
||||
assert
|
||||
.dom('[data-test-field-validation="type"]')
|
||||
.dom(GENERAL.validationErrorByAttr('type'))
|
||||
.hasText('Type is required.', 'Type validation error renders');
|
||||
assert
|
||||
.dom('[data-test-field="commonName"] [data-test-inline-alert]')
|
||||
|
|
|
|||
|
|
@ -46,10 +46,10 @@ module('Integration | Component | pki key form', function (hooks) {
|
|||
|
||||
await click(GENERAL.saveButton);
|
||||
assert
|
||||
.dom(GENERAL.validation('type'))
|
||||
.dom(GENERAL.validationErrorByAttr('type'))
|
||||
.hasTextContaining('Type is required.', 'renders presence validation for type of key');
|
||||
assert
|
||||
.dom(GENERAL.validation('keyType'))
|
||||
.dom(GENERAL.validationErrorByAttr('keyType'))
|
||||
.hasTextContaining('Please select a key type.', 'renders selection prompt for key type');
|
||||
assert
|
||||
.dom(PKI_KEY_FORM.validationError)
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ module('Integration | Component | sync | Secrets::Page::Destinations::CreateAndE
|
|||
await this.renderFormComponent();
|
||||
await typeIn(PAGE.inputByAttr('teamId'), 'id');
|
||||
assert
|
||||
.dom(PAGE.validationWarning('teamId'))
|
||||
.dom(PAGE.validationWarningByAttr('teamId'))
|
||||
.doesNotExist('does not render warning validation for new vercel-project destination');
|
||||
|
||||
// existing model
|
||||
|
|
@ -240,7 +240,7 @@ module('Integration | Component | sync | Secrets::Page::Destinations::CreateAndE
|
|||
await PAGE.form.fillInByAttr('teamId', '');
|
||||
await typeIn(PAGE.inputByAttr('teamId'), 'edit');
|
||||
assert
|
||||
.dom(PAGE.validationWarning('teamId'))
|
||||
.dom(PAGE.validationWarningByAttr('teamId'))
|
||||
.hasText(
|
||||
'Team ID should only be updated if the project was transferred to another account.',
|
||||
'it renders validation warning'
|
||||
|
|
@ -338,7 +338,7 @@ module('Integration | Component | sync | Secrets::Page::Destinations::CreateAndE
|
|||
// only asserts validations for presence, refactor if validations change
|
||||
for (const attr in validationAssertions) {
|
||||
const { message } = validationAssertions[attr].find((v) => v.type === 'presence');
|
||||
assert.dom(PAGE.validation(attr)).hasText(message, `renders validation: ${message}`);
|
||||
assert.dom(PAGE.validationErrorByAttr(attr)).hasText(message, `renders validation: ${message}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { hbs } from 'ember-cli-htmlbars';
|
|||
import sinon from 'sinon';
|
||||
import { setRunOptions } from 'ember-a11y-testing/test-support';
|
||||
import { CERTIFICATES } from 'vault/tests/helpers/pki/pki-helpers';
|
||||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||
|
||||
const SELECTORS = {
|
||||
label: '[data-test-text-file-label]',
|
||||
|
|
@ -97,9 +98,7 @@ module('Integration | Component | text-file', function (hooks) {
|
|||
await render(hbs`<TextFile @onChange={{this.onChange}} />`);
|
||||
|
||||
await triggerEvent(SELECTORS.fileUpload, 'change', { files: [this.file] });
|
||||
assert
|
||||
.dom('[data-test-field-validation="text-file"]')
|
||||
.hasText('There was a problem uploading. Please try again.');
|
||||
assert.dom(GENERAL.inlineAlert).hasText('There was a problem uploading. Please try again.');
|
||||
assert.propEqual(
|
||||
this.onChange.lastCall.args[0],
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue