Skip to content

Commit 94fceeb

Browse files
#2328: validate sms parts (#2335)
* Fix sms prompt validation when there are sms parts
1 parent 012c741 commit 94fceeb

File tree

3 files changed

+87
-23
lines changed

3 files changed

+87
-23
lines changed

assets/js/components/questionnaires/SmsPrompt.jsx

+28-15
Original file line numberDiff line numberDiff line change
@@ -50,24 +50,36 @@ class SmsPrompt extends Component {
5050
return joinSmsPieces(this.allInputs.map((x) => x.getText() || ""))
5151
}
5252

53+
filterErrorsForPart(errors, index) {
54+
const partErrorPrefix = `[${index}]`
55+
return errors &&
56+
errors
57+
.flat()
58+
// We are interested only in the errors that
59+
// match with the sms part index
60+
.filter(error => error.startsWith(partErrorPrefix))
61+
.map(error => {
62+
// We remove the part prefix
63+
const errorMessage = error.split(partErrorPrefix)[1]
64+
return [errorMessage]
65+
})
66+
}
67+
5368
renderSmsInput(total, index, value) {
5469
let { inputErrors, readOnly, fixedEndLength, label, t } = this.props
55-
56-
const shouldDisplayErrors = value == this.props.originalValue
57-
58-
let textBelow = ""
59-
if (limitExceeded(value)) {
60-
textBelow = k("Use SHIFT+ENTER to split in multiple parts")
61-
}
70+
const last = index + 1 == total
71+
const first = index == 0
72+
const textBelow = limitExceeded(value) ? k("Use SHIFT+ENTER to split in multiple parts") : ""
6273

6374
if (!label) label = t("SMS message")
6475

65-
const labelComponent = total == 1 ? label : `${label} (part ${index + 1})`
66-
const last = index + 1 == total
67-
const fixedLength = last ? fixedEndLength : null
76+
if (total > 1){
77+
label = `${label} (part ${index + 1})`
78+
inputErrors = this.filterErrorsForPart(inputErrors, index)
79+
}
6880

6981
let autocompleteProps = {}
70-
if (index == 0) {
82+
if (first) {
7183
autocompleteProps = {
7284
autocomplete: this.props.autocomplete,
7385
autocompleteGetData: this.props.autocompleteGetData,
@@ -78,15 +90,15 @@ class SmsPrompt extends Component {
7890
const inputComponent = (
7991
<div>
8092
<Draft
81-
label={labelComponent}
93+
label={label}
8294
value={value}
8395
readOnly={readOnly}
84-
errors={shouldDisplayErrors && map(inputErrors, (error) => t(...error))}
96+
errors={map(inputErrors, (error) => t(...error))}
8597
textBelow={textBelow}
8698
onSplit={(caretIndex) => this.splitPiece(caretIndex, index)}
8799
onBlur={(e) => this.onBlur()}
88100
characterCounter
89-
characterCounterFixedLength={fixedLength}
101+
characterCounterFixedLength={last ? fixedEndLength : null}
90102
plainText
91103
ref={(ref) => {
92104
if (ref) {
@@ -97,7 +109,7 @@ class SmsPrompt extends Component {
97109
/>
98110
</div>
99111
)
100-
if (total <= 1 || index == 0) {
112+
if (first) {
101113
return (
102114
<div className="row" key={index}>
103115
<div className="col s12">{inputComponent}</div>
@@ -129,6 +141,7 @@ class SmsPrompt extends Component {
129141
this.allInputs = []
130142

131143
const smsPieces = splitSmsText(value)
144+
132145
const inputs = smsPieces.map((piece, index) => {
133146
return this.renderSmsInput(smsPieces.length, index, piece)
134147
})

assets/js/reducers/questionnaire.validation.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// @flow
2-
import { getStepPrompt, newStepPrompt, newIvrPrompt } from "../step"
2+
import { getStepPrompt, newStepPrompt, newIvrPrompt, smsSplitSeparator } from "../step"
33
import { hasSections, countRelevantSteps } from "./questionnaire"
44

55
const k = (...args: any) => args
@@ -204,6 +204,12 @@ const validateSmsLangPrompt = (
204204
addError(context, `${path}.sms`, k("SMS prompt must not be blank"), lang, "sms")
205205
return
206206
}
207+
const smsParts = prompt.sms && prompt.sms.trim().split(smsSplitSeparator) || []
208+
smsParts.forEach((part, idx) => {
209+
if(isBlank(part)){
210+
addError(context, `${path}.sms`, k(`[${idx}]SMS prompt must not be blank`), lang, "sms")
211+
}
212+
})
207213
}
208214

209215
const validateIvrLangPrompt = (

test/js/reducers/questionnaire.spec.js

+52-7
Original file line numberDiff line numberDiff line change
@@ -1226,19 +1226,48 @@ describe('questionnaire reducer', () => {
12261226
})
12271227
})
12281228

1229-
it('should validate mobileweb message must not be blank if mobileweb mode is on', () => {
1229+
it('should validate SMS parts messages must not be blank if "SMS" mode is on and there are SMS parts', () => {
12301230
const resultState = playActions([
12311231
actions.fetch(1, 1),
12321232
actions.receive(questionnaire),
1233-
actions.addMode('mobileweb'),
1234-
actions.changeStepPromptMobileWeb('17141bea-a81c-4227-bdda-f5f69188b0e7', '')
1233+
actions.removeMode('ivr'),
1234+
actions.addLanguage('es'),
1235+
// Two empty sms parts (empty before and after split separator)
1236+
actions.changeStepPromptSms('17141bea-a81c-4227-bdda-f5f69188b0e7', `${smsSplitSeparator}` ),
1237+
actions.setActiveLanguage('es'),
1238+
// One empty sms parts (empty after split separator)
1239+
actions.changeStepPromptSms('17141bea-a81c-4227-bdda-f5f69188b0e7', `Hola!${smsSplitSeparator} `),
12351240
])
12361241

1237-
expect(resultState.errors).toInclude({
1238-
path: "steps[0].prompt['en'].mobileweb",
1242+
const errors = resultState.errors
1243+
expect(errors).toInclude({
1244+
path: "steps[1].prompt['en'].sms",
12391245
lang: 'en',
1240-
mode: 'mobileweb',
1241-
message: ['Mobile web prompt must not be blank']
1246+
mode: 'sms',
1247+
message: ['[0]SMS prompt must not be blank']
1248+
})
1249+
expect(errors).toInclude({
1250+
path: "steps[1].prompt['en'].sms",
1251+
lang: 'en',
1252+
mode: 'sms',
1253+
message: ['[1]SMS prompt must not be blank']
1254+
})
1255+
1256+
expect(errors).toInclude({
1257+
path: "steps[1].prompt['es'].sms",
1258+
lang: 'es',
1259+
mode: 'sms',
1260+
message: ['[1]SMS prompt must not be blank']
1261+
})
1262+
1263+
expect(resultState.errorsByPath).toInclude({
1264+
"steps[1].prompt['en'].sms": [['[0]SMS prompt must not be blank'], ['[1]SMS prompt must not be blank']],
1265+
"steps[1].prompt['es'].sms": [['[1]SMS prompt must not be blank']]
1266+
})
1267+
1268+
expect(resultState.errorsByLang).toInclude({
1269+
'en': true,
1270+
'es': true,
12421271
})
12431272
})
12441273

@@ -1288,6 +1317,22 @@ describe('questionnaire reducer', () => {
12881317
})
12891318
})
12901319

1320+
it('should validate mobileweb message must not be blank if mobileweb mode is on', () => {
1321+
const resultState = playActions([
1322+
actions.fetch(1, 1),
1323+
actions.receive(questionnaire),
1324+
actions.addMode('mobileweb'),
1325+
actions.changeStepPromptMobileWeb('17141bea-a81c-4227-bdda-f5f69188b0e7', '')
1326+
])
1327+
1328+
expect(resultState.errors).toInclude({
1329+
path: "steps[0].prompt['en'].mobileweb",
1330+
lang: 'en',
1331+
mode: 'mobileweb',
1332+
message: ['Mobile web prompt must not be blank']
1333+
})
1334+
})
1335+
12911336
it('should not include an error if mobile web sms message prompt exceeds the character limit', () => {
12921337
const prompt = 'a'.repeat(141)
12931338

0 commit comments

Comments
 (0)