Skip to content

Commit efcb987

Browse files
committed
fix: validation error shown in NcNoteCard for QuestionShort
also add missing aria error handling Signed-off-by: Christian Hartmann <chris-hartmann@gmx.de>
1 parent 722cb76 commit efcb987

9 files changed

Lines changed: 47 additions & 29 deletions

File tree

playwright/support/sections/SubmitSection.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ export class SubmitSection {
4646
): Promise<void> {
4747
const question = this.getQuestion(questionName)
4848
await question.getByRole('textbox').fill(value)
49+
// Wait for the debounced input handler to fire (INPUT_DEBOUNCE_MS = 400ms)
50+
await this.page.waitForTimeout(500)
4951
}
5052

5153
/**

src/components/Questions/QuestionColor.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
:modelValue="pickedColor"
2020
advancedFields
2121
:aria-required="isRequired"
22+
:aria-errormessage="hasError ? errorId : undefined"
23+
:aria-invalid="hasError ? 'true' : undefined"
2224
@update:modelValue="onUpdatePickedColor">
2325
<NcButton :disabled="!readOnly">
2426
{{ colorPickerPlaceholder }}

src/components/Questions/QuestionDate.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@
9797
:disabledDate="disabledDates"
9898
:disabledTime="disabledTimes"
9999
:aria-required="isRequired"
100+
:aria-errormessage="hasError ? errorId : undefined"
101+
:aria-invalid="hasError ? 'true' : undefined"
100102
clearable
101103
@update:modelValue="onValueChange" />
102104
</div>

src/components/Questions/QuestionDropdown.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
:searchable="false"
4242
label="text"
4343
:aria-label-combobox="selectOptionPlaceholder"
44+
:aria-errormessage="hasError ? errorId : undefined"
45+
:aria-invalid="hasError ? 'true' : undefined"
4446
@invalid.prevent="validate"
4547
@update:modelValue="onInput" />
4648
</div>

src/components/Questions/QuestionFile.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,9 @@
111111
class="question__input-wrapper"
112112
role="group"
113113
:aria-labelledby="titleId"
114-
:aria-describedby="description ? descriptionId : undefined">
114+
:aria-describedby="description ? descriptionId : undefined"
115+
:aria-errormessage="hasError ? errorId : undefined"
116+
:aria-invalid="hasError ? 'true' : undefined">
115117
<label>
116118
{{ t('forms', 'Add new file as answer') }}
117119
<input

src/components/Questions/QuestionLinearScale.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@
8181
<NcCheckboxRadioSwitch
8282
:id="`linear-scale-${id}-${option}`"
8383
:aria-describedby="index === 0 ? labelId : undefined"
84+
:aria-errormessage="hasError ? errorId : undefined"
85+
:aria-invalid="hasError ? 'true' : undefined"
8486
:disabled="!readOnly"
8587
:modelValue="questionValues"
8688
:value="option.toString()"

src/components/Questions/QuestionLong.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
ref="textarea"
1616
:aria-labelledby="titleId"
1717
:aria-describedby="description ? descriptionId : undefined"
18+
:aria-errormessage="hasError ? errorId : undefined"
19+
:aria-invalid="hasError ? 'true' : undefined"
1820
:placeholder="submissionInputPlaceholder"
1921
:disabled="!readOnly"
2022
:required="isRequired"

src/components/Questions/QuestionRanking.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232
class="question__content"
3333
role="list"
3434
:aria-labelledby="titleId"
35-
:aria-describedby="description ? descriptionId : undefined">
35+
:aria-describedby="description ? descriptionId : undefined"
36+
:aria-errormessage="hasError ? errorId : undefined"
37+
:aria-invalid="hasError ? 'true' : undefined">
3638
<!-- Unranked pool -->
3739
<div class="ranking-unranked">
3840
<Draggable

src/components/Questions/QuestionShort.vue

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
ref="input"
1616
:aria-labelledby="titleId"
1717
:aria-describedby="description ? descriptionId : undefined"
18+
:aria-errormessage="hasError ? errorId : undefined"
19+
:aria-invalid="hasError ? 'true' : undefined"
1820
:placeholder="submissionInputPlaceholder"
1921
:disabled="!readOnly"
2022
:name="name || undefined"
@@ -78,12 +80,14 @@
7880

7981
<script>
8082
import IconRegex from '@material-symbols/svg-400/outlined/regular_expression.svg?raw'
83+
import debounce from 'debounce'
8184
import NcActionInput from '@nextcloud/vue/components/NcActionInput'
8285
import NcActionRadio from '@nextcloud/vue/components/NcActionRadio'
8386
import NcActions from '@nextcloud/vue/components/NcActions'
8487
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
8588
import Question from './Question.vue'
8689
import QuestionMixin from '../../mixins/QuestionMixin.js'
90+
import { INPUT_DEBOUNCE_MS } from '../../models/Constants.ts'
8791
import validationTypes from '../../models/ValidationTypes.js'
8892
import { splitRegex, validateExpression } from '../../utils/RegularExpression.js'
8993
@@ -159,44 +163,42 @@ export default {
159163
160164
methods: {
161165
async validate() {
162-
if (
163-
this.isRequired
164-
&& (this.values.length === 0 || this.values[0] === '')
165-
) {
166+
/** @type {HTMLInputElement} */
167+
const input = this.$refs.input
168+
const value = input.value
169+
170+
// Clear the previous custom error before checking native validity.
171+
input.setCustomValidity('')
172+
173+
if (this.isRequired && input.validity.valueMissing) {
166174
this.errorMessage = t('forms', 'You must answer this question')
167175
return false
168176
}
169177
178+
const isCustomValid =
179+
!value
180+
|| this.validationObject.validate(
181+
value,
182+
splitRegex(this.validationRegex),
183+
)
184+
185+
if (!input.validity.valid || !isCustomValid) {
186+
input.setCustomValidity(this.validationObject.errorMessage)
187+
this.errorMessage = this.validationObject.errorMessage
188+
return false
189+
}
190+
170191
this.errorMessage = null
171192
return true
172193
},
173194
174-
onInput() {
175-
/** @type {HTMLObjectElement} */
195+
onInput: debounce(async function () {
196+
/** @type {HTMLInputElement} */
176197
const input = this.$refs.input
177-
/** @type {string} */
178198
const value = input.value
179-
180-
input.setCustomValidity('')
181-
182-
// Only check non empty values, this question might not be required, if not already invalid
183-
if (value) {
184-
// Then check native browser validation (might be better then our)
185-
// If the browsers validation succeeds either the browser does not implement a validation
186-
// or it is valid, so we double check by running our custom validation.
187-
if (
188-
!input.checkValidity()
189-
|| !this.validationObject.validate(
190-
value,
191-
splitRegex(this.validationRegex),
192-
)
193-
) {
194-
input.setCustomValidity(this.validationObject.errorMessage)
195-
}
196-
}
197-
198199
this.$emit('update:values', [value])
199-
},
200+
this.validate()
201+
}, INPUT_DEBOUNCE_MS),
200202
201203
/**
202204
* Change input type

0 commit comments

Comments
 (0)