DEV: Add validation message to integer fields in theme object editora (#26284)

Why this change?

This is a continuation of 8de869630f.

In our schema, we support the `min` and `max` validation
rules like so:

```
some_objects_setting
  type: objects
  schema:
    name: some_object
    properties:
      id:
        type: integer
        validations:
          min: 5
          max: 10
```

While the validations used to validate the objects on the server side,
we should also add client side validation for better UX.
This commit is contained in:
Alan Guo Xiang Tan 2024-03-21 15:03:07 +08:00 committed by GitHub
parent 8de869630f
commit a30d73f255
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 131 additions and 25 deletions

View File

@ -1,18 +1,80 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { Input } from "@ember/component";
import { on } from "@ember/modifier";
import { action } from "@ember/object";
import { and, not } from "truth-helpers";
import I18n from "discourse-i18n";
import FieldInputDescription from "admin/components/schema-theme-setting/field-input-description";
export default class SchemaThemeSettingTypeInteger extends Component {
@tracked touched = false;
@tracked value = this.args.value;
min = this.args.spec.validations?.min;
max = this.args.spec.validations?.max;
required = this.args.spec.required;
@action
onInput(event) {
this.args.onChange(parseInt(event.currentTarget.value, 10));
this.touched = true;
let newValue = parseInt(event.currentTarget.value, 10);
if (isNaN(newValue)) {
newValue = null;
}
this.value = newValue;
this.args.onChange(newValue);
}
get validationErrorMessage() {
if (!this.touched) {
return;
}
if (!this.value) {
if (this.required) {
return I18n.t("admin.customize.theme.schema.fields.required");
} else {
return;
}
}
if (this.min && this.value < this.min) {
return I18n.t("admin.customize.theme.schema.fields.number.too_small", {
count: this.min,
});
}
if (this.max && this.value > this.max) {
return I18n.t("admin.customize.theme.schema.fields.number.too_large", {
count: this.max,
});
}
}
<template>
<Input @value={{@value}} {{on "input" this.onInput}} @type="number" />
<Input
@value={{this.value}}
{{on "input" this.onInput}}
@type="number"
inputmode="numeric"
pattern="[0-9]*"
max={{this.max}}
min={{this.min}}
required={{this.required}}
/>
<FieldInputDescription @description={{@description}} />
<div class="schema-field__input-supporting-text">
{{#if (and @description (not this.validationErrorMessage))}}
<FieldInputDescription @description={{@description}} />
{{/if}}
{{#if this.validationErrorMessage}}
<div class="schema-field__input-error">
{{this.validationErrorMessage}}
</div>
{{/if}}
</div>
</template>
}

View File

@ -63,11 +63,15 @@ class InputFieldsFromDOM {
refresh() {
this.fields = {};
this.count = 0;
[...queryAll(".schema-field")].forEach((field) => {
this.count += 1;
this.fields[field.dataset.name] = {
labelElement: field.querySelector(".schema-field__label"),
inputElement: field.querySelector(".schema-field__input").children[0],
countElement: field.querySelector(".schema-field__input-count"),
errorElement: field.querySelector(".schema-field__input-error"),
selector: `.schema-field[data-name="${field.dataset.name}"]`,
};
});
@ -449,59 +453,96 @@ module(
<AdminSchemaThemeSettingEditor @themeId="1" @setting={{setting}} />
</template>);
const fieldSelector =
".schema-field[data-name='id'] .schema-field__input";
const inputFields = new InputFieldsFromDOM();
assert.dom(`${fieldSelector} .schema-field__input-count`).hasText("3/5");
assert.dom(inputFields.fields.id.labelElement).hasText("id*");
assert.dom(inputFields.fields.id.countElement).hasText("3/5");
await fillIn(`${fieldSelector} input`, "1");
await fillIn(inputFields.fields.id.inputElement, "1");
assert.dom(`${fieldSelector} .schema-field__input-error`).hasText(
assert.dom(inputFields.fields.id.countElement).hasText("1/5");
inputFields.refresh();
assert.dom(inputFields.fields.id.errorElement).hasText(
I18n.t("admin.customize.theme.schema.fields.string.too_short", {
count: 2,
})
);
await fillIn(`${fieldSelector} input`, "");
await fillIn(inputFields.fields.id.inputElement, "");
assert.dom(`${fieldSelector} .schema-field__input-count`).hasText("0/5");
assert.dom(inputFields.fields.id.countElement).hasText("0/5");
assert
.dom(`${fieldSelector} .schema-field__input-error`)
.dom(inputFields.fields.id.errorElement)
.hasText(I18n.t("admin.customize.theme.schema.fields.required"));
});
test("input fields of type integer", async function (assert) {
const setting = schemaAndData(3);
const setting = ThemeSettings.create({
setting: "objects_setting",
objects_schema: {
name: "something",
identifier: "id",
properties: {
id: {
type: "integer",
required: true,
validations: {
max: 10,
min: 5,
},
},
},
},
value: [
{
id: 6,
},
],
});
await render(<template>
<AdminSchemaThemeSettingEditor @themeId="1" @setting={{setting}} />
</template>);
const inputFields = new InputFieldsFromDOM();
assert.dom(inputFields.fields.id.labelElement).hasText("id*");
assert.dom(inputFields.fields.id.inputElement).hasValue("6");
assert
.dom(inputFields.fields.integer_field.labelElement)
.hasText("integer_field");
assert.dom(inputFields.fields.integer_field.inputElement).hasValue("92");
assert
.dom(inputFields.fields.integer_field.inputElement)
.dom(inputFields.fields.id.inputElement)
.hasAttribute("type", "number");
await fillIn(inputFields.fields.integer_field.inputElement, "922229");
const tree = new TreeFromDOM();
await click(tree.nodes[1].element);
await fillIn(inputFields.fields.id.inputElement, "922229");
inputFields.refresh();
assert.dom(inputFields.fields.integer_field.inputElement).hasValue("820");
assert.dom(inputFields.fields.id.errorElement).hasText(
I18n.t("admin.customize.theme.schema.fields.number.too_large", {
count: 10,
})
);
await fillIn(inputFields.fields.id.inputElement, "0");
inputFields.refresh();
assert.dom(inputFields.fields.id.errorElement).hasText(
I18n.t("admin.customize.theme.schema.fields.number.too_small", {
count: 5,
})
);
await fillIn(inputFields.fields.id.inputElement, "");
tree.refresh();
await click(tree.nodes[0].element);
inputFields.refresh();
assert
.dom(inputFields.fields.integer_field.inputElement)
.hasValue("922229");
.dom(inputFields.fields.id.errorElement)
.hasText(I18n.t("admin.customize.theme.schema.fields.required"));
});
test("input fields of type float", async function (assert) {

View File

@ -5651,6 +5651,9 @@ en:
required: "*required"
string:
too_short: "must be at least %{count} characters"
number:
too_small: "must be greater than or equal to %{count}"
too_large: "must be less than or equal to %{count}"
colors:
select_base: