[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:
Cristiano Rastelli 2025-05-13 20:59:13 +01:00 committed by GitHub
parent 7295887816
commit e9faec3832
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 410 additions and 57 deletions

View file

@ -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}}

View file

@ -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);
}
}

View file

@ -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}}

View file

@ -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}"]`,

View file

@ -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: {

View file

@ -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);
}

View file

@ -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');
});
});

View file

@ -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');
});

View file

@ -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]')

View file

@ -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]')

View file

@ -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)

View file

@ -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}`);
}
});
});

View file

@ -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],
{