UI: Regex validation on transform templates (#11586)
* Add regex validator component with tests, add to form-field, use in transform template * Update tests with data-test selectors * Add changelog
This commit is contained in:
parent
872a4bd25f
commit
d65947134d
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
ui: Add regex validation to Transform Template pattern input
|
||||
```
|
|
@ -0,0 +1,68 @@
|
|||
<div class="field">
|
||||
<div class="regex-label-wrapper">
|
||||
<div class="regex-label">
|
||||
<label for="{{@attr.name}}" class="is-label">
|
||||
{{@labelString}}
|
||||
{{#if @attr.options.helpText}}
|
||||
{{#info-tooltip}}
|
||||
<span data-test-help-text>
|
||||
{{@attr.options.helpText}}
|
||||
</span>
|
||||
{{/info-tooltip}}
|
||||
{{/if}}
|
||||
</label>
|
||||
{{#if @attr.options.subText}}
|
||||
<p class="sub-text">{{@attr.options.subText}} {{#if @attr.options.docLink}}<a href="{{@attr.options.docLink}}" target="_blank" rel="noopener noreferrer">See our documentation</a> for help.{{/if}}</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div>
|
||||
<Toggle
|
||||
@name={{concat @attr.name "-validation-toggle"}}
|
||||
@status="success"
|
||||
@size="small"
|
||||
@checked={{this.showTestValue}}
|
||||
@onChange={{this.toggleTestValue}}
|
||||
>
|
||||
<span class="has-text-grey">Validation</span>
|
||||
</Toggle>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
id={{@attr.name}}
|
||||
data-test-input={{@attr.name}}
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
{{on 'change' @onChange}}
|
||||
value={{@value}}
|
||||
class="input"
|
||||
/>
|
||||
</div>
|
||||
{{#if this.showTestValue}}
|
||||
<div data-test-regex-validator-test-string>
|
||||
<label for="{{@attr.name}}" class="is-label">
|
||||
Test string
|
||||
</label>
|
||||
<input
|
||||
data-test-input={{concat @attr.name "-testval"}}
|
||||
id={{concat @attr.name "-testval"}}
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
value={{this.testValue}}
|
||||
{{on 'change' this.updateTestValue}}
|
||||
class="input {{if this.regexError 'has-error'}}" />
|
||||
|
||||
{{#if (and this.testValue @value)}}
|
||||
<div data-test-regex-validation-message>
|
||||
{{#if this.regexError}}
|
||||
<AlertInline @type="danger" @message="Your regex doesn't match the subject string" />
|
||||
{{else}}
|
||||
<div class="message-inline">
|
||||
<Icon @glyph="check-circle-fill" class="has-text-success" aria-hidden="true" />
|
||||
<p data-test-inline-success-message>Your regex matches the subject string</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* @module RegexValidator
|
||||
* RegexValidator components are used to provide input forms for regex values, along with a toggle-able validation input which does not get saved to the model.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* const attrExample = {
|
||||
* name: 'valName',
|
||||
* options: {
|
||||
* helpText: 'Shows in tooltip',
|
||||
* subText: 'Shows underneath label',
|
||||
* docLink: 'Adds docs link to subText if present',
|
||||
* defaultValue: 'hello', // Shows if no value on model
|
||||
* }
|
||||
* }
|
||||
* <RegexValidator @onChange={action 'myAction'} @attr={attrExample} @labelString="Label String" @value="initial value" />
|
||||
* ```
|
||||
* @param {func} onChange - the action that should trigger when the main input is changed.
|
||||
* @param {string} value - the value of the main input which will be updated in onChange
|
||||
* @param {string} labelString - Form label. Anticipated from form-field
|
||||
* @param {object} attr - attribute from model. Anticipated from form-field. Example of attribute shape above
|
||||
*/
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
|
||||
export default class RegexValidator extends Component {
|
||||
@tracked testValue = '';
|
||||
@tracked showTestValue = false;
|
||||
|
||||
get regexError() {
|
||||
const testString = this.testValue;
|
||||
if (!testString || !this.args.value) return false;
|
||||
const regex = new RegExp(this.args.value, 'g');
|
||||
const matchArray = testString.toString().match(regex);
|
||||
return testString !== matchArray?.join('');
|
||||
}
|
||||
|
||||
@action
|
||||
updateTestValue(evt) {
|
||||
this.testValue = evt.target.value;
|
||||
}
|
||||
|
||||
@action
|
||||
toggleTestValue() {
|
||||
this.showTestValue = !this.showTestValue;
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ const M = Model.extend({
|
|||
}),
|
||||
type: attr('string', { defaultValue: 'regex' }),
|
||||
pattern: attr('string', {
|
||||
editType: 'regex',
|
||||
subText: 'The template’s pattern defines the data format. Expressed in regex.',
|
||||
}),
|
||||
alphabet: attr('array', {
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
.regex-label-wrapper {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
.regex-label {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
.regex-toggle {
|
||||
flex: 0 1 auto;
|
||||
}
|
|
@ -80,6 +80,7 @@
|
|||
@import './components/radio-card';
|
||||
@import './components/radial-progress';
|
||||
@import './components/raft-join';
|
||||
@import './components/regex-validator';
|
||||
@import './components/replication-dashboard';
|
||||
@import './components/replication-doc-link';
|
||||
@import './components/replication-header';
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"ttl"
|
||||
"stringArray"
|
||||
"json"
|
||||
"regex"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -129,6 +130,18 @@
|
|||
@initialValue={{or (get model valuePath) attr.options.setDefault}}
|
||||
/>
|
||||
</div>
|
||||
{{else if (eq attr.options.editType "regex")}}
|
||||
{{!-- Regex Validated Input --}}
|
||||
<RegexValidator
|
||||
@attr={{attr}}
|
||||
@labelString={{labelString}}
|
||||
@value={{or (get model valuePath)
|
||||
attr.options.defaultValue}}
|
||||
@onChange={{action
|
||||
(action "setAndBroadcast" valuePath)
|
||||
value="target.value"
|
||||
}}
|
||||
/>
|
||||
{{else if (eq attr.options.editType "optionalText")}}
|
||||
{{!-- Togglable Text Input --}}
|
||||
<Toggle
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
import EmberObject from '@ember/object';
|
||||
import sinon from 'sinon';
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render, click, fillIn } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
|
||||
module('Integration | Component | regex-validator', function(hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it renders input and validation messages', async function(assert) {
|
||||
let attr = EmberObject.create({
|
||||
name: 'example',
|
||||
});
|
||||
let spy = sinon.spy();
|
||||
this.set('onChange', spy);
|
||||
this.set('attr', attr);
|
||||
this.set('value', '(\\d{4})');
|
||||
this.set('labelString', 'Regex Example');
|
||||
|
||||
await render(
|
||||
hbs`<RegexValidator
|
||||
@onChange={{onChange}}
|
||||
@attr={{attr}}
|
||||
@value={{value}}
|
||||
@labelString={{labelString}}
|
||||
/>`
|
||||
);
|
||||
assert.dom('.regex-label label').hasText('Regex Example', 'Label is correct');
|
||||
assert.dom('[data-test-toggle-input="example-validation-toggle"]').exists('Validation toggle exists');
|
||||
assert.dom('[data-test-regex-validator-test-string]').doesNotExist('Test string input does not show');
|
||||
|
||||
await click('[data-test-toggle-input="example-validation-toggle"]');
|
||||
assert.dom('[data-test-regex-validator-test-string]').exists('Test string input shows after toggle');
|
||||
assert
|
||||
.dom('[data-test-regex-validation-message]')
|
||||
.doesNotExist('Validation message does not show if test string is empty');
|
||||
|
||||
await fillIn('[data-test-input="example-testval"]', '123a');
|
||||
assert.dom('[data-test-regex-validation-message]').exists('Validation message shows after input filled');
|
||||
assert
|
||||
.dom('[data-test-inline-error-message]')
|
||||
.hasText("Your regex doesn't match the subject string", 'Shows error when regex does not match string');
|
||||
|
||||
await fillIn('[data-test-input="example-testval"]', '1234');
|
||||
assert
|
||||
.dom('[data-test-inline-success-message]')
|
||||
.hasText('Your regex matches the subject string', 'Shows success when regex matches');
|
||||
|
||||
await fillIn('[data-test-input="example-testval"]', '12345');
|
||||
assert
|
||||
.dom('[data-test-inline-error-message]')
|
||||
.hasText(
|
||||
"Your regex doesn't match the subject string",
|
||||
"Shows error if regex doesn't match complete string"
|
||||
);
|
||||
await fillIn('[data-test-input="example"]', '(\\d{5})');
|
||||
assert.ok(spy.calledOnce, 'Calls the passed onChange function when main input is changed');
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue