Skip to content

Commit

Permalink
SelectBox: Set appropriate "aria-invalid" attribute value when input …
Browse files Browse the repository at this point in the history
…is empty or not (T1230706) (DevExpress#27402)

Signed-off-by: Joshua Victoria <[email protected]>
  • Loading branch information
jdvictoria authored Jun 11, 2024
1 parent bc127f1 commit 4e34295
Show file tree
Hide file tree
Showing 3 changed files with 266 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -404,12 +404,15 @@ const DropDownEditor = TextBox.inherit({
},

_integrateInput: function() {
const { isValid } = this.option();

this._renderFocusState();
this._refreshValueChangeEvent();
this._refreshEvents();
this._refreshEmptinessEvent();
this._setDefaultAria();
this._setFieldAria();
this._toggleValidationClasses(!isValid);
this.option('_onMarkupRendered')?.();
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2248,46 +2248,153 @@ QUnit.module('aria accessibility', () => {
assert.equal($input.attr('aria-expanded'), 'false', 'aria-expanded property on closed');
});

QUnit.test('component with fieldTemplate should retain aria attributes after interaction (T1230696, T1230971)', function(assert) {
const $dropDownEditor = $('#dropDownEditorSecond').dxDropDownEditor({
dataSource: ['one', 'two', 'three'],
fieldTemplate: (data) => {
return $('<div>').dxTextBox({ value: data });
},
valueChangeEvent: 'keyup',
}).dxValidator({
validationRules: [ { type: 'required' } ]
});
let $input = $dropDownEditor.find(`.${TEXT_EDITOR_INPUT_CLASS}`);
[
{ attribute: 'aria-required', value: 'true' },
{ attribute: 'aria-haspopup', value: 'true' },
{ attribute: 'aria-autocomplete', value: 'none' },
].forEach(({ attribute, value }) => {
QUnit.test(`component with fieldTemplate should have proper ${attribute} attribute after interaction (T1230696, T1230971)`, function(assert) {
const $dropDownEditor = $('#dropDownEditorSecond').dxDropDownEditor({
dataSource: ['one', 'two', 'three'],
fieldTemplate: (data) => {
return $('<div>').dxTextBox({ value: data });
},
valueChangeEvent: 'keyup',
}).dxValidator({
validationRules: [ { type: 'required' } ]
});
let $input = $dropDownEditor.find(`.${TEXT_EDITOR_INPUT_CLASS}`);

assert.strictEqual($input.attr('aria-required'), 'true', 'initial render should have aria-required attribute set to true');
assert.strictEqual($input.attr(attribute), value, `initial render should have ${attribute} attribute set to ${value}`);

assert.strictEqual($input.attr('aria-haspopup'), 'true', 'initial render should have aria-haspopup attribute set to true');
keyboardMock($input)
.type('a');

assert.strictEqual($input.attr('aria-autocomplete'), 'none', 'initial render should have aria-autocomplete attribute set to none');
$input = $dropDownEditor.find(`.${TEXT_EDITOR_INPUT_CLASS}`);
assert.strictEqual($input.attr(attribute), value, `${attribute} attribute should remain ${value} after typing`);

keyboardMock($input)
.type('a');
keyboardMock($input)
.caret(1)
.press('backspace');

$input = $dropDownEditor.find(`.${TEXT_EDITOR_INPUT_CLASS}`);
$input = $dropDownEditor.find(`.${TEXT_EDITOR_INPUT_CLASS}`);
assert.strictEqual($input.attr(attribute), value, `${attribute} attribute should remain ${value} after deleting`);
});
});

QUnit.module('aria-invalid', {}, () => {
[
{ valueRequired: true, emptyValue: 'true', nonEmptyValue: undefined },
{ valueRequired: false, emptyValue: undefined, nonEmptyValue: undefined }
].forEach(({ valueRequired, emptyValue, nonEmptyValue }) => {
QUnit.test(`component with fieldTemplate should have proper aria-invalid attribute when validator is used and value is ${!valueRequired ? 'not' : ''} required (T1230706)`, function(assert) {
const clock = sinon.useFakeTimers();

const $dropDownEditor = $('#dropDownEditorSecond').dxDropDownEditor({
dataSource: ['one', 'two', 'three'],
searchEnabled: true,
fieldTemplate: 'field',
templatesRenderAsynchronously: true,
integrationOptions: {
templates: {
field: {
render: function({ model, container, onRendered }) {
const $input = $('<div>').appendTo(container);

setTimeout(() => {
$input.dxTextBox({ value: model });
onRendered();
}, 0);
}
}
}
},
valueChangeEvent: 'keyup',
}).dxValidator({
validationRules: valueRequired ? [{ type: 'required', message: 'required' }] : [],
});

assert.strictEqual($input.attr('aria-required'), 'true', 'aria-required attribute should remain true after typing');
clock.tick(500);

assert.strictEqual($input.attr('aria-haspopup'), 'true', 'aria-haspopup attribute should retain to true after typing');
let $input = $dropDownEditor.find(`.${TEXT_EDITOR_INPUT_CLASS}`);

assert.strictEqual($input.attr('aria-autocomplete'), 'none', 'aria-autocomplete attribute should retain to none after typing');
assert.strictEqual($input.attr('aria-invalid'), nonEmptyValue, `initial render should set aria-invalid to ${nonEmptyValue}`);

keyboardMock($input)
.caret(1)
.press('backspace');
keyboardMock($input)
.type('a');

$input = $dropDownEditor.find(`.${TEXT_EDITOR_INPUT_CLASS}`);
clock.tick(500);

$input = $dropDownEditor.find(`.${TEXT_EDITOR_INPUT_CLASS}`);
assert.equal($input.val(), 'a', 'input value is not empty');
assert.strictEqual($input.attr('aria-invalid'), nonEmptyValue, `input should set 'aria-invalid' to ${nonEmptyValue} after typing`);

keyboardMock($input)
.caret(1)
.press('backspace');

clock.tick(500);

$input = $dropDownEditor.find(`.${TEXT_EDITOR_INPUT_CLASS}`);
assert.equal($input.val(), '', 'input value is empty');
assert.strictEqual($input.attr('aria-invalid'), emptyValue, `input should set 'aria-invalid' to ${emptyValue} after deleting`);

clock.restore();
});
});

QUnit.test('component with fieldTemplate should not have aria-invalid attribute when validator is not used (T1230706)', function(assert) {
const clock = sinon.useFakeTimers();

const $dropDownEditor = $('#dropDownEditorSecond').dxDropDownEditor({
dataSource: ['one', 'two', 'three'],
searchEnabled: true,
fieldTemplate: 'field',
templatesRenderAsynchronously: true,
integrationOptions: {
templates: {
field: {
render: function({ model, container, onRendered }) {
const $input = $('<div>').appendTo(container);

setTimeout(() => {
$input.dxTextBox({ value: model });
onRendered();
}, 0);
}
}
}
},
valueChangeEvent: 'keyup',
});

assert.strictEqual($input.attr('aria-required'), 'true', 'aria-required attribute should remain true after deleting');
clock.tick(500);

assert.strictEqual($input.attr('aria-haspopup'), 'true', 'aria-haspopup attribute should retain to true after deleting');
let $input = $dropDownEditor.find(`.${TEXT_EDITOR_INPUT_CLASS}`);

assert.strictEqual($input.attr('aria-invalid'), undefined, 'initial render should set aria-invalid to undefined');

keyboardMock($input)
.type('a');

assert.strictEqual($input.attr('aria-autocomplete'), 'none', 'aria-autocomplete attribute should retain to none after deleting');
clock.tick(500);

$input = $dropDownEditor.find(`.${TEXT_EDITOR_INPUT_CLASS}`);
assert.equal($input.val(), 'a', 'input value is not empty');
assert.strictEqual($input.attr('aria-invalid'), undefined, 'input should set \'aria-invalid\' to undefined after typing');

keyboardMock($input)
.caret(1)
.press('backspace');

clock.tick(500);

$input = $dropDownEditor.find(`.${TEXT_EDITOR_INPUT_CLASS}`);
assert.equal($input.val(), '', 'input value is empty');
assert.strictEqual($input.attr('aria-invalid'), undefined, 'input should set \'aria-invalid\' to undefined after deleting');

clock.restore();
});
});

QUnit.test('component with fieldTemplate should have proper role attribute after interaction (T1230635)', function(assert) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3294,43 +3294,146 @@ QUnit.module('search', moduleSetup, () => {
assert.equal($selectBox.find(toSelector(TEXTEDITOR_INPUT_CLASS)).val(), 'Name 2', 'selectBox displays right value');
});

QUnit.test('component with fieldTemplate should retain aria attributes after search and selection (T1230696, T1230971)', function(assert) {
const $selectBox = $('#selectBox').dxSelectBox({
dataSource: ['a', 'ab', 'abc'],
fieldTemplate: () => {
return $('<div>').dxTextBox({});
},
searchEnabled: true,
searchTimeout: 0,
itemTemplate: () => {
return '<div><span></span></div>';
}
}).dxValidator({
validationRules: [ { type: 'required' } ]
[
{ attribute: 'aria-required', value: 'true' },
{ attribute: 'aria-haspopup', value: 'listbox' },
{ attribute: 'aria-autocomplete', value: 'list' },
].forEach(({ attribute, value }) => {
QUnit.test(`component with fieldTemplate should have correct ${attribute} attribute after search and selection (T1230696, T1230971)`, function(assert) {
const $selectBox = $('#selectBox').dxSelectBox({
dataSource: ['a', 'ab', 'abc'],
fieldTemplate: () => {
return $('<div>').dxTextBox({});
},
searchEnabled: true,
searchTimeout: 0,
itemTemplate: () => {
return '<div><span></span></div>';
}
}).dxValidator({
validationRules: [ { type: 'required' } ]
});
const selectBox = $selectBox.dxSelectBox('instance');
let $input = $selectBox.find(toSelector(TEXTEDITOR_INPUT_CLASS));

assert.strictEqual($input.attr(attribute), value, `initial render should have ${attribute} attribute set to ${value}`);

keyboardMock($input)
.type('a');

const listItem = $(selectBox.content()).find(toSelector(LIST_ITEM_CLASS)).eq(1);
listItem.trigger('dxclick');

$input = $selectBox.find(toSelector(TEXTEDITOR_INPUT_CLASS));
assert.strictEqual($input.attr(attribute), value, `${attribute} should stay ${value} after search and selection`);
});
});

QUnit.module('aria-invalid', {}, () => {
[
{ valueRequired: true, emptyValue: 'true', nonEmptyValue: undefined },
{ valueRequired: false, emptyValue: undefined, nonEmptyValue: undefined }
].forEach(({ valueRequired, emptyValue, nonEmptyValue }) => {
QUnit.test(`component with fieldTemplate should have proper aria-invalid attribute when validator is used and value is ${!valueRequired ? 'not' : ''} required (T1230706)`, function(assert) {
const $selectBox = $('#selectBox').dxSelectBox({
items: [1, 2, 3],
searchEnabled: true,
fieldTemplate: 'field',
showClearButton: true,
templatesRenderAsynchronously: true,
integrationOptions: {
templates: {
field: {
render: function({ model, container, onRendered }) {
const $input = $('<div>').appendTo(container);

setTimeout(() => {
$input.dxTextBox({ value: model });
onRendered();
}, 0);
}
}
}
},
}).dxValidator({
validationRules: valueRequired ? [{ type: 'required', message: 'required' }] : [],
});

this.clock.tick(TIME_TO_WAIT);

const selectBox = $selectBox.dxSelectBox('instance');
let $input = $selectBox.find(toSelector(TEXTEDITOR_INPUT_CLASS));

assert.strictEqual($input.attr('aria-invalid'), nonEmptyValue, `initial render should set aria-invalid to ${nonEmptyValue}`);

const listItem = $(selectBox.content()).find(toSelector(LIST_ITEM_CLASS)).eq(0);
listItem.trigger('dxclick');

this.clock.tick(TIME_TO_WAIT);

assert.equal($input.val(), '1', 'input value is not empty');
$input = $selectBox.find(toSelector(TEXTEDITOR_INPUT_CLASS));
assert.strictEqual($input.attr('aria-invalid'), nonEmptyValue, `non empty input value set aria-invalid to ${nonEmptyValue}`);

const $clearButton = $(toSelector(CLEAR_BUTTON_AREA));
$($clearButton).trigger('dxclick');

this.clock.tick(TIME_TO_WAIT);

assert.equal($input.val(), '', 'input value is empty');
$input = $selectBox.find(toSelector(TEXTEDITOR_INPUT_CLASS));
assert.strictEqual($input.attr('aria-invalid'), emptyValue, `empty input value set aria-invalid to ${emptyValue}`);
});
});
let $input = $selectBox.find(toSelector(TEXTEDITOR_INPUT_CLASS));

assert.strictEqual($input.attr('aria-required'), 'true', 'initial render should have aria-required attribute set to true');
QUnit.test('component with fieldTemplate should not have aria-invalid attribute when validator is not used (T1230706)', function(assert) {
const $selectBox = $('#selectBox').dxSelectBox({
items: [1, 2, 3],
searchEnabled: true,
fieldTemplate: 'field',
showClearButton: true,
templatesRenderAsynchronously: true,
integrationOptions: {
templates: {
field: {
render: function({ model, container, onRendered }) {
const $input = $('<div>').appendTo(container);

setTimeout(() => {
$input.dxTextBox({ value: model });
onRendered();
}, 0);
}
}
}
},
});

assert.strictEqual($input.attr('aria-haspopup'), 'listbox', 'initial render should have aria-haspopup attribute set to listbox');
this.clock.tick(TIME_TO_WAIT);

assert.strictEqual($input.attr('aria-autocomplete'), 'list', 'initial render should have aria-autocomplete attribute set to list');
const selectBox = $selectBox.dxSelectBox('instance');
let $input = $selectBox.find(toSelector(TEXTEDITOR_INPUT_CLASS));

const selectBox = $selectBox.dxSelectBox('instance');
const keyboard = keyboardMock($input);
assert.strictEqual($input.attr('aria-invalid'), undefined, 'initial render should set aria-invalid to undefined');

keyboard.type('a');
const listItem = $(selectBox.content()).find(toSelector(LIST_ITEM_CLASS)).eq(0);
listItem.trigger('dxclick');

const listItem = $(selectBox.content()).find(toSelector(LIST_ITEM_CLASS)).eq(1);
listItem.trigger('dxclick');
this.clock.tick(TIME_TO_WAIT);

$input = $selectBox.find(toSelector(TEXTEDITOR_INPUT_CLASS));
assert.equal($input.val(), '1', 'input value is not empty');
$input = $selectBox.find(toSelector(TEXTEDITOR_INPUT_CLASS));
assert.strictEqual($input.attr('aria-invalid'), undefined, 'initial render should set aria-invalid to undefined');

assert.strictEqual($input.attr('aria-required'), 'true', 'aria-required should stay true after search and selection');
const $clearButton = $(toSelector(CLEAR_BUTTON_AREA));
$($clearButton).trigger('dxclick');

assert.strictEqual($input.attr('aria-haspopup'), 'listbox', 'aria-haspopup should stay to listbox after search and selection');
this.clock.tick(TIME_TO_WAIT);

assert.strictEqual($input.attr('aria-autocomplete'), 'list', 'aria-autocomplete should stay to list after search and selection');
assert.equal($input.val(), '', 'input value is empty');
$input = $selectBox.find(toSelector(TEXTEDITOR_INPUT_CLASS));
assert.strictEqual($input.attr('aria-invalid'), undefined, 'initial render should set aria-invalid to undefined');
});
});

QUnit.test('component with fieldTemplate should have proper role attribute after search and selection (T1230635)', function(assert) {
Expand Down

0 comments on commit 4e34295

Please sign in to comment.