botium-core 1.12.4 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/LICENSES-3RDPARTY.txt +390 -225
  2. package/dist/botium-cjs.js +298 -161
  3. package/dist/botium-cjs.js.map +1 -1
  4. package/dist/botium-es.js +298 -161
  5. package/dist/botium-es.js.map +1 -1
  6. package/package.json +28 -28
  7. package/src/BotDriver.js +24 -19
  8. package/src/Capabilities.js +8 -0
  9. package/src/Defaults.js +1 -0
  10. package/src/containers/plugins/SimpleRestContainer.js +42 -8
  11. package/src/containers/plugins/index.js +1 -1
  12. package/src/helpers/Utils.js +1 -1
  13. package/src/scripting/CompilerCsv.js +150 -102
  14. package/src/scripting/CompilerXlsx.js +2 -2
  15. package/src/scripting/Convo.js +4 -4
  16. package/src/scripting/ScriptingProvider.js +6 -2
  17. package/src/scripting/helper.js +20 -13
  18. package/src/scripting/logichook/asserter/BaseCountAsserter.js +1 -1
  19. package/src/scripting/logichook/asserter/ButtonsAsserter.js +4 -2
  20. package/src/scripting/logichook/asserter/CardsAsserter.js +4 -2
  21. package/test/compiler/compilercsv.spec.js +363 -12
  22. package/test/compiler/compilertxt.spec.js +13 -0
  23. package/test/compiler/convos/csv/utterances_liveperson.csv +108 -0
  24. package/test/compiler/convos/csv/utterances_multicolumn3col.csv +3 -0
  25. package/test/compiler/convos/csv/utterances_multicolumn5col.csv +3 -0
  26. package/test/compiler/convos/csv/utterances_singlecolumn.csv +3 -0
  27. package/test/compiler/convos/csv/utterances_variable_row_len.csv +3 -0
  28. package/test/compiler/convos/txt/convos_args_escape.convo.txt +2 -0
  29. package/test/connectors/simplerest.spec.js +49 -8
  30. package/test/convo/convos/continuefailing_timeout.convo.txt +16 -0
  31. package/test/convo/transcript.spec.js +18 -1
  32. package/test/logichooks/convos/WAITFORBOT_INFINITE.convo.txt +1 -1
  33. package/test/scripting/asserters/buttonsAsserter.spec.js +47 -0
  34. package/test/scripting/asserters/cardsAsserter.spec.js +39 -0
  35. package/test/scripting/scriptingProvider.spec.js +1 -1
  36. package/test/scripting/txt/decompile.spec.js +24 -0
