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:
Chelsea Shaw 2021-05-12 10:12:33 -05:00 committed by GitHub
parent 872a4bd25f
commit d65947134d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 205 additions and 0 deletions

3
changelog/11586.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
ui: Add regex validation to Transform Template pattern input
```

View File

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

View File

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

View File

@ -19,6 +19,7 @@ const M = Model.extend({
}),
type: attr('string', { defaultValue: 'regex' }),
pattern: attr('string', {
editType: 'regex',
subText: 'The templates pattern defines the data format. Expressed in regex.',
}),
alphabet: attr('array', {

View File

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

View File

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

View File

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

View File

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