Skip to content
This repository has been archived by the owner on Nov 29, 2023. It is now read-only.

Fix: integer and decimal widgets remain visible when form language changes #937

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Test language decision logic
  • Loading branch information
eyelidlessness committed Dec 21, 2022
commit b95e5ce2aee5a35f7c9545862202da57b13707dd
24 changes: 14 additions & 10 deletions src/widget/number-input/number-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ export default class NumberInput extends Widget {
}

get languages() {
const formLanguage = this.element
.closest('form.or')
?.parentElement?.querySelector('#form-languages')?.value;
const formLanguage = this.languageSelect?.value;

let validFormLanguage;

Expand Down Expand Up @@ -112,6 +110,12 @@ export default class NumberInput extends Widget {
constructor(input, options) {
super(input, options);

const formElement = input.closest('form.or');

/** @type {HTMLSelectElement | null} */
this.languageSelect =
formElement.parentElement?.querySelector('#form-languages');

let { characterPattern } = this;

const question = inputModule.getWrapNode(input);
Expand All @@ -125,6 +129,7 @@ export default class NumberInput extends Widget {
this.question = question;
this.message = message;

input.pattern = this.pattern.source;
this.setFormattedValue(input.valueAsNumber);
this.setValidity();

Expand All @@ -136,11 +141,12 @@ export default class NumberInput extends Widget {

characterPattern = this.characterPattern;
question.setAttribute('lang', this.language);
input.pattern = this.pattern.source;
this.setFormattedValue(valueAsNumber);
this.setValidity();
};

document.addEventListener(
formElement.addEventListener(
events.ChangeLanguage().type,
languageChanged
);
Expand Down Expand Up @@ -171,18 +177,16 @@ export default class NumberInput extends Widget {
* @param {number} value
*/
setFormattedValue(value) {
const { formatter, element, pattern } = this;

element.setAttribute('pattern', pattern.source);
const { formatter, element } = this;

if (Number.isNaN(value)) {
if (Number.isNaN(value) || value === '') {
element.value = '';
} else if (value !== '') {
} else {
const formatted = formatter.format(value);

element.value = formatted;

if (element.value !== formatted) {
if (element.value !== formatted && element.value !== value) {
element.value = value;
}
}
Expand Down
208 changes: 208 additions & 0 deletions test/spec/widget.number-input.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,214 @@ describe('Number inputs', () => {
expect(widget.value).to.equal('3.4');
});
});

describe('language selection', () => {
const unsupportedLanguage = 'nopenotreal';

/**
* All of the language codes currently under test in
* format.spec.js, plus a few short code variants.
*/
const supportedLanguages = [
'ar-EG',
'en',
'en-US',
'en-us',
'fr',
'fr-FR',
'fi',
'he',
'ko-KR',
'nl',
'zh',
'zh-HK',
];

/** @type {Form} */
let form;

/** @type {HTMLSelectElement} */
let languageSelect;

beforeEach(() => {
form = loadForm('number-input-widgets.xml');
formElement = form.view.html;

const languageOptions = [
unsupportedLanguage,
...supportedLanguages,
].map((language) => {
const option = document.createElement('option');

option.value = language;

return option;
});

const body = document.createElement('body');

formElement.insertAdjacentElement('beforebegin', body);
body.append(formElement);

const formHeader = document.createElement('header');

formHeader.classList.add('form-header');

const languageSelectContainer =
document.createElement('span');

languageSelectContainer.classList.add(
'form-language-selector'
);
formHeader.append(languageSelectContainer);
body.append(formHeader);

languageSelect = document.createElement('select');
languageSelect.id = 'form-languages';
languageSelectContainer.append(languageSelect);
languageSelect.append(...languageOptions);
formElement.append(languageSelect);
});

supportedLanguages.forEach((language) => {
it(`uses the form language ${language} on load`, async () => {
languageSelect.dataset.defaultLang = language;
form.init();

const control = formElement.querySelector(
DecimalInput.selector
);
const question = control.closest('.question');

expect(question.lang).to.equal(language);
});

it(`uses the form language ${language} on change`, async () => {
form.init();
languageSelect.querySelector(
`[value="${language}"]`
).selected = true;
languageSelect.dispatchEvent(events.Change());

const control = formElement.querySelector(
DecimalInput.selector
);
const question = control.closest('.question');

expect(question.lang).to.equal(language);
});

it(`falls back to the browser language ${language} on load when the selected form language is unsupported by the browser`, () => {
languageSelect.dataset.defaultLang =
unsupportedLanguage;
sandbox
.stub(navigator, 'languages')
.get(() => [language, ...supportedLanguages]);
form.init();

const control = formElement.querySelector(
DecimalInput.selector
);
const question = control.closest('.question');

expect(question.lang).to.equal(language);
});

it(`falls back to the browser language ${language} on change when the selected form language is unsupported by the browser`, () => {
const languagesStub = sandbox.stub(
navigator,
'languages'
);

languageSelect.dataset.defaultLang =
unsupportedLanguage;

languagesStub.get(() => [
'en-GB',
...supportedLanguages,
]);

form.init();

const control = formElement.querySelector(
DecimalInput.selector
);
const question = control.closest('.question');

expect(question.lang).to.equal('en-GB');

languagesStub.get(() => [language, 'en-GB']);

window.dispatchEvent(new Event('languagechange'));

expect(question.lang).to.equal(language);
});

it(`uses the form language ${language} on change after previously falling back to the browser language`, () => {
const languagesStub = sandbox.stub(
navigator,
'languages'
);

languageSelect.dataset.defaultLang =
unsupportedLanguage;

languagesStub.get(() => ['en-GB']);

form.init();

const control = formElement.querySelector(
DecimalInput.selector
);
const question = control.closest('.question');

expect(question.lang).to.equal('en-GB');

languageSelect.querySelector(
`[value="${language}"]`
).selected = true;
languageSelect.dispatchEvent(events.Change());

expect(question.lang).to.equal(language);
});

it(`falls back to the browser language ${language} on change after an unsupported form language is chosen`, () => {
const languagesStub = sandbox.stub(
navigator,
'languages'
);

languageSelect.dataset.defaultLang = language;

languagesStub.get(() => ['en-GB']);

form.init();

const control = formElement.querySelector(
DecimalInput.selector
);
const question = control.closest('.question');

expect(question.lang).to.equal(language);

languageSelect.querySelector(
`[value="${unsupportedLanguage}"]`
).selected = true;
languageSelect.dispatchEvent(events.Change());

expect(question.lang).to.equal('en-GB');
});
});

it.skip('reformats to a localized decimal character when the form language changes', () => {
// This is evidently untestable. The assignment
// `input.value = input.valueAsNumber`
// does cause the browser to reformat the
// number to the specified `lang`, but its
// runtime `value` always uses a period for the
// decimal character.
});
});
}
});
});