@@ -47,6 +47,7 @@ module.exports = class CompilerCsv extends CompilerBase {
47
47
  if (scriptData.length === 0) {
48
48
  return []
49
49
  }
50
+ const legacyModeOn = !this._GetOptionalCapability(Capabilities.SCRIPTING_CSV_LEGACY_MODE_OFF, false)
50
51
 
51
52
  let delimiter = this._GetOptionalCapability(Capabilities.SCRIPTING_CSV_DELIMITER)
52
53
  if (!delimiter) {
@@ -71,7 +72,8 @@ module.exports = class CompilerCsv extends CompilerBase {
71
72
  delimiter,
72
73
  escape: this.caps[Capabilities.SCRIPTING_CSV_ESCAPE],
73
74
  quote: this.caps[Capabilities.SCRIPTING_CSV_QUOTE],
74
- columns: false
75
+ columns: false,
76
+ relax_column_count: true
75
77
  })
76
78
  } catch (err) {
77
79
  throw new Error(`Invalid CSV: ${err.message || err}`)
@@ -79,123 +81,169 @@ module.exports = class CompilerCsv extends CompilerBase {
79
81
  if (rows.length === 0) {
80
82
  return []
81
83
  }
82
- if (rows[0].length === 1) {
83
- debug('Found 1-column CSV file, treating it as utterance file')
84
- if (scriptType === Constants.SCRIPTING_TYPE_UTTERANCES) {
85
- const result = [{ name: rows[0][0], utterances: rows.slice(1).map(r => r[0]) }]
86
- this.context.AddUtterances(result)
87
- return result
88
- } else {
84
+ const columnCount = rows[0].length
85
+ debug(`Legacy mode ${legacyModeOn ? 'on' : 'off'} rows ${rows.length} columns ${columnCount}`)
86
+
87
+ if ((scriptType === Constants.SCRIPTING_TYPE_CONVO || scriptType === Constants.SCRIPTING_TYPE_PCONVO)) {
88
+ if (columnCount === 1 || (!legacyModeOn && columnCount > 3)) {
89
+ debug(`Invalid column count '${columnCount}' in convo mode`)
90
+ return []
91
+ }
92
+ let header = null
93
+ if (rows.length > 0 && this.caps[Capabilities.SCRIPTING_CSV_SKIP_HEADER]) {
94
+ header = rows[0]
95
+ rows = rows.slice(1)
96
+ }
97
+ if (rows.length === 0) {
98
+ debug('Datarows not found in convo mode')
89
99
  return []
90
100
  }
91
- }
92
-
93
- if (scriptType !== Constants.SCRIPTING_TYPE_CONVO && scriptType !== Constants.SCRIPTING_TYPE_PCONVO) {
94
- return []
95
- }
96
- let header = null
97
- if (rows.length > 0 && this.caps[Capabilities.SCRIPTING_CSV_SKIP_HEADER]) {
98
- header = rows[0]
99
- rows = rows.slice(1)
100
- }
101
- if (rows.length === 0) {
102
- return []
103
- }
104
-
105
- const lineNumberBase = this.caps[Capabilities.SCRIPTING_CSV_SKIP_HEADER] ? 2 : 1
106
- if (rows[0].length === 2) {
107
- debug('Found 2-column CSV file, treating it as question/answer file')
108
101
 
109
- let colQuestion = DEFAULT_QA_COLUMN_QUESTION
110
- let colAnswer = DEFAULT_QA_COLUMN_ANSWER
102
+ const lineNumberBase = this.caps[Capabilities.SCRIPTING_CSV_SKIP_HEADER] ? 2 : 1
103
+ if (columnCount === 2) {
104
+ let colQuestion = DEFAULT_QA_COLUMN_QUESTION
105
+ let colAnswer = DEFAULT_QA_COLUMN_ANSWER
111
106
 
112
- if (header) {
113
- if (this.caps[Capabilities.SCRIPTING_CSV_QA_COLUMN_QUESTION] !== undefined) {
114
- colQuestion = _findColIndex(header, this.caps[Capabilities.SCRIPTING_CSV_QA_COLUMN_QUESTION])
107
+ if (header) {
108
+ if (this.caps[Capabilities.SCRIPTING_CSV_QA_COLUMN_QUESTION] !== undefined) {
109
+ colQuestion = _findColIndex(header, this.caps[Capabilities.SCRIPTING_CSV_QA_COLUMN_QUESTION])
110
+ }
111
+ if (this.caps[Capabilities.SCRIPTING_CSV_QA_COLUMN_ANSWER] !== undefined) {
112
+ colAnswer = _findColIndex(header, this.caps[Capabilities.SCRIPTING_CSV_QA_COLUMN_ANSWER])
113
+ }
115
114
  }
116
- if (this.caps[Capabilities.SCRIPTING_CSV_QA_COLUMN_ANSWER] !== undefined) {
117
- colAnswer = _findColIndex(header, this.caps[Capabilities.SCRIPTING_CSV_QA_COLUMN_ANSWER])
115
+
116
+ const convos = rows.map((row, i) => new Convo(this.context, {
117
+ header: {
118
+ name: `L${i + lineNumberBase}`
119
+ },
120
+ conversation: [
121
+ Object.assign({},
122
+ linesToConvoStep(
123
+ [row[colQuestion]],
124
+ 'me',
125
+ this.context,
126
+ undefined,
127
+ true
128
+ ), { stepTag: `L${i + lineNumberBase}-Question` }),
129
+ Object.assign({},
130
+ linesToConvoStep(
131
+ [row[colAnswer]],
132
+ 'bot',
133
+ this.context,
134
+ undefined,
135
+ true
136
+ ), { stepTag: `L${i + lineNumberBase}-Answer` })
137
+ ]
138
+ }))
139
+ if (scriptType === Constants.SCRIPTING_TYPE_CONVO) {
140
+ this.context.AddConvos(convos)
141
+ } else if (scriptType === Constants.SCRIPTING_TYPE_PCONVO) {
142
+ this.context.AddPartialConvos(convos)
118
143
  }
144
+ debug(`Found 2-column CSV file, treating it as question/answer file, extracted ${convos.length} convos`)
145
+ return convos
119
146
  }
120
147
 
121
- const convos = rows.map((row, i) => new Convo(this.context, {
122
- header: {
123
- name: `L${i + lineNumberBase}`
124
- },
125
- conversation: [
126
- Object.assign({},
127
- linesToConvoStep(
128
- [row[colQuestion]],
129
- 'me',
130
- this.context,
131
- undefined,
132
- true
133
- ), { stepTag: `L${i + lineNumberBase}-Question` }),
134
- Object.assign({},
135
- linesToConvoStep(
136
- [row[colAnswer]],
137
- 'bot',
138
- this.context,
139
- undefined,
140
- true
141
- ), { stepTag: `L${i + lineNumberBase}-Answer` })
142
- ]
143
- }))
144
- if (scriptType === Constants.SCRIPTING_TYPE_CONVO) {
145
- this.context.AddConvos(convos)
146
- } else if (scriptType === Constants.SCRIPTING_TYPE_PCONVO) {
147
- this.context.AddPartialConvos(convos)
148
- }
149
- return convos
150
- }
148
+ if (columnCount >= 3) {
149
+ let colConversationId = DEFAULT_MULTIROW_COLUMN_CONVERSATION
150
+ let colSender = DEFAULT_MULTIROW_COLUMN_SENDER
151
+ let colText = DEFAULT_MULTIROW_COLUMN_TEXT
151
152
 
152
- if (rows[0].length >= 3) {
153
- debug('Found 3-column CSV file, treating it as multi-row conversation file')
153
+ if (header) {
154
+ if (this.caps[Capabilities.SCRIPTING_CSV_MULTIROW_COLUMN_CONVERSATION_ID] !== undefined) {
155
+ colConversationId = _findColIndex(header, this.caps[Capabilities.SCRIPTING_CSV_MULTIROW_COLUMN_CONVERSATION_ID])
156
+ }
157
+ if (this.caps[Capabilities.SCRIPTING_CSV_MULTIROW_COLUMN_SENDER] !== undefined) {
158
+ colSender = _findColIndex(header, this.caps[Capabilities.SCRIPTING_CSV_MULTIROW_COLUMN_SENDER])
159
+ }
160
+ if (this.caps[Capabilities.SCRIPTING_CSV_MULTIROW_COLUMN_TEXT] !== undefined) {
161
+ colText = _findColIndex(header, this.caps[Capabilities.SCRIPTING_CSV_MULTIROW_COLUMN_TEXT])
162
+ }
163
+ }
154
164
 
155
- let colConversationId = DEFAULT_MULTIROW_COLUMN_CONVERSATION
156
- let colSender = DEFAULT_MULTIROW_COLUMN_SENDER
157
- let colText = DEFAULT_MULTIROW_COLUMN_TEXT
165
+ const conversationIds = _.uniq(rows.map(r => r[colConversationId]))
166
+ const convos = conversationIds.map(conversationId => {
167
+ const convoRows = rows.map((row, i) => {
168
+ if (row[colConversationId] === conversationId) {
169
+ return Object.assign({},
170
+ linesToConvoStep(
171
+ [row[colText]],
172
+ row[colSender],
173
+ this.context,
174
+ undefined,
175
+ true
176
+ ), { stepTag: `L${i + lineNumberBase}` })
177
+ }
178
+ return null
179
+ }).filter(c => c)
180
+ return new Convo(this.context, {
181
+ header: {
182
+ name: conversationId
183
+ },
184
+ conversation: convoRows
185
+ })
186
+ })
187
+ if (scriptType === Constants.SCRIPTING_TYPE_CONVO) {
188
+ this.context.AddConvos(convos)
189
+ } else if (scriptType === Constants.SCRIPTING_TYPE_PCONVO) {
190
+ this.context.AddPartialConvos(convos)
191
+ }
192
+ debug(`Found 3-column CSV file, treating it as multi-row conversation file, extracted ${convos.length} convos`)
193
+ return convos
194
+ }
195
+ } else if (scriptType === Constants.SCRIPTING_TYPE_UTTERANCES) {
196
+ if (columnCount === 2 || columnCount === 3 || (legacyModeOn && columnCount > 4)) {
197
+ debug(`Invalid column count '${columnCount}' in utterances mode`)
198
+ return []
199
+ }
200
+ const result = []
201
+ const startRow = this._GetOptionalCapability(Capabilities.SCRIPTING_CSV_UTTERANCE_STARTROW, 2) - 1
202
+ const startRowHeader = this._GetOptionalCapability(Capabilities.SCRIPTING_CSV_UTTERANCE_STARTROW_HEADER)
203
+ const stopOnEmpty = this._GetOptionalCapability(Capabilities.SCRIPTING_CSV_UTTERANCE_STOP_ON_EMPTY)
204
+
205
+ for (let col = 0; col < columnCount; col++) {
206
+ const name = rows[0][col]
207
+ if (!name || name.trim().length === 0) {
208
+ debug(`Column ${col + 1} has no header, skipping`)
209
+ continue
210
+ }
158
211
 
159
- if (header) {
160
- if (this.caps[Capabilities.SCRIPTING_CSV_MULTIROW_COLUMN_CONVERSATION_ID] !== undefined) {
161
- colConversationId = _findColIndex(header, this.caps[Capabilities.SCRIPTING_CSV_MULTIROW_COLUMN_CONVERSATION_ID])
212
+ const uttStruct = {
213
+ name,
214
+ utterances: []
215
+ }
216
+ let skip = !!startRowHeader
217
+ const getData = (row) => {
218
+ return rows[row][col] ? rows[row][col].trim() : false
162
219
  }
163
- if (this.caps[Capabilities.SCRIPTING_CSV_MULTIROW_COLUMN_SENDER] !== undefined) {
164
- colSender = _findColIndex(header, this.caps[Capabilities.SCRIPTING_CSV_MULTIROW_COLUMN_SENDER])
220
+ //
221
+ for (let row = startRow; row < rows.length && (skip || !stopOnEmpty || !!getData(row)); row++) { // eslint-disable-line no-unmodified-loop-condition
222
+ const data = getData(row)
223
+ if (!data) {
224
+ continue
225
+ }
226
+ if (!skip) {
227
+ uttStruct.utterances.push(data)
228
+ } else {
229
+ if (startRowHeader === rows[row][col]) {
230
+ skip = false
231
+ }
232
+ }
165
233
  }
166
- if (this.caps[Capabilities.SCRIPTING_CSV_MULTIROW_COLUMN_TEXT] !== undefined) {
167
- colText = _findColIndex(header, this.caps[Capabilities.SCRIPTING_CSV_MULTIROW_COLUMN_TEXT])
234
+ if (uttStruct.utterances.length === 0) {
235
+ // liveperson, skipping meta intents
236
+ debug(`Column ${col + 1} has no utterances, skipping`)
237
+ continue
168
238
  }
239
+ result.push(uttStruct)
169
240
  }
170
241
 
171
- const conversationIds = _.uniq(rows.map(r => r[colConversationId]))
172
- const convos = conversationIds.map(conversationId => {
173
- const convoRows = rows.map((row, i) => {
174
- if (row[colConversationId] === conversationId) {
175
- return Object.assign({},
176
- linesToConvoStep(
177
- [row[colText]],
178
- row[colSender],
179
- this.context,
180
- undefined,
181
- true
182
- ), { stepTag: `L${i + lineNumberBase}` })
183
- }
184
- return null
185
- }).filter(c => c)
186
- return new Convo(this.context, {
187
- header: {
188
- name: conversationId
189
- },
190
- conversation: convoRows
191
- })
192
- })
193
- if (scriptType === Constants.SCRIPTING_TYPE_CONVO) {
194
- this.context.AddConvos(convos)
195
- } else if (scriptType === Constants.SCRIPTING_TYPE_PCONVO) {
196
- this.context.AddPartialConvos(convos)
197
- }
198
- return convos
242
+ debug(`Multi-column utterance file, extracted ${result.length} utterances`)
243
+ this.context.AddUtterances(result)
244
+ return result
245
+ } else {
246
+ return []
199
247
  }
200
248
  }
201
249
  }
@@ -339,7 +339,7 @@ module.exports = class CompilerXlsx extends CompilerBase {
339
339
  agg[varName] = varValues[varIndex][caseIndex] || null
340
340
  return agg
341
341
  }, {})
342
- scriptResults.push({ header: { name: caseName }, values: values })
342
+ scriptResults.push({ header: { name: caseName }, values })
343
343
  }
344
344
  } else {
345
345
  const variableNames = []
@@ -370,7 +370,7 @@ module.exports = class CompilerXlsx extends CompilerBase {
370
370
  }
371
371
  rowindex += 1
372
372
 
373
- scriptResults.push({ header: { name: caseName }, values: values })
373
+ scriptResults.push({ header: { name: caseName }, values })
374
374
  } else {
375
375
  break
376
376
  }
@@ -235,14 +235,14 @@ class Convo {
235
235
  await this.runConversation(container, scriptingMemory, transcript)
236
236
  await this._checkBotRepliesConsumed(container)
237
237
  try {
238
- await this.scriptingEvents.onConvoEnd({ convo: this, convoStep: { stepTag: '#end' }, container, transcript, scriptingMemory: scriptingMemory })
238
+ await this.scriptingEvents.onConvoEnd({ convo: this, convoStep: { stepTag: '#end' }, container, transcript, scriptingMemory })
239
239
  } catch (err) {
240
240
  throw new TranscriptError(botiumErrorFromErr(`${this.header.name}: ${err.message}`, err), transcript)
241
241
  }
242
242
  if (transcript.err && container.caps[Capabilities.SCRIPTING_ENABLE_MULTIPLE_ASSERT_ERRORS]) {
243
243
  let assertConvoEndErr = null
244
244
  try {
245
- await this.scriptingEvents.assertConvoEnd({ convo: this, convoStep: { stepTag: '#end' }, container, transcript, scriptingMemory: scriptingMemory })
245
+ await this.scriptingEvents.assertConvoEnd({ convo: this, convoStep: { stepTag: '#end' }, container, transcript, scriptingMemory })
246
246
  } catch (err) {
247
247
  assertConvoEndErr = botiumErrorFromErr(`${this.header.name}: ${err.message}`, err)
248
248
  }
@@ -257,7 +257,7 @@ class Convo {
257
257
  throw new TranscriptError(transcript.err, transcript)
258
258
  }
259
259
  try {
260
- await this.scriptingEvents.assertConvoEnd({ convo: this, convoStep: { stepTag: '#end' }, container, transcript, scriptingMemory: scriptingMemory })
260
+ await this.scriptingEvents.assertConvoEnd({ convo: this, convoStep: { stepTag: '#end' }, container, transcript, scriptingMemory })
261
261
  } catch (err) {
262
262
  transcript.err = botiumErrorFromErr(`${this.header.name}: ${err.message}`, err)
263
263
  throw new TranscriptError(transcript.err, transcript)
@@ -535,7 +535,7 @@ class Convo {
535
535
  if (container.caps[Capabilities.SCRIPTING_ENABLE_SKIP_ASSERT_ERRORS]) {
536
536
  const transcriptStepErrs = transcript.steps.filter(s => s.err).map(s => s.err)
537
537
  if (transcriptStepErrs && transcriptStepErrs.length > 0) {
538
- transcript.err = botiumErrorFromList([transcriptStepErrs, transcript.err].filter(e => e), {})
538
+ transcript.err = botiumErrorFromList([...transcriptStepErrs.filter(err => err !== transcript.err), transcript.err].filter(e => e), {})
539
539
  }
540
540
  }
541
541
  }
@@ -20,7 +20,7 @@ const RetryHelper = require('../helpers/RetryHelper')
20
20
  const { getMatchFunction } = require('./MatchFunctions')
21
21
  const precompilers = require('./precompilers')
22
22
 
23
- const globPattern = '**/+(*.convo.txt|*.utterances.txt|*.pconvo.txt|*.scriptingmemory.txt|*.xlsx|*.xlsm|*.convo.csv|*.pconvo.csv|*.yaml|*.yml|*.json|*.md|*.markdown)'
23
+ const globPattern = '**/+(*.convo.txt|*.utterances.txt|*.pconvo.txt|*.scriptingmemory.txt|*.xlsx|*.xlsm|*.convo.csv|*.pconvo.csv|*.utterances.csv|*.yaml|*.yml|*.json|*.md|*.markdown)'
24
24
  const skipPattern = /^skip[.\-_]/i
25
25
 
26
26
  const p = (retryHelper, fn) => {
@@ -522,6 +522,10 @@ module.exports = class ScriptingProvider {
522
522
  result = this.ReadScriptFromBuffer(scriptBuffer, Constants.SCRIPTING_FORMAT_CSV, Constants.SCRIPTING_TYPE_CONVO)
523
523
  } else if (filename.endsWith('.pconvo.csv')) {
524
524
  result = this.ReadScriptFromBuffer(scriptBuffer, Constants.SCRIPTING_FORMAT_CSV, Constants.SCRIPTING_TYPE_PCONVO)
525
+ } else if (filename.endsWith('.pconvo.csv')) {
526
+ result = this.ReadScriptFromBuffer(scriptBuffer, Constants.SCRIPTING_FORMAT_CSV, Constants.SCRIPTING_TYPE_PCONVO)
527
+ } else if (filename.endsWith('.utterance.csv')) {
528
+ result = this.ReadScriptFromBuffer(scriptBuffer, Constants.SCRIPTING_FORMAT_CSV, Constants.SCRIPTING_TYPE_UTTERANCES)
525
529
  } else if (filename.endsWith('.yaml') || filename.endsWith('.yml')) {
526
530
  result = this.ReadScriptFromBuffer(scriptBuffer, Constants.SCRIPTING_FORMAT_YAML, [Constants.SCRIPTING_TYPE_UTTERANCES, Constants.SCRIPTING_TYPE_PCONVO, Constants.SCRIPTING_TYPE_CONVO, Constants.SCRIPTING_TYPE_SCRIPTING_MEMORY])
527
531
  } else if (filename.endsWith('.json')) {
@@ -1192,7 +1196,7 @@ module.exports = class ScriptingProvider {
1192
1196
  const node = {
1193
1197
  sender: convoNode.sender,
1194
1198
  key: randomize('0', 20),
1195
- hash: hash,
1199
+ hash,
1196
1200
  convoNodes: convoNodeValues,
1197
1201
  convos: [_.cloneDeep(convoNodeHeader)],
1198
1202
  childNodes: []
@@ -77,6 +77,13 @@ const flatString = (str) => {
77
77
  return str ? str.split('\n').map(s => s.trim()).join(' ') : ''
78
78
  }
79
79
 
80
+ const _formatAppendArgs = (args) => {
81
+ return args ? ` ${args.map(a => _.isString(a) ? a.replace(/\|/g, '\\|') : `${a}`).join('|')}` : ''
82
+ }
83
+ const _parseArgs = (str) => {
84
+ return (str && str.length > 0 && str.replace(/\\\|/g, '###ESCAPESPLIT###').split('|').map(s => s.replace(/###ESCAPESPLIT###/g, '|').trim())) || []
85
+ }
86
+
80
87
  const linesToConvoStep = (lines, sender, context, eol, singleLineMode = false) => {
81
88
  if (!validateSender(sender)) throw new Error(`Failed to parse conversation. Section "${sender}" unknown.`)
82
89
 
@@ -106,14 +113,14 @@ const linesToConvoStep = (lines, sender, context, eol, singleLineMode = false) =
106
113
  }
107
114
  const name = logicLine.split(' ')[0]
108
115
  if (sender !== 'me' && context.IsAsserterValid(name)) {
109
- const args = (logicLine.length > name.length ? logicLine.substr(name.length + 1).split('|').map(a => a.trim()) : [])
116
+ const args = (logicLine.length > name.length ? _parseArgs(logicLine.substr(name.length + 1)) : [])
110
117
  convoStep.asserters.push({ name, args, not, optional })
111
118
  } else if (sender === 'me' && context.IsUserInputValid(name)) {
112
- const args = (logicLine.length > name.length ? logicLine.substr(name.length + 1).split('|').map(a => a.trim()) : [])
119
+ const args = (logicLine.length > name.length ? _parseArgs(logicLine.substr(name.length + 1)) : [])
113
120
  convoStep.userInputs.push({ name, args })
114
121
  textLinesAccepted = false
115
122
  } else if (context.IsLogicHookValid(name)) {
116
- const args = (logicLine.length > name.length ? logicLine.substr(name.length + 1).split('|').map(a => a.trim()) : [])
123
+ const args = (logicLine.length > name.length ? _parseArgs(logicLine.substr(name.length + 1)) : [])
117
124
  convoStep.logicHooks.push({ name, args })
118
125
  textLinesAccepted = false
119
126
  } else {
@@ -422,7 +429,7 @@ const convoStepToLines = (step) => {
422
429
  const lines = []
423
430
  if (step.sender === 'me') {
424
431
  step.forms && step.forms.filter(form => form.value).forEach((form) => {
425
- lines.push(`FORM ${form.name}|${form.value}`)
432
+ lines.push(`FORM${_formatAppendArgs([form.name, form.value])}`)
426
433
  })
427
434
  if (step.buttons && step.buttons.length > 0) {
428
435
  lines.push('BUTTON ' + _decompileButton(step.buttons[0]))
@@ -432,34 +439,34 @@ const convoStepToLines = (step) => {
432
439
  lines.push(step.messageText)
433
440
  }
434
441
  step.userInputs && step.userInputs.forEach((userInput) => {
435
- lines.push(userInput.name + (userInput.args ? ' ' + userInput.args.join('|') : ''))
442
+ lines.push(userInput.name + _formatAppendArgs(userInput.args))
436
443
  })
437
444
  step.logicHooks && step.logicHooks.forEach((logicHook) => {
438
- lines.push(logicHook.name + (logicHook.args ? ' ' + logicHook.args.join('|') : ''))
445
+ lines.push(logicHook.name + _formatAppendArgs(logicHook.args))
439
446
  })
440
447
  } else {
441
448
  if (step.messageText) {
442
449
  lines.push((step.optional ? '?' : '') + (step.not ? '!' : '') + step.messageText)
443
450
  }
444
- if (step.buttons && step.buttons.length > 0) lines.push('BUTTONS ' + step.buttons.filter(b => b.text).map(b => flatString(b.text)).join('|'))
445
- if (step.media && step.media.length > 0) lines.push('MEDIA ' + step.media.filter(m => !m.buffer && m.mediaUri).map(m => m.mediaUri).join('|'))
451
+ if (step.buttons && step.buttons.length > 0) lines.push('BUTTONS' + _formatAppendArgs(step.buttons.filter(b => b.text).map(b => flatString(b.text))))
452
+ if (step.media && step.media.length > 0) lines.push('MEDIA' + _formatAppendArgs(step.media.filter(m => !m.buffer && m.mediaUri).map(m => m.mediaUri)))
446
453
  if (step.cards && step.cards.length > 0) {
447
454
  step.cards.forEach(c => {
448
455
  let cardTexts = []
449
456
  if (c.text) cardTexts = cardTexts.concat(_.isArray(c.text) ? c.text : [c.text])
450
457
  if (c.subtext) cardTexts = cardTexts.concat(_.isArray(c.subtext) ? c.subtext : [c.subtext])
451
458
  if (c.content) cardTexts = cardTexts.concat(_.isArray(c.content) ? c.content : [c.content])
452
- if (cardTexts.length > 0) lines.push('CARDS ' + cardTexts.map(c => flatString(c)).join('|'))
459
+ if (cardTexts.length > 0) lines.push('CARDS' + _formatAppendArgs(cardTexts.map(c => flatString(c))))
453
460
 
454
- if (c.buttons && c.buttons.length > 0) lines.push('BUTTONS ' + c.buttons.filter(b => b.text).map(b => flatString(b.text)).join('|'))
461
+ if (c.buttons && c.buttons.length > 0) lines.push('BUTTONS' + _formatAppendArgs(c.buttons.filter(b => b.text).map(b => flatString(b.text))))
455
462
  if (c.image && !c.image.buffer && c.image.mediaUri) lines.push('MEDIA ' + c.image.mediaUri)
456
463
  })
457
464
  }
458
465
  step.asserters && step.asserters.forEach((asserter) => {
459
- lines.push((asserter.optional ? '?' : '') + (asserter.not ? '!' : '') + asserter.name + (asserter.args ? ' ' + asserter.args.join('|') : ''))
466
+ lines.push((asserter.optional ? '?' : '') + (asserter.not ? '!' : '') + asserter.name + _formatAppendArgs(asserter.args))
460
467
  })
461
468
  step.logicHooks && step.logicHooks.forEach((logicHook) => {
462
- lines.push(logicHook.name + (logicHook.args ? ' ' + logicHook.args.join('|') : ''))
469
+ lines.push(logicHook.name + _formatAppendArgs(logicHook.args))
463
470
  })
464
471
  }
465
472
  return lines.map(l => l.trim())
@@ -494,7 +501,7 @@ const linesToScriptingMemories = (lines, columnMode = null) => {
494
501
  agg[varName] = varValues[varIndex][caseIndex] || null
495
502
  return agg
496
503
  }, {})
497
- const scriptingMemory = { header: { name: caseName }, values: values }
504
+ const scriptingMemory = { header: { name: caseName }, values }
498
505
  scriptingMemories.push(scriptingMemory)
499
506
  }
500
507
  } else {
@@ -28,7 +28,7 @@ module.exports = class BaseCountAsserter {
28
28
  args
29
29
  },
30
30
  cause: {
31
- not: not,
31
+ not,
32
32
  expected: check,
33
33
  actual: count
34
34
  }
@@ -1,5 +1,7 @@
1
+ const { SCRIPTING_NORMALIZE_TEXT } = require('../../../Capabilities')
1
2
  const { BotiumError } = require('../../BotiumError')
2
3
  const { buttonsFromMsg } = require('../helpers')
4
+ const { normalizeText } = require('../../helper')
3
5
 
4
6
  module.exports = class ButtonsAsserter {
5
7
  constructor (context, caps = {}) {
@@ -9,14 +11,14 @@ module.exports = class ButtonsAsserter {
9
11
  }
10
12
 
11
13
  _evalButtons (args, botMsg) {
12
- const allButtons = buttonsFromMsg(botMsg, true).map(b => b.text)
14
+ const allButtons = buttonsFromMsg(botMsg, true).map(b => b.text).filter(b => b).map(b => normalizeText(b, !!this.caps[SCRIPTING_NORMALIZE_TEXT]))
13
15
  if (!args || args.length === 0) {
14
16
  return { allButtons, buttonsNotFound: [], buttonsFound: allButtons }
15
17
  }
16
18
  const buttonsNotFound = []
17
19
  const buttonsFound = []
18
20
  for (let i = 0; i < (args || []).length; i++) {
19
- if (allButtons.findIndex(b => this.context.Match(b, args[i])) < 0) {
21
+ if (allButtons.findIndex(b => this.context.Match(b, normalizeText(args[i], !!this.caps[SCRIPTING_NORMALIZE_TEXT]))) < 0) {
20
22
  buttonsNotFound.push(args[i])
21
23
  } else {
22
24
  buttonsFound.push(args[i])
@@ -1,5 +1,7 @@
1
+ const { SCRIPTING_NORMALIZE_TEXT } = require('../../../Capabilities')
1
2
  const { BotiumError } = require('../../BotiumError')
2
3
  const { cardsFromMsg } = require('../helpers')
4
+ const { normalizeText } = require('../../helper')
3
5
 
4
6
  module.exports = class CardsAsserter {
5
7
  constructor (context, caps = {}) {
@@ -9,14 +11,14 @@ module.exports = class CardsAsserter {
9
11
  }
10
12
 
11
13
  _evalCards (args, botMsg) {
12
- const allCards = cardsFromMsg(botMsg, true).reduce((acc, mc) => acc.concat([mc.text, mc.subtext, mc.content].filter(t => t)), [])
14
+ const allCards = cardsFromMsg(botMsg, true).reduce((acc, mc) => acc.concat([mc.text, mc.subtext, mc.content].filter(t => t).map(t => normalizeText(t, !!this.caps[SCRIPTING_NORMALIZE_TEXT]))), [])
13
15
  if (!args || args.length === 0) {
14
16
  return { allCards, cardsNotFound: [], cardsFound: allCards }
15
17
  }
16
18
  const cardsNotFound = []
17
19
  const cardsFound = []
18
20
  for (let i = 0; i < (args || []).length; i++) {
19
- if (allCards.findIndex(c => this.context.Match(c, args[i])) < 0) {
21
+ if (allCards.findIndex(c => this.context.Match(c, normalizeText(args[i], !!this.caps[SCRIPTING_NORMALIZE_TEXT]))) < 0) {
20
22
  cardsNotFound.push(args[i])
21
23
  } else {
22
24
  cardsFound.push(args[i])